mirror of
https://github.com/logto-io/logto.git
synced 2025-01-13 21:30:30 -05:00
refactor(core): make the interaction event mandatory (#6337)
* refactor(core): refactor backup code generate flow refactor backup code generate flow * fix(core): fix api payload fix api payload * fix(core): fix rebase issue fix rebase issue * refactor(core): make the interaction event mandatory make the interaction event mandatory * test: update integration tests update integration tests * fix(core): fix the middleware apply bug fix the koaExperienceInteraction middleware apply bug
This commit is contained in:
parent
d932e304cb
commit
dbc5512c0b
14 changed files with 105 additions and 93 deletions
|
@ -92,9 +92,12 @@ describe('ExperienceInteraction class', () => {
|
||||||
|
|
||||||
describe('new user registration', () => {
|
describe('new user registration', () => {
|
||||||
it('First admin user provisioning', async () => {
|
it('First admin user provisioning', async () => {
|
||||||
const experienceInteraction = new ExperienceInteraction(ctx, tenant);
|
const experienceInteraction = new ExperienceInteraction(
|
||||||
|
ctx,
|
||||||
|
tenant,
|
||||||
|
InteractionEvent.Register
|
||||||
|
);
|
||||||
|
|
||||||
await experienceInteraction.setInteractionEvent(InteractionEvent.Register);
|
|
||||||
experienceInteraction.setVerificationRecord(emailVerificationRecord);
|
experienceInteraction.setVerificationRecord(emailVerificationRecord);
|
||||||
await experienceInteraction.identifyUser(emailVerificationRecord.id);
|
await experienceInteraction.identifyUser(emailVerificationRecord.id);
|
||||||
|
|
||||||
|
|
|
@ -37,7 +37,7 @@ import {
|
||||||
import { VerificationRecordsMap } from './verifications/verification-records-map.js';
|
import { VerificationRecordsMap } from './verifications/verification-records-map.js';
|
||||||
|
|
||||||
type InteractionStorage = {
|
type InteractionStorage = {
|
||||||
interactionEvent?: InteractionEvent;
|
interactionEvent: InteractionEvent;
|
||||||
userId?: string;
|
userId?: string;
|
||||||
profile?: InteractionProfile;
|
profile?: InteractionProfile;
|
||||||
mfa?: MfaData;
|
mfa?: MfaData;
|
||||||
|
@ -45,7 +45,7 @@ type InteractionStorage = {
|
||||||
};
|
};
|
||||||
|
|
||||||
const interactionStorageGuard = z.object({
|
const interactionStorageGuard = z.object({
|
||||||
interactionEvent: z.nativeEnum(InteractionEvent).optional(),
|
interactionEvent: z.nativeEnum(InteractionEvent),
|
||||||
userId: z.string().optional(),
|
userId: z.string().optional(),
|
||||||
profile: interactionProfileGuard.optional(),
|
profile: interactionProfileGuard.optional(),
|
||||||
mfa: mfaDataGuard.optional(),
|
mfa: mfaDataGuard.optional(),
|
||||||
|
@ -72,18 +72,20 @@ export default class ExperienceInteraction {
|
||||||
private userId?: string;
|
private userId?: string;
|
||||||
private userCache?: User;
|
private userCache?: User;
|
||||||
/** The interaction event for the current interaction. */
|
/** The interaction event for the current interaction. */
|
||||||
#interactionEvent?: InteractionEvent;
|
#interactionEvent: InteractionEvent;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new `ExperienceInteraction` instance.
|
* Restore experience interaction from the interaction storage.
|
||||||
*
|
|
||||||
* If the `interactionDetails` is provided, the instance will be initialized with the data from the `interactionDetails` storage.
|
|
||||||
* Otherwise, a brand new instance will be created.
|
|
||||||
*/
|
*/
|
||||||
|
constructor(ctx: WithLogContext, tenant: TenantContext, interactionDetails: Interaction);
|
||||||
|
/**
|
||||||
|
* Create a new `ExperienceInteraction` instance.
|
||||||
|
*/
|
||||||
|
constructor(ctx: WithLogContext, tenant: TenantContext, interactionEvent: InteractionEvent);
|
||||||
constructor(
|
constructor(
|
||||||
private readonly ctx: WithLogContext,
|
private readonly ctx: WithLogContext,
|
||||||
private readonly tenant: TenantContext,
|
private readonly tenant: TenantContext,
|
||||||
interactionDetails?: Interaction
|
interactionData: Interaction | InteractionEvent
|
||||||
) {
|
) {
|
||||||
const { libraries, queries } = tenant;
|
const { libraries, queries } = tenant;
|
||||||
|
|
||||||
|
@ -96,13 +98,14 @@ export default class ExperienceInteraction {
|
||||||
this.getVerificationRecordByTypeAndId(type, verificationId),
|
this.getVerificationRecordByTypeAndId(type, verificationId),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!interactionDetails) {
|
if (typeof interactionData === 'string') {
|
||||||
|
this.#interactionEvent = interactionData;
|
||||||
this.profile = new Profile(libraries, queries, {}, interactionContext);
|
this.profile = new Profile(libraries, queries, {}, interactionContext);
|
||||||
this.mfa = new Mfa(libraries, queries, {}, interactionContext);
|
this.mfa = new Mfa(libraries, queries, {}, interactionContext);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = interactionStorageGuard.safeParse(interactionDetails.result ?? {});
|
const result = interactionStorageGuard.safeParse(interactionData.result ?? {});
|
||||||
|
|
||||||
// `interactionDetails.result` is not a valid experience interaction storage
|
// `interactionDetails.result` is not a valid experience interaction storage
|
||||||
assertThat(
|
assertThat(
|
||||||
|
@ -148,14 +151,12 @@ export default class ExperienceInteraction {
|
||||||
await this.signInExperienceValidator.guardInteractionEvent(interactionEvent);
|
await this.signInExperienceValidator.guardInteractionEvent(interactionEvent);
|
||||||
|
|
||||||
// `ForgotPassword` interaction event can not interchanged with other events
|
// `ForgotPassword` interaction event can not interchanged with other events
|
||||||
if (this.interactionEvent) {
|
assertThat(
|
||||||
assertThat(
|
interactionEvent === InteractionEvent.ForgotPassword
|
||||||
interactionEvent === InteractionEvent.ForgotPassword
|
? this.interactionEvent === InteractionEvent.ForgotPassword
|
||||||
? this.interactionEvent === InteractionEvent.ForgotPassword
|
: this.interactionEvent !== InteractionEvent.ForgotPassword,
|
||||||
: this.interactionEvent !== InteractionEvent.ForgotPassword,
|
new RequestError({ code: 'session.not_supported_for_forgot_password', status: 400 })
|
||||||
new RequestError({ code: 'session.not_supported_for_forgot_password', status: 400 })
|
);
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.#interactionEvent = interactionEvent;
|
this.#interactionEvent = interactionEvent;
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,7 +41,7 @@ export default function experienceApiRoutes<T extends AnonymousRouter>(
|
||||||
) {
|
) {
|
||||||
const { queries } = tenant;
|
const { queries } = tenant;
|
||||||
|
|
||||||
const router =
|
const experienceRouter =
|
||||||
// @ts-expect-error for good koa types
|
// @ts-expect-error for good koa types
|
||||||
// eslint-disable-next-line no-restricted-syntax
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
(anonymousRouter as Router<unknown, WithExperienceInteractionContext<RouterContext<T>>>).use(
|
(anonymousRouter as Router<unknown, WithExperienceInteractionContext<RouterContext<T>>>).use(
|
||||||
|
@ -49,7 +49,7 @@ export default function experienceApiRoutes<T extends AnonymousRouter>(
|
||||||
koaExperienceInteraction(tenant)
|
koaExperienceInteraction(tenant)
|
||||||
);
|
);
|
||||||
|
|
||||||
router.put(
|
experienceRouter.put(
|
||||||
experienceRoutes.prefix,
|
experienceRoutes.prefix,
|
||||||
koaGuard({
|
koaGuard({
|
||||||
body: z.object({
|
body: z.object({
|
||||||
|
@ -63,20 +63,19 @@ export default function experienceApiRoutes<T extends AnonymousRouter>(
|
||||||
|
|
||||||
createLog(`Interaction.${interactionEvent}.Update`);
|
createLog(`Interaction.${interactionEvent}.Update`);
|
||||||
|
|
||||||
const experienceInteraction = new ExperienceInteraction(ctx, tenant);
|
const experienceInteraction = new ExperienceInteraction(ctx, tenant, interactionEvent);
|
||||||
|
|
||||||
await experienceInteraction.setInteractionEvent(interactionEvent);
|
|
||||||
|
|
||||||
|
// Save new experience interaction instance.
|
||||||
|
// This will overwrite any existing interaction data in the storage.
|
||||||
await experienceInteraction.save();
|
await experienceInteraction.save();
|
||||||
|
|
||||||
ctx.experienceInteraction = experienceInteraction;
|
|
||||||
ctx.status = 204;
|
ctx.status = 204;
|
||||||
|
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
router.put(
|
experienceRouter.put(
|
||||||
`${experienceRoutes.prefix}/interaction-event`,
|
`${experienceRoutes.prefix}/interaction-event`,
|
||||||
koaGuard({
|
koaGuard({
|
||||||
body: z.object({
|
body: z.object({
|
||||||
|
@ -88,9 +87,7 @@ export default function experienceApiRoutes<T extends AnonymousRouter>(
|
||||||
const { interactionEvent } = ctx.guard.body;
|
const { interactionEvent } = ctx.guard.body;
|
||||||
const { createLog, experienceInteraction } = ctx;
|
const { createLog, experienceInteraction } = ctx;
|
||||||
|
|
||||||
const eventLog = createLog(
|
const eventLog = createLog(`Interaction.${experienceInteraction.interactionEvent}.Update`);
|
||||||
`Interaction.${experienceInteraction.interactionEvent ?? interactionEvent}.Update`
|
|
||||||
);
|
|
||||||
|
|
||||||
await experienceInteraction.setInteractionEvent(interactionEvent);
|
await experienceInteraction.setInteractionEvent(interactionEvent);
|
||||||
|
|
||||||
|
@ -106,7 +103,7 @@ export default function experienceApiRoutes<T extends AnonymousRouter>(
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
router.post(
|
experienceRouter.post(
|
||||||
experienceRoutes.identification,
|
experienceRoutes.identification,
|
||||||
koaGuard({
|
koaGuard({
|
||||||
body: identificationApiPayloadGuard,
|
body: identificationApiPayloadGuard,
|
||||||
|
@ -127,7 +124,7 @@ export default function experienceApiRoutes<T extends AnonymousRouter>(
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
router.post(
|
experienceRouter.post(
|
||||||
`${experienceRoutes.prefix}/submit`,
|
`${experienceRoutes.prefix}/submit`,
|
||||||
koaGuard({
|
koaGuard({
|
||||||
status: [200, 400, 403, 404, 422],
|
status: [200, 400, 403, 404, 422],
|
||||||
|
@ -144,14 +141,14 @@ export default function experienceApiRoutes<T extends AnonymousRouter>(
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
passwordVerificationRoutes(router, tenant);
|
passwordVerificationRoutes(experienceRouter, tenant);
|
||||||
verificationCodeRoutes(router, tenant);
|
verificationCodeRoutes(experienceRouter, tenant);
|
||||||
socialVerificationRoutes(router, tenant);
|
socialVerificationRoutes(experienceRouter, tenant);
|
||||||
enterpriseSsoVerificationRoutes(router, tenant);
|
enterpriseSsoVerificationRoutes(experienceRouter, tenant);
|
||||||
totpVerificationRoutes(router, tenant);
|
totpVerificationRoutes(experienceRouter, tenant);
|
||||||
webAuthnVerificationRoute(router, tenant);
|
webAuthnVerificationRoute(experienceRouter, tenant);
|
||||||
backupCodeVerificationRoutes(router, tenant);
|
backupCodeVerificationRoutes(experienceRouter, tenant);
|
||||||
newPasswordIdentityVerificationRoutes(router, tenant);
|
newPasswordIdentityVerificationRoutes(experienceRouter, tenant);
|
||||||
|
|
||||||
profileRoutes(router, tenant);
|
profileRoutes(experienceRouter, tenant);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { type WithLogContext } from '#src/middleware/koa-audit-log.js';
|
||||||
import type TenantContext from '#src/tenants/TenantContext.js';
|
import type TenantContext from '#src/tenants/TenantContext.js';
|
||||||
|
|
||||||
import ExperienceInteraction from '../classes/experience-interaction.js';
|
import ExperienceInteraction from '../classes/experience-interaction.js';
|
||||||
|
import { experienceRoutes } from '../const.js';
|
||||||
|
|
||||||
export type WithExperienceInteractionContext<ContextT extends WithLogContext = WithLogContext> =
|
export type WithExperienceInteractionContext<ContextT extends WithLogContext = WithLogContext> =
|
||||||
ContextT & {
|
ContextT & {
|
||||||
|
@ -25,6 +26,14 @@ export default function koaExperienceInteraction<
|
||||||
tenant: TenantContext
|
tenant: TenantContext
|
||||||
): MiddlewareType<StateT, WithExperienceInteractionContext<ContextT>, ResponseT> {
|
): MiddlewareType<StateT, WithExperienceInteractionContext<ContextT>, ResponseT> {
|
||||||
return async (ctx, next) => {
|
return async (ctx, next) => {
|
||||||
|
const { method, path } = ctx.request;
|
||||||
|
|
||||||
|
// Should not apply the koaExperienceInteraction middleware to the PUT /experience route.
|
||||||
|
// New ExperienceInteraction instance are supposed to be created in the PUT /experience route.
|
||||||
|
if (method === 'PUT' && path === `${experienceRoutes.prefix}`) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
const { provider } = tenant;
|
const { provider } = tenant;
|
||||||
const interactionDetails = await provider.interactionDetails(ctx.req, ctx.res);
|
const interactionDetails = await provider.interactionDetails(ctx.req, ctx.res);
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import type { LogtoConfig, SignInOptions } from '@logto/node';
|
import type { LogtoConfig, SignInOptions } from '@logto/node';
|
||||||
|
import { InteractionEvent } from '@logto/schemas';
|
||||||
import { assert } from '@silverhand/essentials';
|
import { assert } from '@silverhand/essentials';
|
||||||
|
|
||||||
import { ExperienceClient } from '#src/client/experience/index.js';
|
import { ExperienceClient } from '#src/client/experience/index.js';
|
||||||
|
@ -17,6 +18,7 @@ export const initClient = async (
|
||||||
};
|
};
|
||||||
|
|
||||||
export const initExperienceClient = async (
|
export const initExperienceClient = async (
|
||||||
|
interactionEvent: InteractionEvent = InteractionEvent.SignIn,
|
||||||
config?: Partial<LogtoConfig>,
|
config?: Partial<LogtoConfig>,
|
||||||
redirectUri?: string,
|
redirectUri?: string,
|
||||||
options: Omit<SignInOptions, 'redirectUri'> = {}
|
options: Omit<SignInOptions, 'redirectUri'> = {}
|
||||||
|
@ -24,6 +26,7 @@ export const initExperienceClient = async (
|
||||||
const client = new ExperienceClient(config);
|
const client = new ExperienceClient(config);
|
||||||
await client.initSession(redirectUri, options);
|
await client.initSession(redirectUri, options);
|
||||||
assert(client.interactionCookie, new Error('Session not found'));
|
assert(client.interactionCookie, new Error('Session not found'));
|
||||||
|
await client.initInteraction({ interactionEvent });
|
||||||
|
|
||||||
return client;
|
return client;
|
||||||
};
|
};
|
||||||
|
|
|
@ -34,8 +34,6 @@ export const signInWithPassword = async ({
|
||||||
}) => {
|
}) => {
|
||||||
const client = await initExperienceClient();
|
const client = await initExperienceClient();
|
||||||
|
|
||||||
await client.initInteraction({ interactionEvent: InteractionEvent.SignIn });
|
|
||||||
|
|
||||||
const { verificationId } = await client.verifyPassword({
|
const { verificationId } = await client.verifyPassword({
|
||||||
identifier,
|
identifier,
|
||||||
password,
|
password,
|
||||||
|
@ -54,8 +52,6 @@ export const signInWithPassword = async ({
|
||||||
export const signInWithVerificationCode = async (identifier: VerificationCodeIdentifier) => {
|
export const signInWithVerificationCode = async (identifier: VerificationCodeIdentifier) => {
|
||||||
const client = await initExperienceClient();
|
const client = await initExperienceClient();
|
||||||
|
|
||||||
await client.initInteraction({ interactionEvent: InteractionEvent.SignIn });
|
|
||||||
|
|
||||||
const { verificationId, code } = await successfullySendVerificationCode(client, {
|
const { verificationId, code } = await successfullySendVerificationCode(client, {
|
||||||
identifier,
|
identifier,
|
||||||
interactionEvent: InteractionEvent.SignIn,
|
interactionEvent: InteractionEvent.SignIn,
|
||||||
|
@ -89,8 +85,6 @@ export const identifyUserWithUsernamePassword = async (
|
||||||
username: string,
|
username: string,
|
||||||
password: string
|
password: string
|
||||||
) => {
|
) => {
|
||||||
await client.initInteraction({ interactionEvent: InteractionEvent.SignIn });
|
|
||||||
|
|
||||||
const { verificationId } = await client.verifyPassword({
|
const { verificationId } = await client.verifyPassword({
|
||||||
identifier: {
|
identifier: {
|
||||||
type: SignInIdentifier.Username,
|
type: SignInIdentifier.Username,
|
||||||
|
@ -108,9 +102,7 @@ export const registerNewUserWithVerificationCode = async (
|
||||||
identifier: VerificationCodeIdentifier,
|
identifier: VerificationCodeIdentifier,
|
||||||
options?: { fulfillPassword?: boolean }
|
options?: { fulfillPassword?: boolean }
|
||||||
) => {
|
) => {
|
||||||
const client = await initExperienceClient();
|
const client = await initExperienceClient(InteractionEvent.Register);
|
||||||
|
|
||||||
await client.initInteraction({ interactionEvent: InteractionEvent.Register });
|
|
||||||
|
|
||||||
const { verificationId, code } = await successfullySendVerificationCode(client, {
|
const { verificationId, code } = await successfullySendVerificationCode(client, {
|
||||||
identifier,
|
identifier,
|
||||||
|
@ -166,7 +158,6 @@ export const signInWithSocial = async (
|
||||||
const redirectUri = 'http://localhost:3000';
|
const redirectUri = 'http://localhost:3000';
|
||||||
|
|
||||||
const client = await initExperienceClient();
|
const client = await initExperienceClient();
|
||||||
await client.initInteraction({ interactionEvent: InteractionEvent.SignIn });
|
|
||||||
|
|
||||||
const { verificationId } = await successFullyCreateSocialVerification(client, connectorId, {
|
const { verificationId } = await successFullyCreateSocialVerification(client, connectorId, {
|
||||||
redirectUri,
|
redirectUri,
|
||||||
|
@ -223,7 +214,6 @@ export const signInWithEnterpriseSso = async (
|
||||||
const redirectUri = 'http://localhost:3000';
|
const redirectUri = 'http://localhost:3000';
|
||||||
|
|
||||||
const client = await initExperienceClient();
|
const client = await initExperienceClient();
|
||||||
await client.initInteraction({ interactionEvent: InteractionEvent.SignIn });
|
|
||||||
|
|
||||||
const { verificationId } = await client.getEnterpriseSsoAuthorizationUri(connectorId, {
|
const { verificationId } = await client.getEnterpriseSsoAuthorizationUri(connectorId, {
|
||||||
redirectUri,
|
redirectUri,
|
||||||
|
@ -257,8 +247,7 @@ export const signInWithEnterpriseSso = async (
|
||||||
};
|
};
|
||||||
|
|
||||||
export const registerNewUserUsernamePassword = async (username: string, password: string) => {
|
export const registerNewUserUsernamePassword = async (username: string, password: string) => {
|
||||||
const client = await initExperienceClient();
|
const client = await initExperienceClient(InteractionEvent.Register);
|
||||||
await client.initInteraction({ interactionEvent: InteractionEvent.Register });
|
|
||||||
|
|
||||||
const { verificationId } = await client.createNewPasswordIdentityVerification({
|
const { verificationId } = await client.createNewPasswordIdentityVerification({
|
||||||
identifier: {
|
identifier: {
|
||||||
|
|
|
@ -43,8 +43,7 @@ devFeatureTest.describe('Bind MFA APIs happy path', () => {
|
||||||
|
|
||||||
it('should bind TOTP on register', async () => {
|
it('should bind TOTP on register', async () => {
|
||||||
const { username, password } = generateNewUserProfile({ username: true, password: true });
|
const { username, password } = generateNewUserProfile({ username: true, password: true });
|
||||||
const client = await initExperienceClient();
|
const client = await initExperienceClient(InteractionEvent.Register);
|
||||||
await client.initInteraction({ interactionEvent: InteractionEvent.Register });
|
|
||||||
|
|
||||||
const { verificationId } = await client.createNewPasswordIdentityVerification({
|
const { verificationId } = await client.createNewPasswordIdentityVerification({
|
||||||
identifier: {
|
identifier: {
|
||||||
|
@ -131,8 +130,7 @@ devFeatureTest.describe('Bind MFA APIs happy path', () => {
|
||||||
|
|
||||||
it('should able to skip MFA binding on register', async () => {
|
it('should able to skip MFA binding on register', async () => {
|
||||||
const { username, password } = generateNewUserProfile({ username: true, password: true });
|
const { username, password } = generateNewUserProfile({ username: true, password: true });
|
||||||
const client = await initExperienceClient();
|
const client = await initExperienceClient(InteractionEvent.Register);
|
||||||
await client.initInteraction({ interactionEvent: InteractionEvent.Register });
|
|
||||||
|
|
||||||
const { verificationId } = await client.createNewPasswordIdentityVerification({
|
const { verificationId } = await client.createNewPasswordIdentityVerification({
|
||||||
identifier: {
|
identifier: {
|
||||||
|
@ -193,8 +191,7 @@ devFeatureTest.describe('Bind MFA APIs happy path', () => {
|
||||||
|
|
||||||
it('should bind TOTP and backup codes on register', async () => {
|
it('should bind TOTP and backup codes on register', async () => {
|
||||||
const { username, password } = generateNewUserProfile({ username: true, password: true });
|
const { username, password } = generateNewUserProfile({ username: true, password: true });
|
||||||
const client = await initExperienceClient();
|
const client = await initExperienceClient(InteractionEvent.Register);
|
||||||
await client.initInteraction({ interactionEvent: InteractionEvent.Register });
|
|
||||||
|
|
||||||
const { verificationId } = await client.createNewPasswordIdentityVerification({
|
const { verificationId } = await client.createNewPasswordIdentityVerification({
|
||||||
identifier: {
|
identifier: {
|
||||||
|
|
|
@ -53,8 +53,7 @@ devFeatureTest.describe('Bind MFA APIs sad path', () => {
|
||||||
it('should throw not supported error when binding TOTP on ForgotPassword interaction', async () => {
|
it('should throw not supported error when binding TOTP on ForgotPassword interaction', async () => {
|
||||||
const { username, password } = generateNewUserProfile({ username: true, password: true });
|
const { username, password } = generateNewUserProfile({ username: true, password: true });
|
||||||
await userApi.create({ username, password });
|
await userApi.create({ username, password });
|
||||||
const client = await initExperienceClient();
|
const client = await initExperienceClient(InteractionEvent.ForgotPassword);
|
||||||
await client.initInteraction({ interactionEvent: InteractionEvent.ForgotPassword });
|
|
||||||
|
|
||||||
await expectRejects(client.skipMfaBinding(), {
|
await expectRejects(client.skipMfaBinding(), {
|
||||||
code: 'session.not_supported_for_forgot_password',
|
code: 'session.not_supported_for_forgot_password',
|
||||||
|
@ -69,7 +68,6 @@ devFeatureTest.describe('Bind MFA APIs sad path', () => {
|
||||||
|
|
||||||
it('should throw identifier_not_found error, if user has not been identified', async () => {
|
it('should throw identifier_not_found error, if user has not been identified', async () => {
|
||||||
const client = await initExperienceClient();
|
const client = await initExperienceClient();
|
||||||
await client.initInteraction({ interactionEvent: InteractionEvent.SignIn });
|
|
||||||
await expectRejects(client.bindMfa(MfaFactor.TOTP, 'dummy_verification_id'), {
|
await expectRejects(client.bindMfa(MfaFactor.TOTP, 'dummy_verification_id'), {
|
||||||
code: 'session.identifier_not_found',
|
code: 'session.identifier_not_found',
|
||||||
status: 404,
|
status: 404,
|
||||||
|
|
|
@ -14,10 +14,9 @@ devFeatureTest.describe('PUT /experience API', () => {
|
||||||
|
|
||||||
it('PUT new experience API should reset all existing verification records', async () => {
|
it('PUT new experience API should reset all existing verification records', async () => {
|
||||||
const { username, password } = generateNewUserProfile({ username: true, password: true });
|
const { username, password } = generateNewUserProfile({ username: true, password: true });
|
||||||
const user = await userApi.create({ username, password });
|
await userApi.create({ username, password });
|
||||||
|
|
||||||
const client = await initExperienceClient();
|
const client = await initExperienceClient();
|
||||||
await client.initInteraction({ interactionEvent: InteractionEvent.SignIn });
|
|
||||||
const { verificationId } = await client.verifyPassword({
|
const { verificationId } = await client.verifyPassword({
|
||||||
identifier: { type: SignInIdentifier.Username, value: username },
|
identifier: { type: SignInIdentifier.Username, value: username },
|
||||||
password,
|
password,
|
||||||
|
@ -31,4 +30,36 @@ devFeatureTest.describe('PUT /experience API', () => {
|
||||||
status: 404,
|
status: 404,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should throw if trying to update interaction event from ForgotPassword to others', async () => {
|
||||||
|
const client = await initExperienceClient(InteractionEvent.ForgotPassword);
|
||||||
|
|
||||||
|
await expectRejects(
|
||||||
|
client.updateInteractionEvent({ interactionEvent: InteractionEvent.SignIn }),
|
||||||
|
{
|
||||||
|
code: 'session.not_supported_for_forgot_password',
|
||||||
|
status: 400,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw if trying to update interaction event from SignIn and Register to ForgotPassword', async () => {
|
||||||
|
const client = await initExperienceClient();
|
||||||
|
|
||||||
|
await expectRejects(
|
||||||
|
client.updateInteractionEvent({ interactionEvent: InteractionEvent.ForgotPassword }),
|
||||||
|
{
|
||||||
|
code: 'session.not_supported_for_forgot_password',
|
||||||
|
status: 400,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update interaction event from SignIn to Register', async () => {
|
||||||
|
const client = await initExperienceClient();
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
client.updateInteractionEvent({ interactionEvent: InteractionEvent.Register })
|
||||||
|
).resolves.not.toThrow();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -38,9 +38,7 @@ devFeatureTest.describe('Fulfill User Profiles', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw 400 if the interaction event is ForgotPassword', async () => {
|
it('should throw 400 if the interaction event is ForgotPassword', async () => {
|
||||||
const client = await initExperienceClient();
|
const client = await initExperienceClient(InteractionEvent.ForgotPassword);
|
||||||
|
|
||||||
await client.initInteraction({ interactionEvent: InteractionEvent.ForgotPassword });
|
|
||||||
|
|
||||||
await expectRejects(
|
await expectRejects(
|
||||||
client.updateProfile({ type: SignInIdentifier.Username, value: 'username' }),
|
client.updateProfile({ type: SignInIdentifier.Username, value: 'username' }),
|
||||||
|
@ -54,8 +52,6 @@ devFeatureTest.describe('Fulfill User Profiles', () => {
|
||||||
it('should throw 404 if the interaction is not identified', async () => {
|
it('should throw 404 if the interaction is not identified', async () => {
|
||||||
const client = await initExperienceClient();
|
const client = await initExperienceClient();
|
||||||
|
|
||||||
await client.initInteraction({ interactionEvent: InteractionEvent.SignIn });
|
|
||||||
|
|
||||||
await expectRejects(
|
await expectRejects(
|
||||||
client.updateProfile({ type: SignInIdentifier.Username, value: 'username' }),
|
client.updateProfile({ type: SignInIdentifier.Username, value: 'username' }),
|
||||||
{
|
{
|
||||||
|
|
|
@ -14,11 +14,7 @@ import { enableAllPasswordSignInMethods } from '#src/helpers/sign-in-experience.
|
||||||
import { generateNewUserProfile, UserApiTest } from '#src/helpers/user.js';
|
import { generateNewUserProfile, UserApiTest } from '#src/helpers/user.js';
|
||||||
import { devFeatureTest, generatePassword } from '#src/utils.js';
|
import { devFeatureTest, generatePassword } from '#src/utils.js';
|
||||||
|
|
||||||
const initAndIdentifyForgotPasswordInteraction = async (
|
const identifyForgotPasswordInteraction = async (client: ExperienceClient, email: string) => {
|
||||||
client: ExperienceClient,
|
|
||||||
email: string
|
|
||||||
) => {
|
|
||||||
await client.initInteraction({ interactionEvent: InteractionEvent.ForgotPassword });
|
|
||||||
const { verificationId, code } = await successfullySendVerificationCode(client, {
|
const { verificationId, code } = await successfullySendVerificationCode(client, {
|
||||||
identifier: { type: SignInIdentifier.Email, value: email },
|
identifier: { type: SignInIdentifier.Email, value: email },
|
||||||
interactionEvent: InteractionEvent.ForgotPassword,
|
interactionEvent: InteractionEvent.ForgotPassword,
|
||||||
|
@ -52,8 +48,6 @@ devFeatureTest.describe('Reset Password', () => {
|
||||||
it('should 400 if the interaction is not ForgotPassword', async () => {
|
it('should 400 if the interaction is not ForgotPassword', async () => {
|
||||||
const client = await initExperienceClient();
|
const client = await initExperienceClient();
|
||||||
|
|
||||||
await client.initInteraction({ interactionEvent: InteractionEvent.SignIn });
|
|
||||||
|
|
||||||
await expectRejects(client.resetPassword({ password: 'password' }), {
|
await expectRejects(client.resetPassword({ password: 'password' }), {
|
||||||
status: 400,
|
status: 400,
|
||||||
code: 'session.invalid_interaction_type',
|
code: 'session.invalid_interaction_type',
|
||||||
|
@ -61,9 +55,7 @@ devFeatureTest.describe('Reset Password', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw 404 if the interaction is not identified', async () => {
|
it('should throw 404 if the interaction is not identified', async () => {
|
||||||
const client = await initExperienceClient();
|
const client = await initExperienceClient(InteractionEvent.ForgotPassword);
|
||||||
|
|
||||||
await client.initInteraction({ interactionEvent: InteractionEvent.ForgotPassword });
|
|
||||||
|
|
||||||
await expectRejects(client.resetPassword({ password: 'password' }), {
|
await expectRejects(client.resetPassword({ password: 'password' }), {
|
||||||
status: 404,
|
status: 404,
|
||||||
|
@ -74,8 +66,8 @@ devFeatureTest.describe('Reset Password', () => {
|
||||||
it('should throw 422 if identify the user using VerificationType other than CodeVerification', async () => {
|
it('should throw 422 if identify the user using VerificationType other than CodeVerification', async () => {
|
||||||
const { username, password } = generateNewUserProfile({ username: true, password: true });
|
const { username, password } = generateNewUserProfile({ username: true, password: true });
|
||||||
await userApi.create({ username, password });
|
await userApi.create({ username, password });
|
||||||
const client = await initExperienceClient();
|
const client = await initExperienceClient(InteractionEvent.ForgotPassword);
|
||||||
await client.initInteraction({ interactionEvent: InteractionEvent.ForgotPassword });
|
|
||||||
const { verificationId } = await client.verifyPassword({
|
const { verificationId } = await client.verifyPassword({
|
||||||
identifier: { type: SignInIdentifier.Username, value: username },
|
identifier: { type: SignInIdentifier.Username, value: username },
|
||||||
password,
|
password,
|
||||||
|
@ -93,9 +85,9 @@ devFeatureTest.describe('Reset Password', () => {
|
||||||
password: true,
|
password: true,
|
||||||
});
|
});
|
||||||
await userApi.create({ primaryEmail, password });
|
await userApi.create({ primaryEmail, password });
|
||||||
const client = await initExperienceClient();
|
const client = await initExperienceClient(InteractionEvent.ForgotPassword);
|
||||||
|
|
||||||
await initAndIdentifyForgotPasswordInteraction(client, primaryEmail);
|
await identifyForgotPasswordInteraction(client, primaryEmail);
|
||||||
|
|
||||||
await expectRejects(client.resetPassword({ password }), {
|
await expectRejects(client.resetPassword({ password }), {
|
||||||
status: 422,
|
status: 422,
|
||||||
|
@ -123,9 +115,9 @@ devFeatureTest.describe('Reset Password', () => {
|
||||||
|
|
||||||
await userApi.create({ primaryEmail, password });
|
await userApi.create({ primaryEmail, password });
|
||||||
|
|
||||||
const client = await initExperienceClient();
|
const client = await initExperienceClient(InteractionEvent.ForgotPassword);
|
||||||
|
|
||||||
await initAndIdentifyForgotPasswordInteraction(client, primaryEmail);
|
await identifyForgotPasswordInteraction(client, primaryEmail);
|
||||||
|
|
||||||
await expectRejects(client.resetPassword({ password: primaryEmail }), {
|
await expectRejects(client.resetPassword({ password: primaryEmail }), {
|
||||||
status: 422,
|
status: 422,
|
||||||
|
@ -142,9 +134,9 @@ devFeatureTest.describe('Reset Password', () => {
|
||||||
|
|
||||||
const newPassword = generatePassword();
|
const newPassword = generatePassword();
|
||||||
|
|
||||||
const client = await initExperienceClient();
|
const client = await initExperienceClient(InteractionEvent.ForgotPassword);
|
||||||
|
|
||||||
await initAndIdentifyForgotPasswordInteraction(client, primaryEmail);
|
await identifyForgotPasswordInteraction(client, primaryEmail);
|
||||||
|
|
||||||
await client.resetPassword({ password: newPassword });
|
await client.resetPassword({ password: newPassword });
|
||||||
|
|
||||||
|
|
|
@ -59,8 +59,7 @@ devFeatureTest.describe('Register interaction with verification code happy path'
|
||||||
value: userProfile[identifiersTypeToUserProfile[identifierType]]!,
|
value: userProfile[identifiersTypeToUserProfile[identifierType]]!,
|
||||||
};
|
};
|
||||||
|
|
||||||
const client = await initExperienceClient();
|
const client = await initExperienceClient(InteractionEvent.Register);
|
||||||
await client.initInteraction({ interactionEvent: InteractionEvent.Register });
|
|
||||||
|
|
||||||
const { verificationId, code } = await successfullySendVerificationCode(client, {
|
const { verificationId, code } = await successfullySendVerificationCode(client, {
|
||||||
identifier,
|
identifier,
|
||||||
|
|
|
@ -137,8 +137,6 @@ devFeatureTest.describe('social sign-in and sign-up', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const client = await initExperienceClient();
|
const client = await initExperienceClient();
|
||||||
await client.initInteraction({ interactionEvent: InteractionEvent.SignIn });
|
|
||||||
|
|
||||||
const { verificationId } = await successFullyCreateSocialVerification(client, connectorId, {
|
const { verificationId } = await successFullyCreateSocialVerification(client, connectorId, {
|
||||||
redirectUri,
|
redirectUri,
|
||||||
state,
|
state,
|
||||||
|
|
|
@ -61,7 +61,6 @@ devFeatureTest.describe('Sign-in with verification code', () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const client = await initExperienceClient();
|
const client = await initExperienceClient();
|
||||||
await client.initInteraction({ interactionEvent: InteractionEvent.SignIn });
|
|
||||||
|
|
||||||
const { verificationId, code } = await successfullySendVerificationCode(client, {
|
const { verificationId, code } = await successfullySendVerificationCode(client, {
|
||||||
identifier,
|
identifier,
|
||||||
|
|
Loading…
Add table
Reference in a new issue