diff --git a/packages/schemas/alterations/next-1725971571-add-verification-record.ts b/packages/schemas/alterations/next-1725971571-add-verification-record.ts new file mode 100644 index 00000000000..9af89651e0c --- /dev/null +++ b/packages/schemas/alterations/next-1725971571-add-verification-record.ts @@ -0,0 +1,37 @@ +import { sql } from '@silverhand/slonik'; + +import type { AlterationScript } from '../lib/types/alteration.js'; + +import { applyTableRls, dropTableRls } from './utils/1704934999-tables.js'; + +const alteration: AlterationScript = { + up: async (pool) => { + await pool.query(sql` + create table verification_records ( + tenant_id varchar(21) not null + references tenants (id) on update cascade on delete cascade, + id varchar(21) not null, + user_id varchar(21) + references users (id) on update cascade on delete cascade, + created_at timestamptz not null default(now()), + type varchar(255) /* @use VerificationType */ not null, + verified_at timestamptz, + expires_at timestamptz not null, + data jsonb /* @use VerificationRecordData */ not null default '{}'::jsonb, + primary key (id) + ); + + create index verification_records__id + on verification_records (tenant_id, id); + `); + await applyTableRls(pool, 'verification_records'); + }, + down: async (pool) => { + await dropTableRls(pool, 'verification_records'); + await pool.query(sql` + drop table verification_records; + `); + }, +}; + +export default alteration; diff --git a/packages/schemas/src/foundations/jsonb-types/index.ts b/packages/schemas/src/foundations/jsonb-types/index.ts index 601855d1f37..aad607656af 100644 --- a/packages/schemas/src/foundations/jsonb-types/index.ts +++ b/packages/schemas/src/foundations/jsonb-types/index.ts @@ -8,6 +8,7 @@ export * from './sentinel.js'; export * from './users.js'; export * from './sso-connector.js'; export * from './applications.js'; +export * from './verification-records.js'; export { configurableConnectorMetadataGuard, diff --git a/packages/schemas/src/foundations/jsonb-types/verification-records.ts b/packages/schemas/src/foundations/jsonb-types/verification-records.ts new file mode 100644 index 00000000000..93ede6fa7b0 --- /dev/null +++ b/packages/schemas/src/foundations/jsonb-types/verification-records.ts @@ -0,0 +1,46 @@ +import { socialUserInfoGuard } from '@logto/connector-kit'; +import { z } from 'zod'; + +export const defaultVerificationRecordDataGuard = z.object({}); + +export const emailVerificationRecordDataGuard = z.object({ + email: z.string(), +}); + +export type EmailVerificationRecordData = z.infer; + +export const phoneVerificationRecordDataGuard = z.object({ + phone: z.string(), +}); + +export type PhoneVerificationRecordData = z.infer; + +export const socialVerificationRecordDataGuard = z.object({ + connectorId: z.string(), + socialUserInfo: socialUserInfoGuard.optional(), +}); + +export type SocialVerificationRecordData = z.infer; + +export const verificationRecordDataGuard = z.union([ + defaultVerificationRecordDataGuard, + emailVerificationRecordDataGuard, + phoneVerificationRecordDataGuard, + socialVerificationRecordDataGuard, +]); + +export type VerificationRecordData = z.infer; + +export enum VerificationType { + Password = 'Password', + EmailVerificationCode = 'EmailVerificationCode', + PhoneVerificationCode = 'PhoneVerificationCode', + Social = 'Social', + EnterpriseSso = 'EnterpriseSso', + TOTP = 'Totp', + WebAuthn = 'WebAuthn', + BackupCode = 'BackupCode', + NewPasswordIdentity = 'NewPasswordIdentity', +} + +export const verificationTypeGuard = z.nativeEnum(VerificationType); diff --git a/packages/schemas/src/types/interactions.ts b/packages/schemas/src/types/interactions.ts index ed008729eb5..64b92ed27eb 100644 --- a/packages/schemas/src/types/interactions.ts +++ b/packages/schemas/src/types/interactions.ts @@ -55,19 +55,6 @@ export const verificationCodeIdentifierGuard = z.object({ value: z.string(), }) satisfies ToZodObject; -/** Logto supported interaction verification types. */ -export enum VerificationType { - Password = 'Password', - EmailVerificationCode = 'EmailVerificationCode', - PhoneVerificationCode = 'PhoneVerificationCode', - Social = 'Social', - EnterpriseSso = 'EnterpriseSso', - TOTP = 'Totp', - WebAuthn = 'WebAuthn', - BackupCode = 'BackupCode', - NewPasswordIdentity = 'NewPasswordIdentity', -} - // REMARK: API payload guard /** Payload type for `POST /api/experience/verification/{social|sso}/:connectorId/authorization-uri`. */ diff --git a/packages/schemas/src/types/log/interaction.ts b/packages/schemas/src/types/log/interaction.ts index bcec59bb0e7..f70a13f4e9b 100644 --- a/packages/schemas/src/types/log/interaction.ts +++ b/packages/schemas/src/types/log/interaction.ts @@ -1,5 +1,5 @@ -import { type MfaFactor } from '../../foundations/index.js'; -import type { InteractionEvent, VerificationType } from '../interactions.js'; +import { type VerificationType, type MfaFactor } from '../../foundations/index.js'; +import type { InteractionEvent } from '../interactions.js'; export type Prefix = 'Interaction'; diff --git a/packages/schemas/tables/verification_records.sql b/packages/schemas/tables/verification_records.sql new file mode 100644 index 00000000000..1f524b69698 --- /dev/null +++ b/packages/schemas/tables/verification_records.sql @@ -0,0 +1,16 @@ +create table verification_records ( + tenant_id varchar(21) not null + references tenants (id) on update cascade on delete cascade, + id varchar(21) not null, + user_id varchar(21) + references users (id) on update cascade on delete cascade, + created_at timestamptz not null default(now()), + type varchar(255) /* @use VerificationType */ not null, + verified_at timestamptz, + expires_at timestamptz not null, + data jsonb /* @use VerificationRecordData */ not null default '{}'::jsonb, + primary key (id) +); + +create index verification_records__id + on verification_records (tenant_id, id);