From f1c9ceed2e050c67252f6bcbd5e14e7d22c19e97 Mon Sep 17 00:00:00 2001 From: simeng-li Date: Mon, 1 Jul 2024 18:12:44 +0800 Subject: [PATCH] feat(core,schemas): implement social verification implement social verification --- .../connector-mock-social/src/index.ts | 18 +- .../experience/classes/interaction-session.ts | 4 +- .../experience/classes/verifications/index.ts | 16 +- .../verifications/social-verification.ts | 179 ++++++++++++++++ .../classes/verifications/verification.ts | 4 + packages/core/src/routes/experience/index.ts | 6 + .../social-verification.ts | 99 +++++++++ .../src/routes/interaction/types/index.ts | 6 + .../api/experience-api/social-verification.ts | 33 +++ .../helpers/experience/social-verification.ts | 47 ++++ .../social-verification.test.ts | 202 ++++++++++++++++++ .../social-sign-in/sad-path.test.ts | 2 +- packages/schemas/src/types/interactions.ts | 18 ++ 13 files changed, 627 insertions(+), 7 deletions(-) create mode 100644 packages/core/src/routes/experience/classes/verifications/social-verification.ts create mode 100644 packages/core/src/routes/experience/verification-routes/social-verification.ts create mode 100644 packages/integration-tests/src/api/experience-api/social-verification.ts create mode 100644 packages/integration-tests/src/helpers/experience/social-verification.ts create mode 100644 packages/integration-tests/src/tests/api/experience-api/verifications-api/social-verification.test.ts diff --git a/packages/connectors/connector-mock-social/src/index.ts b/packages/connectors/connector-mock-social/src/index.ts index b9c76058c352..ba9b3985b672 100644 --- a/packages/connectors/connector-mock-social/src/index.ts +++ b/packages/connectors/connector-mock-social/src/index.ts @@ -2,9 +2,9 @@ import { randomUUID } from 'node:crypto'; import { z } from 'zod'; import type { + CreateConnector, GetAuthorizationUri, GetUserInfo, - CreateConnector, SocialConnector, } from '@logto/connector-kit'; import { @@ -17,11 +17,16 @@ import { import { defaultMetadata } from './constant.js'; import { mockSocialConfigGuard } from './types.js'; -const getAuthorizationUri: GetAuthorizationUri = async ({ state, redirectUri }) => { +const getAuthorizationUri: GetAuthorizationUri = async ( + { state, redirectUri, connectorId }, + setSession +) => { + await setSession({ state, redirectUri, connectorId }); + return `http://mock.social.com/?state=${state}&redirect_uri=${redirectUri}`; }; -const getUserInfo: GetUserInfo = async (data) => { +const getUserInfo: GetUserInfo = async (data, getSession) => { const dataGuard = z.object({ code: z.string(), userId: z.optional(z.string()), @@ -34,6 +39,13 @@ const getUserInfo: GetUserInfo = async (data) => { throw new ConnectorError(ConnectorErrorCodes.InvalidResponse, JSON.stringify(data)); } + const connectorSession = await getSession(); + + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (!connectorSession) { + throw new ConnectorError(ConnectorErrorCodes.AuthorizationFailed); + } + const { code, userId, ...rest } = result.data; // For mock use only. Use to track the created user entity diff --git a/packages/core/src/routes/experience/classes/interaction-session.ts b/packages/core/src/routes/experience/classes/interaction-session.ts index 2884aac21190..4c3b0b61a3ce 100644 --- a/packages/core/src/routes/experience/classes/interaction-session.ts +++ b/packages/core/src/routes/experience/classes/interaction-session.ts @@ -83,6 +83,7 @@ export default class InteractionSession { /** Set the verified accountId of the current interaction session from the verification record */ public identifyUser(verificationRecord: VerificationRecord) { // Throws an 404 error if the user is not found by the given verification record + // TODO: refactor using real-time user verification. Static verifiedUserId will be removed. assertThat( verificationRecord.verifiedUserId, new RequestError( @@ -91,7 +92,8 @@ export default class InteractionSession { status: 404, }, { - identifier: verificationRecord.identifier.value, + identifier: + 'identifier' in verificationRecord ? verificationRecord.identifier : undefined, } ) ); diff --git a/packages/core/src/routes/experience/classes/verifications/index.ts b/packages/core/src/routes/experience/classes/verifications/index.ts index 4a4d1919b0f0..a3cb80353cf8 100644 --- a/packages/core/src/routes/experience/classes/verifications/index.ts +++ b/packages/core/src/routes/experience/classes/verifications/index.ts @@ -14,15 +14,24 @@ import { passwordVerificationRecordDataGuard, type PasswordVerificationRecordData, } from './password-verification.js'; +import { + SocialVerification, + socialVerificationRecordDataGuard, + type SocialVerificationRecordData, +} from './social-verification.js'; -type VerificationRecordData = PasswordVerificationRecordData | CodeVerificationRecordData; +type VerificationRecordData = + | PasswordVerificationRecordData + | CodeVerificationRecordData + | SocialVerificationRecordData; export const verificationRecordDataGuard = z.discriminatedUnion('type', [ passwordVerificationRecordDataGuard, codeVerificationRecordDataGuard, + socialVerificationRecordDataGuard, ]); -export type VerificationRecord = PasswordVerification | CodeVerification; +export type VerificationRecord = PasswordVerification | CodeVerification | SocialVerification; export const buildVerificationRecord = ( libraries: Libraries, @@ -36,5 +45,8 @@ export const buildVerificationRecord = ( case VerificationType.VerificationCode: { return new CodeVerification(libraries, queries, data); } + case VerificationType.Social: { + return new SocialVerification(libraries, queries, data); + } } }; diff --git a/packages/core/src/routes/experience/classes/verifications/social-verification.ts b/packages/core/src/routes/experience/classes/verifications/social-verification.ts new file mode 100644 index 000000000000..a73ae08bfca4 --- /dev/null +++ b/packages/core/src/routes/experience/classes/verifications/social-verification.ts @@ -0,0 +1,179 @@ +import { socialUserInfoGuard, type SocialUserInfo, type ToZodObject } from '@logto/connector-kit'; +import { + VerificationType, + type JsonObject, + type SocialAuthorizationUrlPayload, + type User, +} from '@logto/schemas'; +import { generateStandardId } from '@logto/shared'; +import { z } from 'zod'; + +import { type WithLogContext } from '#src/middleware/koa-audit-log.js'; +import { + createSocialAuthorizationUrl, + verifySocialIdentity, +} from '#src/routes/interaction/utils/social-verification.js'; +import type Libraries from '#src/tenants/Libraries.js'; +import type Queries from '#src/tenants/Queries.js'; +import type TenantContext from '#src/tenants/TenantContext.js'; + +import { type Verification } from './verification.js'; + +/** The JSON data type for the SocialVerification record stored in the interaction storage */ +export type SocialVerificationRecordData = { + id: string; + connectorId: string; + type: VerificationType.Social; + /** + * The social identity returned by the connector. + */ + socialUserInfo?: SocialUserInfo; + /** + * The userId of the user that has been verified by the social identity. + */ + userId?: string; +}; + +export const socialVerificationRecordDataGuard = z.object({ + id: z.string(), + connectorId: z.string(), + type: z.literal(VerificationType.Social), + socialUserInfo: socialUserInfoGuard.optional(), + userId: z.string().optional(), +}) satisfies ToZodObject; + +export class SocialVerification implements Verification { + /** + * Factory method to create a new SocialVerification instance + */ + static create(libraries: Libraries, queries: Queries, connectorId: string) { + return new SocialVerification(libraries, queries, { + id: generateStandardId(), + connectorId, + type: VerificationType.Social, + }); + } + + public readonly id: string; + public readonly type = VerificationType.Social; + public readonly connectorId: string; + public socialUserInfo?: SocialUserInfo; + public userId?: string; + + constructor( + private readonly libraries: Libraries, + private readonly queries: Queries, + data: SocialVerificationRecordData + ) { + const { id, connectorId, socialUserInfo, userId } = + socialVerificationRecordDataGuard.parse(data); + + this.id = id; + this.connectorId = connectorId; + this.socialUserInfo = socialUserInfo; + this.userId = userId; + } + + /** + * Returns true if the social identity has been verified + */ + get isVerified() { + return Boolean(this.socialUserInfo); + } + + get verifiedUserId() { + return this.userId; + } + + /** + * Create the authorization URL for the social connector. + * Store the connector session result in the provider's interaction storage. + * + * @remarks + * Refers to the {@link createSocialAuthorizationUrl} method in the interaction/utils/social-verification.ts file. + * Currently, all the intermediate connector session results are stored in the provider's interactionDetails separately, + * apart from the new verification record. + * For compatibility reasons, we keep using the old {@link createSocialAuthorizationUrl} method here as a single source of truth. + * Especially for the SAML connectors, + * SAML ACS endpoint will find the connector session result by the jti and assign it to the interaction storage. + * We will need to update the SAML ACS endpoint before move the logic to this new SocialVerification class. + * + * TODO: Consider store the connector session result in the verification record directly. + * SAML ACS endpoint will find the verification record by the jti and assign the connector session result to the verification record. + */ + async createAuthorizationUrl( + ctx: WithLogContext, + tenantContext: TenantContext, + { state, redirectUri }: SocialAuthorizationUrlPayload + ) { + return createSocialAuthorizationUrl(ctx, tenantContext, { + connectorId: this.connectorId, + state, + redirectUri, + }); + } + + /** + * Verify the social identity and store the social identity in the verification record. + * + * - Store the social identity in the verification record. + * - Find the user by the social identity and store the userId in the verification record if the user exists. + * + * @remarks + * Refer to the {@link verifySocialIdentity} method in the interaction/utils/social-verification.ts file. + * For compatibility reasons, we keep using the old {@link verifySocialIdentity} method here as a single source of truth. + * See the above {@link createAuthorizationUrl} method for more details. + * + * TODO: check the log event + */ + async verify(ctx: WithLogContext, tenantContext: TenantContext, connectorData: JsonObject) { + const socialUserInfo = await verifySocialIdentity( + { connectorId: this.connectorId, connectorData }, + ctx, + tenantContext + ); + + this.socialUserInfo = socialUserInfo; + + const user = await this.findUserBySocialIdentity(); + this.userId = user?.id; + } + + async findUserBySocialIdentity(): Promise { + const { socials } = this.libraries; + const { + users: { findUserByIdentity }, + } = this.queries; + + if (!this.socialUserInfo) { + return; + } + + const { + metadata: { target }, + } = await socials.getConnector(this.connectorId); + + const user = await findUserByIdentity(target, this.socialUserInfo.id); + + return user ?? undefined; + } + + async findRelatedUserBySocialIdentity(): ReturnType { + const { socials } = this.libraries; + + if (!this.socialUserInfo) { + return null; + } + + return socials.findSocialRelatedUser(this.socialUserInfo); + } + + toJson(): SocialVerificationRecordData { + return { + id: this.id, + connectorId: this.connectorId, + type: this.type, + socialUserInfo: this.socialUserInfo, + }; + } +} diff --git a/packages/core/src/routes/experience/classes/verifications/verification.ts b/packages/core/src/routes/experience/classes/verifications/verification.ts index e6095bb63e15..73120f2ecea3 100644 --- a/packages/core/src/routes/experience/classes/verifications/verification.ts +++ b/packages/core/src/routes/experience/classes/verifications/verification.ts @@ -8,6 +8,10 @@ export abstract class Verification { abstract readonly type: VerificationType; abstract get isVerified(): boolean; + /** + * @deprecated + * TODO: Remove this @simeng-li, should get the userId asynchronously in real-time + */ abstract get verifiedUserId(): string | undefined; abstract toJson(): { diff --git a/packages/core/src/routes/experience/index.ts b/packages/core/src/routes/experience/index.ts index ecc6140fbfca..98e57a8532cf 100644 --- a/packages/core/src/routes/experience/index.ts +++ b/packages/core/src/routes/experience/index.ts @@ -25,6 +25,7 @@ import koaInteractionSession, { type WithInteractionSessionContext, } from './middleware/koa-interaction-session.js'; import passwordVerificationRoutes from './verification-routes/password-verification.js'; +import socialVerificationRoutes from './verification-routes/social-verification.js'; import verificationCodeRoutes from './verification-routes/verification-code.js'; type RouterContext = T extends Router ? Context : never; @@ -60,6 +61,10 @@ export default function experienceApiRoutes( new RequestError({ code: 'session.verification_session_not_found', status: 404 }) ); + // TODO: SIE verification method check + // TODO: forgot password verification method check, only allow email and phone verification code + // TODO: user suspension check + ctx.interactionSession.identifyUser(verificationRecord); await ctx.interactionSession.save(); @@ -84,4 +89,5 @@ export default function experienceApiRoutes( passwordVerificationRoutes(router, tenant); verificationCodeRoutes(router, tenant); + socialVerificationRoutes(router, tenant); } diff --git a/packages/core/src/routes/experience/verification-routes/social-verification.ts b/packages/core/src/routes/experience/verification-routes/social-verification.ts new file mode 100644 index 000000000000..378390b81207 --- /dev/null +++ b/packages/core/src/routes/experience/verification-routes/social-verification.ts @@ -0,0 +1,99 @@ +import { + VerificationType, + socialAuthorizationUrlPayloadGuard, + socialVerificationCallbackPayloadGuard, +} from '@logto/schemas'; +import type Router from 'koa-router'; +import { z } from 'zod'; + +import RequestError from '#src/errors/RequestError/index.js'; +import { type WithLogContext } from '#src/middleware/koa-audit-log.js'; +import koaGuard from '#src/middleware/koa-guard.js'; +import type TenantContext from '#src/tenants/TenantContext.js'; +import assertThat from '#src/utils/assert-that.js'; + +import { SocialVerification } from '../classes/verifications/social-verification.js'; +import { experienceVerificationApiRoutesPrefix } from '../const.js'; +import { type WithInteractionSessionContext } from '../middleware/koa-interaction-session.js'; + +export default function socialVerificationRoutes( + router: Router>, + tenantContext: TenantContext +) { + const { libraries, queries } = tenantContext; + + router.post( + `${experienceVerificationApiRoutesPrefix}/social/:connectorId/authorization-uri`, + koaGuard({ + params: z.object({ + connectorId: z.string(), + }), + body: socialAuthorizationUrlPayloadGuard, + response: z.object({ + authorizationUri: z.string(), + verificationId: z.string(), + }), + status: [200, 400, 404, 500], + }), + async (ctx, next) => { + const { connectorId } = ctx.guard.params; + + const socialVerification = SocialVerification.create(libraries, queries, connectorId); + + const authorizationUri = await socialVerification.createAuthorizationUrl( + ctx, + tenantContext, + ctx.guard.body + ); + + ctx.interactionSession.appendVerificationRecord(socialVerification); + + await ctx.interactionSession.save(); + + ctx.body = { + authorizationUri, + verificationId: socialVerification.id, + }; + + return next(); + } + ); + + router.post( + `${experienceVerificationApiRoutesPrefix}/social/:connectorId/verify`, + koaGuard({ + params: z.object({ + connectorId: z.string(), + }), + body: socialVerificationCallbackPayloadGuard, + response: z.object({ + verificationId: z.string(), + }), + status: [200, 400, 404], + }), + async (ctx, next) => { + const { connectorId } = ctx.params; + const { connectorData, verificationId } = ctx.guard.body; + + const socialVerificationRecord = + ctx.interactionSession.getVerificationRecordById(verificationId); + + assertThat( + socialVerificationRecord && + socialVerificationRecord.type === VerificationType.Social && + socialVerificationRecord.connectorId === connectorId, + new RequestError({ code: 'session.verification_session_not_found', status: 404 }) + ); + + await socialVerificationRecord.verify(ctx, tenantContext, connectorData); + + await ctx.interactionSession.save(); + + ctx.body = { + verificationId, + }; + + return next(); + } + ); +} diff --git a/packages/core/src/routes/interaction/types/index.ts b/packages/core/src/routes/interaction/types/index.ts index a1bf7d6e35c2..1ddbcc6ed7bb 100644 --- a/packages/core/src/routes/interaction/types/index.ts +++ b/packages/core/src/routes/interaction/types/index.ts @@ -30,6 +30,12 @@ export type PasswordIdentifierPayload = export type SocialVerifiedIdentifierPayload = SocialEmailPayload | SocialPhonePayload; +/** + * @deprecated + * Legacy type for the interaction API. + * Use the latest experience API instead. + * Moved to `@logto/schemas` + */ export type SocialAuthorizationUrlPayload = z.infer; /* Interaction Types */ diff --git a/packages/integration-tests/src/api/experience-api/social-verification.ts b/packages/integration-tests/src/api/experience-api/social-verification.ts new file mode 100644 index 000000000000..c79af3aaecd9 --- /dev/null +++ b/packages/integration-tests/src/api/experience-api/social-verification.ts @@ -0,0 +1,33 @@ +import api from '../api.js'; + +import { experienceVerificationApiRoutesPrefix } from './const.js'; + +export const getSocialAuthorizationUri = async ( + cookie: string, + connectorId: string, + payload: { + redirectUri: string; + state: string; + } +) => + api + .post(`${experienceVerificationApiRoutesPrefix}/social/${connectorId}/authorization-uri`, { + headers: { cookie }, + json: payload, + }) + .json<{ authorizationUri: string; verificationId: string }>(); + +export const verifySocialAuthorization = async ( + cookie: string, + connectorId: string, + payload: { + verificationId: string; + connectorData: Record; + } +) => + api + .post(`${experienceVerificationApiRoutesPrefix}/social/${connectorId}/verify`, { + headers: { cookie }, + json: payload, + }) + .json<{ verificationId: string }>(); diff --git a/packages/integration-tests/src/helpers/experience/social-verification.ts b/packages/integration-tests/src/helpers/experience/social-verification.ts new file mode 100644 index 000000000000..795ac8518ec1 --- /dev/null +++ b/packages/integration-tests/src/helpers/experience/social-verification.ts @@ -0,0 +1,47 @@ +import { + getSocialAuthorizationUri, + verifySocialAuthorization, +} from '#src/api/experience-api/social-verification.js'; +import type MockClient from '#src/client/index.js'; + +export const successFullyCreateSocialVerification = async ( + client: MockClient, + connectorId: string, + payload: { + redirectUri: string; + state: string; + } +) => { + const { authorizationUri, verificationId } = await client.send( + getSocialAuthorizationUri, + connectorId, + payload + ); + + expect(verificationId).toBeTruthy(); + expect(authorizationUri).toBeTruthy(); + + return { + verificationId, + authorizationUri, + }; +}; + +export const successFullyVerifySocialAuthorization = async ( + client: MockClient, + connectorId: string, + payload: { + verificationId: string; + connectorData: Record; + } +) => { + const { verificationId: verifiedVerificationId } = await client.send( + verifySocialAuthorization, + connectorId, + payload + ); + + expect(verifiedVerificationId).toBeTruthy(); + + return verifiedVerificationId; +}; diff --git a/packages/integration-tests/src/tests/api/experience-api/verifications-api/social-verification.test.ts b/packages/integration-tests/src/tests/api/experience-api/verifications-api/social-verification.test.ts new file mode 100644 index 000000000000..0b0908226c39 --- /dev/null +++ b/packages/integration-tests/src/tests/api/experience-api/verifications-api/social-verification.test.ts @@ -0,0 +1,202 @@ +import { ConnectorType } from '@logto/connector-kit'; +import { InteractionEvent } from '@logto/schemas'; + +import { mockEmailConnectorId, mockSocialConnectorId } from '#src/__mocks__/connectors-mock.js'; +import { + getSocialAuthorizationUri, + verifySocialAuthorization, +} from '#src/api/experience-api/social-verification.js'; +import { sendVerificationCode } from '#src/api/experience-api/verification-code.js'; +import { initClient } from '#src/helpers/client.js'; +import { + clearConnectorsByTypes, + setEmailConnector, + setSocialConnector, +} from '#src/helpers/connector.js'; +import { + successFullyCreateSocialVerification, + successFullyVerifySocialAuthorization, +} from '#src/helpers/experience/social-verification.js'; +import { expectRejects } from '#src/helpers/index.js'; +import { devFeatureTest } from '#src/utils.js'; + +devFeatureTest.describe('social verification', () => { + const state = 'fake_state'; + const redirectUri = 'http://localhost:3000/redirect'; + const authorizationCode = 'fake_code'; + const connectorIdMap = new Map(); + + beforeAll(async () => { + await clearConnectorsByTypes([ConnectorType.Social, ConnectorType.Email]); + + const { id: emailConnectorId } = await setEmailConnector(); + const { id: socialConnectorId } = await setSocialConnector(); + connectorIdMap.set(mockSocialConnectorId, socialConnectorId); + connectorIdMap.set(mockEmailConnectorId, emailConnectorId); + }); + + afterAll(async () => { + await clearConnectorsByTypes([ConnectorType.Social, ConnectorType.Email]); + }); + + describe('getSocialAuthorizationUri', () => { + it('should throw if the state or redirectUri is empty', async () => { + const client = await initClient(); + const connectorId = connectorIdMap.get(mockSocialConnectorId)!; + + await expectRejects( + client.send(getSocialAuthorizationUri, connectorId, { + redirectUri, + state: '', + }), + { + code: 'session.insufficient_info', + status: 400, + } + ); + + await expectRejects( + client.send(getSocialAuthorizationUri, connectorId, { + redirectUri: '', + state, + }), + { + code: 'session.insufficient_info', + status: 400, + } + ); + }); + + it('should throw if the connector is not a social connector', async () => { + const client = await initClient(); + const connectorId = connectorIdMap.get(mockEmailConnectorId)!; + + await expectRejects( + client.send(getSocialAuthorizationUri, connectorId, { + redirectUri, + state, + }), + { + code: 'connector.unexpected_type', + status: 400, + } + ); + }); + + it('should throw if the connector is not found', async () => { + const client = await initClient(); + + await expectRejects( + client.send(getSocialAuthorizationUri, 'invalid_connector_id', { + redirectUri, + state, + }), + { + code: 'entity.not_found', + status: 404, + } + ); + }); + + it('should return the authorizationUri and verificationId', async () => { + const client = await initClient(); + const connectorId = connectorIdMap.get(mockSocialConnectorId)!; + + await successFullyCreateSocialVerification(client, connectorId, { + redirectUri, + state, + }); + }); + }); + + describe('verifySocialAuthorization', () => { + it('should throw if the verification record is not found', async () => { + const client = await initClient(); + const connectorId = connectorIdMap.get(mockSocialConnectorId)!; + + await successFullyCreateSocialVerification(client, connectorId, { + redirectUri, + state, + }); + + await expectRejects( + client.send(verifySocialAuthorization, connectorId, { + verificationId: 'invalid_verification_id', + connectorData: { + authorizationCode, + }, + }), + { + code: 'session.verification_session_not_found', + status: 404, + } + ); + }); + + it('should throw if the verification type is not social', async () => { + const client = await initClient(); + const connectorId = connectorIdMap.get(mockEmailConnectorId)!; + + const { verificationId } = await client.send(sendVerificationCode, { + identifier: { + type: 'email', + value: 'foo', + }, + interactionEvent: InteractionEvent.SignIn, + }); + + await expectRejects( + client.send(verifySocialAuthorization, connectorId, { + verificationId, + connectorData: { + authorizationCode, + }, + }), + { + code: 'session.verification_session_not_found', + status: 404, + } + ); + }); + + it('should throw if the connectorId is different', async () => { + const client = await initClient(); + const connectorId = connectorIdMap.get(mockSocialConnectorId)!; + + const { verificationId } = await client.send(getSocialAuthorizationUri, connectorId, { + redirectUri, + state, + }); + + await expectRejects( + client.send(verifySocialAuthorization, 'invalid_connector_id', { + verificationId, + connectorData: { + authorizationCode, + }, + }), + { + code: 'session.verification_session_not_found', + status: 404, + } + ); + }); + + it('should successfully verify the social authorization', async () => { + const client = await initClient(); + const connectorId = connectorIdMap.get(mockSocialConnectorId)!; + + const { verificationId } = await successFullyCreateSocialVerification(client, connectorId, { + redirectUri, + state, + }); + + await successFullyVerifySocialAuthorization(client, connectorId, { + verificationId, + connectorData: { + code: authorizationCode, + }, + }); + }); + }); +}); diff --git a/packages/integration-tests/src/tests/api/interaction/social-sign-in/sad-path.test.ts b/packages/integration-tests/src/tests/api/interaction/social-sign-in/sad-path.test.ts index b9be50cf8157..d7026d5ea7e6 100644 --- a/packages/integration-tests/src/tests/api/interaction/social-sign-in/sad-path.test.ts +++ b/packages/integration-tests/src/tests/api/interaction/social-sign-in/sad-path.test.ts @@ -18,9 +18,9 @@ import { import { initClient, logoutClient, processSession } from '#src/helpers/client.js'; import { clearConnectorsByTypes, - setSocialConnector, setEmailConnector, setSmsConnector, + setSocialConnector, } from '#src/helpers/connector.js'; import { expectRejects } from '#src/helpers/index.js'; import { enableAllPasswordSignInMethods } from '#src/helpers/sign-in-experience.js'; diff --git a/packages/schemas/src/types/interactions.ts b/packages/schemas/src/types/interactions.ts index 22fe88d91870..5f39ded1f4a0 100644 --- a/packages/schemas/src/types/interactions.ts +++ b/packages/schemas/src/types/interactions.ts @@ -51,12 +51,29 @@ export enum VerificationType { Password = 'Password', VerificationCode = 'VerificationCode', Social = 'Social', + EnterpriseSso = 'EnterpriseSso', TOTP = 'Totp', WebAuthn = 'WebAuthn', BackupCode = 'BackupCode', } /* API payload guard start */ +export const socialAuthorizationUrlPayloadGuard = z.object({ + state: z.string(), + redirectUri: z.string(), +}); + +export type SocialAuthorizationUrlPayload = z.infer; + +export const socialVerificationCallbackPayloadGuard = z.object({ + connectorData: jsonObjectGuard, + verificationId: z.string(), +}); + +export type SocialVerificationCallbackPayload = z.infer< + typeof socialVerificationCallbackPayloadGuard +>; + export const passwordVerificationPayloadGuard = z.object({ identifier: interactionIdentifierGuard, password: z.string().min(1), @@ -69,6 +86,7 @@ export const identificationApiPayloadGuard = z.object({ }); export type IdentificationApiPayload = z.infer; + /* API payload guard end */ // ====== Experience API payload guard and types definitions end ======