diff --git a/.changeset/plenty-shirts-tease.md b/.changeset/plenty-shirts-tease.md new file mode 100644 index 00000000000..c5ab218f22c --- /dev/null +++ b/.changeset/plenty-shirts-tease.md @@ -0,0 +1,5 @@ +--- +'@clerk/backend': patch +--- + +Add `last_authenticated_at` to `SAMLAccount` resource, which represents the date when the SAML account was last authenticated diff --git a/.changeset/thick-jokes-talk.md b/.changeset/thick-jokes-talk.md new file mode 100644 index 00000000000..d4e96024416 --- /dev/null +++ b/.changeset/thick-jokes-talk.md @@ -0,0 +1,7 @@ +--- +'@clerk/clerk-js': patch +'@clerk/types': patch +--- + +- Add experimental property `last_authenticated_at` to `SamlAccount` resource, which represents the date when the SAML account was last authenticated +- Add experimental support for `enterprise_sso` as a `strategy` param for `session.prepareFirstFactorVerification` diff --git a/packages/backend/src/api/resources/JSON.ts b/packages/backend/src/api/resources/JSON.ts index 8e5ec664887..6e99e0855fa 100644 --- a/packages/backend/src/api/resources/JSON.ts +++ b/packages/backend/src/api/resources/JSON.ts @@ -246,6 +246,7 @@ export interface SamlAccountJSON extends ClerkResourceJSON { last_name: string; verification: VerificationJSON | null; saml_connection: SamlAccountConnectionJSON | null; + last_authenticated_at: number | null; } export interface IdentificationLinkJSON extends ClerkResourceJSON { diff --git a/packages/backend/src/api/resources/SamlAccount.ts b/packages/backend/src/api/resources/SamlAccount.ts index ba061536c34..7fb22daaf7f 100644 --- a/packages/backend/src/api/resources/SamlAccount.ts +++ b/packages/backend/src/api/resources/SamlAccount.ts @@ -43,6 +43,10 @@ export class SamlAccount { * The SAML connection of the SAML account. */ readonly samlConnection: SamlAccountConnection | null, + /** + * The date when the SAML account was last authenticated. + */ + readonly lastAuthenticatedAt: number | null, ) {} static fromJSON(data: SamlAccountJSON): SamlAccount { @@ -56,6 +60,7 @@ export class SamlAccount { data.last_name, data.verification && Verification.fromJSON(data.verification), data.saml_connection && SamlAccountConnection.fromJSON(data.saml_connection), + data.last_authenticated_at ?? null, ); } } diff --git a/packages/clerk-js/src/core/resources/EnterpriseAccount.ts b/packages/clerk-js/src/core/resources/EnterpriseAccount.ts index 8a884c089f7..eb6797e51cc 100644 --- a/packages/clerk-js/src/core/resources/EnterpriseAccount.ts +++ b/packages/clerk-js/src/core/resources/EnterpriseAccount.ts @@ -24,6 +24,7 @@ export class EnterpriseAccount extends BaseResource implements EnterpriseAccount publicMetadata = {}; verification: VerificationResource | null = null; enterpriseConnection: EnterpriseAccountConnectionResource | null = null; + lastAuthenticatedAt: Date | null = null; public constructor(data: Partial, pathRoot: string); public constructor(data: EnterpriseAccountJSON | EnterpriseAccountJSONSnapshot, pathRoot: string) { @@ -46,7 +47,7 @@ export class EnterpriseAccount extends BaseResource implements EnterpriseAccount this.firstName = data.first_name; this.lastName = data.last_name; this.publicMetadata = data.public_metadata; - + this.lastAuthenticatedAt = data.last_authenticated_at ? unixEpochToDate(data.last_authenticated_at) : null; if (data.verification) { this.verification = new Verification(data.verification); } @@ -72,6 +73,7 @@ export class EnterpriseAccount extends BaseResource implements EnterpriseAccount public_metadata: this.publicMetadata, verification: this.verification?.__internal_toSnapshot() || null, enterprise_connection: this.enterpriseConnection?.__internal_toSnapshot() || null, + last_authenticated_at: this.lastAuthenticatedAt ? this.lastAuthenticatedAt.getTime() : null, }; } } diff --git a/packages/clerk-js/src/core/resources/SamlAccount.ts b/packages/clerk-js/src/core/resources/SamlAccount.ts index 1fb407edb35..40078ac63b6 100644 --- a/packages/clerk-js/src/core/resources/SamlAccount.ts +++ b/packages/clerk-js/src/core/resources/SamlAccount.ts @@ -23,6 +23,7 @@ export class SamlAccount extends BaseResource implements SamlAccountResource { lastName = ''; verification: VerificationResource | null = null; samlConnection: SamlAccountConnectionResource | null = null; + lastAuthenticatedAt: Date | null = null; public constructor(data: Partial, pathRoot: string); public constructor(data: SamlAccountJSON | SamlAccountJSONSnapshot, pathRoot: string) { @@ -52,6 +53,8 @@ export class SamlAccount extends BaseResource implements SamlAccountResource { this.samlConnection = new SamlAccountConnection(data.saml_connection); } + this.lastAuthenticatedAt = data.last_authenticated_at ? unixEpochToDate(data.last_authenticated_at) : null; + return this; } @@ -67,6 +70,7 @@ export class SamlAccount extends BaseResource implements SamlAccountResource { last_name: this.lastName, verification: this.verification?.__internal_toSnapshot() || null, saml_connection: this.samlConnection?.__internal_toSnapshot(), + last_authenticated_at: this.lastAuthenticatedAt ? this.lastAuthenticatedAt.getTime() : null, }; } } diff --git a/packages/clerk-js/src/core/resources/Session.ts b/packages/clerk-js/src/core/resources/Session.ts index 08161325f27..7128832ccaf 100644 --- a/packages/clerk-js/src/core/resources/Session.ts +++ b/packages/clerk-js/src/core/resources/Session.ts @@ -6,6 +6,7 @@ import type { ActClaim, CheckAuthorization, EmailCodeConfig, + EnterpriseSSOConfig, GetToken, GetTokenOptions, PhoneCodeConfig, @@ -179,6 +180,13 @@ export class Session extends BaseResource implements SessionResource { case 'passkey': config = {}; break; + case 'enterprise_sso': + config = { + emailAddressId: factor.emailAddressId, + enterpriseConnectionId: factor.enterpriseConnectionId, + redirectUrl: factor.redirectUrl, + } as EnterpriseSSOConfig; + break; default: clerkInvalidStrategy('Session.prepareFirstFactorVerification', (factor as any).strategy); } diff --git a/packages/clerk-js/src/ui/components/UserVerification/AlternativeMethods.tsx b/packages/clerk-js/src/ui/components/UserVerification/AlternativeMethods.tsx index 509835a6a15..fd0a8102da5 100644 --- a/packages/clerk-js/src/ui/components/UserVerification/AlternativeMethods.tsx +++ b/packages/clerk-js/src/ui/components/UserVerification/AlternativeMethods.tsx @@ -10,7 +10,7 @@ import { formatSafeIdentifier } from '@/ui/utils/formatSafeIdentifier'; import type { LocalizationKey } from '../../customizables'; import { Col, descriptors, Flex, Flow, localizationKeys } from '../../customizables'; import { useCardState } from '../../elements/contexts'; -import { ChatAltIcon, Email, Fingerprint, LockClosedIcon } from '../../icons'; +import { ChatAltIcon, Email, Fingerprint, LockClosedIcon, Organization } from '../../icons'; import { useReverificationAlternativeStrategies } from './useReverificationAlternativeStrategies'; import { useUserVerificationSession } from './useUserVerificationSession'; import { withHavingTrouble } from './withHavingTrouble'; @@ -128,6 +128,7 @@ export function getButtonIcon(factor: SessionVerificationFirstFactor) { phone_code: ChatAltIcon, password: LockClosedIcon, passkey: Fingerprint, + enterprise_sso: Organization, } as const; return icons[factor.strategy]; diff --git a/packages/types/src/factors.ts b/packages/types/src/factors.ts index 407b5904538..2c2e11531dc 100644 --- a/packages/types/src/factors.ts +++ b/packages/types/src/factors.ts @@ -124,6 +124,10 @@ export type EnterpriseSSOConfig = EnterpriseSSOFactor & { redirectUrl: string; actionCompleteRedirectUrl: string; oidcPrompt?: string; + /** + * @experimental + */ + emailAddressId?: string; /** * @experimental */ diff --git a/packages/types/src/json.ts b/packages/types/src/json.ts index dc1fdee75e5..8860137b287 100644 --- a/packages/types/src/json.ts +++ b/packages/types/src/json.ts @@ -252,6 +252,7 @@ export interface EnterpriseAccountJSON extends ClerkResourceJSON { provider_user_id: string | null; public_metadata: Record; verification: VerificationJSON | null; + last_authenticated_at: number | null; } export interface EnterpriseAccountConnectionJSON extends ClerkResourceJSON { @@ -279,6 +280,7 @@ export interface SamlAccountJSON extends ClerkResourceJSON { last_name: string; verification?: VerificationJSON; saml_connection?: SamlAccountConnectionJSON; + last_authenticated_at: number | null; } export interface UserJSON extends ClerkResourceJSON { diff --git a/packages/types/src/session.ts b/packages/types/src/session.ts index a189d85c73c..11629a838d4 100644 --- a/packages/types/src/session.ts +++ b/packages/types/src/session.ts @@ -2,6 +2,7 @@ import type { BackupCodeAttempt, EmailCodeAttempt, EmailCodeConfig, + EnterpriseSSOConfig, PasskeyAttempt, PassKeyConfig, PasswordAttempt, @@ -351,7 +352,14 @@ export type SessionVerifyCreateParams = { level: SessionVerificationLevel; }; -export type SessionVerifyPrepareFirstFactorParams = EmailCodeConfig | PhoneCodeConfig | PassKeyConfig; +export type SessionVerifyPrepareFirstFactorParams = + | EmailCodeConfig + | PhoneCodeConfig + | PassKeyConfig + /** + * @experimental + */ + | Omit; export type SessionVerifyAttemptFirstFactorParams = | EmailCodeAttempt | PhoneCodeAttempt diff --git a/packages/types/src/sessionVerification.ts b/packages/types/src/sessionVerification.ts index ffc4341abe0..61af637ce2b 100644 --- a/packages/types/src/sessionVerification.ts +++ b/packages/types/src/sessionVerification.ts @@ -1,6 +1,7 @@ import type { BackupCodeFactor, EmailCodeFactor, + EnterpriseSSOFactor, PasskeyFactor, PasswordFactor, PhoneCodeFactor, @@ -49,5 +50,13 @@ export type ReverificationConfig = export type SessionVerificationLevel = 'first_factor' | 'second_factor' | 'multi_factor'; export type SessionVerificationAfterMinutes = number; -export type SessionVerificationFirstFactor = EmailCodeFactor | PhoneCodeFactor | PasswordFactor | PasskeyFactor; +export type SessionVerificationFirstFactor = + | EmailCodeFactor + | PhoneCodeFactor + | PasswordFactor + | PasskeyFactor + /** + * @experimental + */ + | EnterpriseSSOFactor; export type SessionVerificationSecondFactor = PhoneCodeFactor | TOTPFactor | BackupCodeFactor;