Skip to content

Commit e6b7584

Browse files
authored
Merge branch 'main' into feat--secret-cursor-focus-shift
2 parents 94bc969 + 963420c commit e6b7584

File tree

7 files changed

+103
-93
lines changed

7 files changed

+103
-93
lines changed

backend/api/emails.py

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,13 @@
33
from django.template.loader import render_to_string
44
from datetime import datetime
55
import os
6+
import logging
67
from api.utils.rest import encode_string_to_base64, get_client_ip
78
from api.models import OrganisationMember
89
from django.utils import timezone
10+
from smtplib import SMTPException
11+
12+
logger = logging.getLogger(__name__)
913

1014

1115
def get_org_member_name(org_member):
@@ -29,14 +33,24 @@ def send_email(subject, recipient_list, template_name, context):
2933
# Get the DEFAULT_FROM_EMAIL from settings
3034
default_from_email = getattr(settings, "DEFAULT_FROM_EMAIL")
3135

32-
# Send the email
33-
send_mail(
34-
subject,
35-
"", # plain text content can be empty as we're sending HTML
36-
f"Phase <{default_from_email}>",
37-
recipient_list,
38-
html_message=email_html_message,
39-
)
36+
try:
37+
# Send the email
38+
send_mail(
39+
subject,
40+
"", # plain text content can be empty as we're sending HTML
41+
f"Phase <{default_from_email}>",
42+
recipient_list,
43+
html_message=email_html_message,
44+
fail_silently=False, # Changed to False to catch exceptions
45+
)
46+
logger.debug(f"Email sent successfully: {subject} to {recipient_list}")
47+
return True
48+
except SMTPException as e:
49+
logger.error(f"SMTP Error sending email to {recipient_list}: {str(e)}")
50+
return False
51+
except Exception as e:
52+
logger.error(f"Unexpected error sending email to {recipient_list}: {str(e)}")
53+
return False
4054

4155

4256
def send_login_email(request, email, full_name, provider):

backend/backend/settings.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,10 +179,12 @@ def get_version():
179179
EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
180180
EMAIL_HOST = os.getenv("SMTP_SERVER")
181181
EMAIL_PORT = int(os.getenv("SMTP_PORT", 587))
182-
EMAIL_USE_TLS = True
182+
EMAIL_USE_TLS = os.getenv("SMTP_USE_TLS", "True").lower() in ("true", "1")
183+
EMAIL_USE_SSL = os.getenv("SMTP_USE_SSL", "False").lower() in ("true", "1")
183184
EMAIL_HOST_USER = os.getenv("SMTP_USERNAME")
184185
EMAIL_HOST_PASSWORD = get_secret("SMTP_PASSWORD")
185186
DEFAULT_FROM_EMAIL = os.getenv("DEFAULT_FROM_EMAIL")
187+
EMAIL_TIMEOUT = int(os.getenv("SMTP_TIMEOUT", 5))
186188

187189

188190
SITE_ID = 1

backend/version.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
v2.39.0
1+
v2.39.2

frontend/app/[team]/access/service-accounts/[account]/_components/DeleteServiceAccountTokenDialog.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export const DeleteServiceAccountTokenDialog = ({ token }: { token: ServiceAccou
2020

2121
const dialogRef = useRef<{ closeModal: () => void }>(null)
2222

23-
const [deleteToken] = useMutation(DeleteServiceAccountTokenOp)
23+
const [deleteToken, { loading }] = useMutation(DeleteServiceAccountTokenOp)
2424

2525
const handleDelete = async () => {
2626
const deleted = await deleteToken({
@@ -49,7 +49,7 @@ export const DeleteServiceAccountTokenDialog = ({ token }: { token: ServiceAccou
4949
<div className="space-y-4">
5050
<div className="text-neutral-500 py-4">Are you sure you want to delete this token?</div>
5151
<div className="flex justify-end">
52-
<Button variant="danger" onClick={handleDelete}>
52+
<Button variant="danger" onClick={handleDelete} isLoading={loading}>
5353
<FaTrash /> Delete
5454
</Button>
5555
</div>

frontend/app/[team]/access/service-accounts/[account]/page.tsx

Lines changed: 72 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import { Button } from '@/components/common/Button'
3131
import { toast } from 'react-toastify'
3232
import CopyButton from '@/components/common/CopyButton'
3333
import { SseLabel } from '@/components/apps/EncryptionModeIndicator'
34+
import clsx from 'clsx'
3435

3536
export default function ServiceAccount({ params }: { params: { team: string; account: string } }) {
3637
const { activeOrganisation: organisation } = useContext(organisationContext)
@@ -319,74 +320,85 @@ export default function ServiceAccount({ params }: { params: { team: string; acc
319320
{userCanReadTokens ? (
320321
<div className="space-y-2 divide-y divide-neutral-500/20 py-4">
321322
{account.tokens && account.tokens.length > 0 ? (
322-
account.tokens.map((token) => (
323-
<div
324-
key={token!.id}
325-
className="grid grid-cols-1 md:grid-cols-12 gap-4 items-center p-2 group"
326-
>
327-
{/* Token Name and ID*/}
328-
<div className="md:col-span-4 space-y-1">
329-
<div className="flex items-center gap-2">
330-
<FaKey className="text-neutral-500 flex-shrink-0" />
331-
<span className="font-medium text-lg text-zinc-900 dark:text-zinc-100 truncate">
332-
{token!.name}
333-
</span>
334-
</div>
335-
<div className="flex items-center gap-2 text-sm text-neutral-500">
336-
<span className="text-neutral-500 text-xs flex items-center">
337-
Token ID:
338-
</span>
339-
<CopyButton
340-
value={token!.id}
341-
buttonVariant="ghost"
342-
title="Copy Token ID to clipboard"
343-
>
344-
<span className="text-neutral-500 text-2xs font-mono">{token!.id}</span>
345-
</CopyButton>
323+
account.tokens.map((token) => {
324+
const isExpired =
325+
token!.expiresAt === null ? false : new Date(token!.expiresAt) < new Date()
326+
return (
327+
<div
328+
key={token!.id}
329+
className="grid grid-cols-1 md:grid-cols-12 gap-4 items-center p-2 group"
330+
>
331+
{/* Token Name and ID*/}
332+
<div className="md:col-span-4 space-y-1">
333+
<div className="flex items-center gap-2">
334+
<FaKey className="text-neutral-500 flex-shrink-0" />
335+
<span className="font-medium text-lg text-zinc-900 dark:text-zinc-100 truncate">
336+
{token!.name}
337+
</span>
338+
</div>
339+
<div className="flex items-center gap-2 text-sm text-neutral-500">
340+
<span className="text-neutral-500 text-xs flex items-center">
341+
Token ID:
342+
</span>
343+
<CopyButton
344+
value={token!.id}
345+
buttonVariant="ghost"
346+
title="Copy Token ID to clipboard"
347+
>
348+
<span className="text-neutral-500 text-2xs font-mono">{token!.id}</span>
349+
</CopyButton>
350+
</div>
346351
</div>
347-
</div>
348352

349-
{/* Created Info*/}
350-
<div className="md:col-span-4 text-neutral-500 text-sm flex flex-col gap-1">
351-
<div className="whitespace-nowrap">
352-
Created {relativeTimeFromDates(new Date(token?.createdAt))}
353-
</div>
354-
<div className="flex items-center gap-2">
355-
<span className="text-neutral-500">by:</span>
356-
<Avatar member={token!.createdBy!} size="sm" />
357-
<span className="font-medium text-zinc-900 dark:text-zinc-100">
358-
{token?.createdBy?.fullName}
359-
</span>
353+
{/* Created Info*/}
354+
<div className="md:col-span-4 text-neutral-500 text-sm flex flex-col gap-1">
355+
<div className="whitespace-nowrap">
356+
Created {relativeTimeFromDates(new Date(token?.createdAt))}
357+
</div>
358+
<div className="flex items-center gap-2">
359+
<span className="text-neutral-500">by</span>
360+
<Avatar member={token!.createdBy!} size="sm" />
361+
<span className="font-medium text-zinc-900 dark:text-zinc-100">
362+
{token?.createdBy?.fullName}
363+
</span>
364+
</div>
360365
</div>
361-
</div>
362366

363-
{/* Token Status*/}
364-
<div className="md:col-span-3 space-y-2">
365-
<div className="flex items-center gap-1 text-sm text-neutral-500">
366-
<span className="whitespace-nowrap">Expires:</span>
367-
<span className="whitespace-nowrap">
368-
{token!.expiresAt
369-
? relativeTimeFromDates(new Date(token?.expiresAt))
370-
: 'never'}
371-
</span>
372-
</div>
367+
{/* Token Status*/}
368+
<div className="md:col-span-3 space-y-2">
369+
<div
370+
className={clsx(
371+
'flex items-center gap-1 text-sm ',
372+
isExpired ? 'text-red-500' : 'text-neutral-500'
373+
)}
374+
>
375+
<span className="whitespace-nowrap">
376+
{isExpired ? 'Expired' : 'Expires'}
377+
</span>
378+
<span className="whitespace-nowrap">
379+
{token!.expiresAt
380+
? relativeTimeFromDates(new Date(token?.expiresAt))
381+
: 'never'}
382+
</span>
383+
</div>
373384

374-
<div className="flex items-center gap-1 text-sm text-neutral-500">
375-
<span className="whitespace-nowrap">Last used:</span>
376-
<span className="whitespace-nowrap">
377-
{token!.lastUsed
378-
? relativeTimeFromDates(new Date(token?.lastUsed))
379-
: 'never'}
380-
</span>
385+
<div className="flex items-center gap-1 text-sm text-neutral-500">
386+
<span className="whitespace-nowrap">Last used</span>
387+
<span className="whitespace-nowrap">
388+
{token!.lastUsed
389+
? relativeTimeFromDates(new Date(token?.lastUsed))
390+
: 'never'}
391+
</span>
392+
</div>
381393
</div>
382-
</div>
383394

384-
{/* Delete Button*/}
385-
<div className="md:col-span-1 flex justify-end opacity-0 group-hover:opacity-100 transition ease">
386-
<DeleteServiceAccountTokenDialog token={token!} />
395+
{/* Delete Button*/}
396+
<div className="md:col-span-1 flex justify-end opacity-0 group-hover:opacity-100 transition ease">
397+
<DeleteServiceAccountTokenDialog token={token!} />
398+
</div>
387399
</div>
388-
</div>
389-
))
400+
)
401+
})
390402
) : (
391403
<div className="py-8">
392404
<EmptyState

frontend/ee/authentication/sso/oidc/util/entraidProvider.ts

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -31,30 +31,13 @@ export function EntraIDProvider(options: EntraIDProviderConfig): OAuthConfig<Ent
3131
idToken: true,
3232
checks: ['pkce', 'state', 'nonce'],
3333
profile: async (profile, tokens) => {
34-
// Get the profile photo from the Microsoft Graph API
35-
let image
36-
try {
37-
const response = await fetch(`https://graph.microsoft.com/v1.0/me/photo/$value`, {
38-
headers: { Authorization: `Bearer ${tokens.access_token}` },
39-
})
40-
41-
// Convert ArrayBuffer to base64 if response is OK
42-
if (response.ok) {
43-
const pictureBuffer = await response.arrayBuffer()
44-
const pictureBase64 = Buffer.from(pictureBuffer).toString('base64')
45-
image = `data:image/jpeg;base64,${pictureBase64}`
46-
}
47-
} catch (error) {
48-
console.error('Error fetching Entra ID profile photo:', error)
49-
}
50-
51-
if (!profile.email) throw new Error("User does not have a valid email")
34+
if (!profile.email) throw new Error('User does not have a valid email')
5235

5336
return {
5437
id: profile.sub,
5538
name: profile.name || '',
5639
email: profile.email,
57-
image: image || profile.picture || '',
40+
image: '',
5841
}
5942
},
6043
}

frontend/utils/tokens.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ export const humanReadableExpiry = (expiryOption: ExpiryOptionT) =>
3333
? 'This token will never expire.'
3434
: `This token will expire on ${new Date(expiryOption.getExpiry()!).toLocaleDateString()}.`
3535

36-
3736
export const compareExpiryOptions = (a: ExpiryOptionT, b: ExpiryOptionT) => {
3837
return a.getExpiry() === b.getExpiry()
39-
}
38+
}

0 commit comments

Comments
 (0)