mirror of
https://github.com/logto-io/logto.git
synced 2025-03-31 22:51:25 -05:00
refactor(core): use template type for code verification (#6687)
This commit is contained in:
parent
11d9ed4315
commit
af5c6c7f8e
8 changed files with 60 additions and 36 deletions
|
@ -40,6 +40,8 @@ const sendMessage =
|
|||
body: {
|
||||
data: {
|
||||
to,
|
||||
// TODO @wangsijie: fix this circular dependency, the connector-kit type change should be released first
|
||||
// @ts-expect-error circular dependency
|
||||
type,
|
||||
payload: {
|
||||
...payload,
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { TemplateType } from '@logto/connector-kit';
|
||||
import {
|
||||
adminConsoleApplicationId,
|
||||
adminTenantId,
|
||||
|
@ -88,7 +89,7 @@ describe('ExperienceInteraction class', () => {
|
|||
type: SignInIdentifier.Email,
|
||||
value: mockEmail,
|
||||
},
|
||||
interactionEvent: InteractionEvent.Register,
|
||||
templateType: TemplateType.Register,
|
||||
verified: true,
|
||||
});
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { TemplateType } from '@logto/connector-kit';
|
||||
import {
|
||||
InteractionEvent,
|
||||
type SignInExperience,
|
||||
|
@ -68,7 +69,7 @@ const verificationCodeVerificationRecords = Object.freeze({
|
|||
type: SignInIdentifier.Email,
|
||||
value: `foo@${emailDomain}`,
|
||||
},
|
||||
InteractionEvent.SignIn
|
||||
TemplateType.SignIn
|
||||
),
|
||||
[SignInIdentifier.Phone]: createNewCodeVerificationRecord(
|
||||
mockTenant.libraries,
|
||||
|
@ -77,7 +78,7 @@ const verificationCodeVerificationRecords = Object.freeze({
|
|||
type: SignInIdentifier.Phone,
|
||||
value: 'value',
|
||||
},
|
||||
InteractionEvent.SignIn
|
||||
TemplateType.SignIn
|
||||
),
|
||||
});
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { TemplateType, type ToZodObject } from '@logto/connector-kit';
|
||||
import {
|
||||
InteractionEvent,
|
||||
type InteractionEvent,
|
||||
SignInIdentifier,
|
||||
VerificationType,
|
||||
type User,
|
||||
|
@ -27,12 +27,9 @@ const eventToTemplateTypeMap: Record<InteractionEvent, TemplateType> = {
|
|||
};
|
||||
|
||||
/**
|
||||
* To make the typescript type checking work. A valid TemplateType is required.
|
||||
* This is a work around to map the latest interaction event type to old TemplateType.
|
||||
*
|
||||
* @remark This is a temporary solution until the connector-kit is updated to use the latest interaction event types.
|
||||
* Utility method to convert interaction event to template type.
|
||||
**/
|
||||
const getTemplateTypeByEvent = (event: InteractionEvent): TemplateType =>
|
||||
export const getTemplateTypeByEvent = (event: InteractionEvent): TemplateType =>
|
||||
eventToTemplateTypeMap[event];
|
||||
|
||||
/** This util method convert the interaction identifier to passcode library payload format */
|
||||
|
@ -64,7 +61,7 @@ export type CodeVerificationRecordData<T extends CodeVerificationType = CodeVeri
|
|||
id: string;
|
||||
type: T;
|
||||
identifier: VerificationCodeIdentifierOf<T>;
|
||||
interactionEvent: InteractionEvent;
|
||||
templateType: TemplateType;
|
||||
verified: boolean;
|
||||
};
|
||||
|
||||
|
@ -83,13 +80,9 @@ abstract class CodeVerification<T extends CodeVerificationType>
|
|||
public readonly identifier: VerificationCodeIdentifierOf<T>;
|
||||
|
||||
/**
|
||||
* The interaction event that triggered the verification.
|
||||
* This will be used to determine the template type for the verification code.
|
||||
*
|
||||
* @remark
|
||||
* `InteractionEvent.ForgotPassword` triggered verification results can not used as a verification record for other events.
|
||||
* The template type for sending the verification code, the connector will use this to get the correct template.
|
||||
*/
|
||||
public readonly interactionEvent: InteractionEvent;
|
||||
public readonly templateType: TemplateType;
|
||||
public abstract readonly type: T;
|
||||
protected verified: boolean;
|
||||
|
||||
|
@ -98,11 +91,11 @@ abstract class CodeVerification<T extends CodeVerificationType>
|
|||
private readonly queries: Queries,
|
||||
data: CodeVerificationRecordData<T>
|
||||
) {
|
||||
const { id, identifier, verified, interactionEvent } = data;
|
||||
const { id, identifier, verified, templateType } = data;
|
||||
|
||||
this.id = id;
|
||||
this.identifier = identifier;
|
||||
this.interactionEvent = interactionEvent;
|
||||
this.templateType = templateType;
|
||||
this.verified = verified;
|
||||
}
|
||||
|
||||
|
@ -123,7 +116,7 @@ abstract class CodeVerification<T extends CodeVerificationType>
|
|||
|
||||
const verificationCode = await createPasscode(
|
||||
this.id,
|
||||
getTemplateTypeByEvent(this.interactionEvent),
|
||||
this.templateType,
|
||||
getPasscodeIdentifierPayload(this.identifier)
|
||||
);
|
||||
|
||||
|
@ -148,7 +141,7 @@ abstract class CodeVerification<T extends CodeVerificationType>
|
|||
|
||||
await verifyPasscode(
|
||||
this.id,
|
||||
getTemplateTypeByEvent(this.interactionEvent),
|
||||
this.templateType,
|
||||
code,
|
||||
getPasscodeIdentifierPayload(identifier)
|
||||
);
|
||||
|
@ -178,13 +171,13 @@ abstract class CodeVerification<T extends CodeVerificationType>
|
|||
}
|
||||
|
||||
toJson(): CodeVerificationRecordData<T> {
|
||||
const { id, type, identifier, interactionEvent, verified } = this;
|
||||
const { id, type, identifier, templateType, verified } = this;
|
||||
|
||||
return {
|
||||
id,
|
||||
type,
|
||||
identifier,
|
||||
interactionEvent,
|
||||
templateType,
|
||||
verified,
|
||||
};
|
||||
}
|
||||
|
@ -194,7 +187,7 @@ abstract class CodeVerification<T extends CodeVerificationType>
|
|||
|
||||
const basicCodeVerificationRecordDataGuard = z.object({
|
||||
id: z.string(),
|
||||
interactionEvent: z.nativeEnum(InteractionEvent),
|
||||
templateType: z.nativeEnum(TemplateType),
|
||||
verified: z.boolean(),
|
||||
});
|
||||
|
||||
|
@ -273,7 +266,7 @@ export const createNewCodeVerificationRecord = (
|
|||
identifier:
|
||||
| VerificationCodeIdentifier<SignInIdentifier.Email>
|
||||
| VerificationCodeIdentifier<SignInIdentifier.Phone>,
|
||||
interactionEvent: InteractionEvent
|
||||
templateType: TemplateType
|
||||
) => {
|
||||
const { type } = identifier;
|
||||
|
||||
|
@ -283,7 +276,7 @@ export const createNewCodeVerificationRecord = (
|
|||
id: generateStandardId(),
|
||||
type: VerificationType.EmailVerificationCode,
|
||||
identifier,
|
||||
interactionEvent,
|
||||
templateType,
|
||||
verified: false,
|
||||
});
|
||||
}
|
||||
|
@ -292,7 +285,7 @@ export const createNewCodeVerificationRecord = (
|
|||
id: generateStandardId(),
|
||||
type: VerificationType.PhoneVerificationCode,
|
||||
identifier,
|
||||
interactionEvent,
|
||||
templateType,
|
||||
verified: false,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -15,7 +15,10 @@ import type TenantContext from '#src/tenants/TenantContext.js';
|
|||
import type ExperienceInteraction from '../classes/experience-interaction.js';
|
||||
import { withSentinel } from '../classes/libraries/sentinel-guard.js';
|
||||
import { codeVerificationIdentifierRecordTypeMap } from '../classes/utils.js';
|
||||
import { createNewCodeVerificationRecord } from '../classes/verifications/code-verification.js';
|
||||
import {
|
||||
createNewCodeVerificationRecord,
|
||||
getTemplateTypeByEvent,
|
||||
} from '../classes/verifications/code-verification.js';
|
||||
import { experienceRoutes } from '../const.js';
|
||||
import { type ExperienceInteractionRouterContext } from '../types.js';
|
||||
|
||||
|
@ -68,7 +71,7 @@ export default function verificationCodeRoutes<T extends ExperienceInteractionRo
|
|||
libraries,
|
||||
queries,
|
||||
identifier,
|
||||
interactionEvent
|
||||
getTemplateTypeByEvent(interactionEvent)
|
||||
);
|
||||
|
||||
await codeVerification.sendVerificationCode();
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { TemplateType } from '@logto/connector-kit';
|
||||
import {
|
||||
AdditionalIdentifier,
|
||||
InteractionEvent,
|
||||
SentinelActivityAction,
|
||||
SignInIdentifier,
|
||||
verificationCodeIdentifierGuard,
|
||||
|
@ -84,13 +84,15 @@ export default function verificationRoutes<T extends UserRouter>(
|
|||
const { identifier } = ctx.guard.body;
|
||||
|
||||
const user = await queries.users.findUserById(userId);
|
||||
const isNewIdentifier =
|
||||
(identifier.type === SignInIdentifier.Email && identifier.value === user.primaryEmail) ||
|
||||
(identifier.type === SignInIdentifier.Phone && identifier.value === user.primaryPhone);
|
||||
|
||||
const codeVerification = createNewCodeVerificationRecord(
|
||||
libraries,
|
||||
queries,
|
||||
identifier,
|
||||
// TODO(LOG-10148): Add new event
|
||||
InteractionEvent.SignIn
|
||||
isNewIdentifier ? TemplateType.BindNewIdentifier : TemplateType.UserPermissionValidation
|
||||
);
|
||||
|
||||
await codeVerification.sendVerificationCode();
|
||||
|
@ -98,11 +100,7 @@ export default function verificationRoutes<T extends UserRouter>(
|
|||
const { expiresAt } = await insertVerificationRecord(
|
||||
codeVerification,
|
||||
queries,
|
||||
// If the identifier is the primary email or phone, the verification record is associated with the user.
|
||||
(identifier.type === SignInIdentifier.Email && identifier.value === user.primaryEmail) ||
|
||||
(identifier.type === SignInIdentifier.Phone && identifier.value === user.primaryPhone)
|
||||
? userId
|
||||
: undefined
|
||||
isNewIdentifier ? userId : undefined
|
||||
);
|
||||
|
||||
ctx.body = {
|
||||
|
|
|
@ -118,6 +118,14 @@ export const mockSmsConnectorConfig = {
|
|||
usageType: 'Test',
|
||||
content: 'This is for testing purposes only. Your passcode is {{code}}.',
|
||||
},
|
||||
{
|
||||
usageType: 'UserPermissionValidation',
|
||||
content: 'This is for user permission validation purposes only. Your passcode is {{code}}.',
|
||||
},
|
||||
{
|
||||
usageType: 'BindNewIdentifier',
|
||||
content: 'This is for binding new identifier purposes only. Your passcode is {{code}}.',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
|
@ -163,6 +171,18 @@ export const mockEmailConnectorConfig = {
|
|||
subject: 'Logto Organization Invitation Template',
|
||||
content: 'This is for organization invitation purposes only. Your link is {{link}}.',
|
||||
},
|
||||
{
|
||||
usageType: 'UserPermissionValidation',
|
||||
type: 'text/plain',
|
||||
subject: 'Logto User Permission Validation Template',
|
||||
content: 'This is for user permission validation purposes only. Your passcode is {{code}}.',
|
||||
},
|
||||
{
|
||||
usageType: 'BindNewIdentifier',
|
||||
type: 'text/plain',
|
||||
subject: 'Logto Bind New Identifier Template',
|
||||
content: 'This is for binding new identifier purposes only. Your passcode is {{code}}.',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
|
|
|
@ -10,6 +10,8 @@ export enum VerificationCodeType {
|
|||
Register = 'Register',
|
||||
ForgotPassword = 'ForgotPassword',
|
||||
Generic = 'Generic',
|
||||
UserPermissionValidation = 'UserPermissionValidation',
|
||||
BindNewIdentifier = 'BindNewIdentifier',
|
||||
/** @deprecated Use `Generic` type template for sending test sms/email use case */
|
||||
Test = 'Test',
|
||||
}
|
||||
|
@ -28,6 +30,10 @@ export enum TemplateType {
|
|||
OrganizationInvitation = 'OrganizationInvitation',
|
||||
/** The template for generic usage. */
|
||||
Generic = 'Generic',
|
||||
/** The template for validating user permission for sensitive operations. */
|
||||
UserPermissionValidation = 'UserPermissionValidation',
|
||||
/** The template for binding a new identifier to an existing account. */
|
||||
BindNewIdentifier = 'BindNewIdentifier',
|
||||
}
|
||||
|
||||
export const templateTypeGuard = z.nativeEnum(TemplateType);
|
||||
|
|
Loading…
Add table
Reference in a new issue