From 4dc0930e09d953e695c35b8a573fe89808be4bf7 Mon Sep 17 00:00:00 2001 From: Darcy Ye Date: Wed, 28 Jun 2023 10:59:03 +0800 Subject: [PATCH] feat(schemas): add email service guard and system key (#4079) --- packages/core/src/tenants/SystemContext.ts | 11 ++++ packages/schemas/src/types/connector.ts | 1 + packages/schemas/src/types/system.ts | 64 ++++++++++++++++++++- packages/toolkit/connector-kit/src/types.ts | 8 ++- 4 files changed, 78 insertions(+), 6 deletions(-) diff --git a/packages/core/src/tenants/SystemContext.ts b/packages/core/src/tenants/SystemContext.ts index 421d992b6..ff7cddd9e 100644 --- a/packages/core/src/tenants/SystemContext.ts +++ b/packages/core/src/tenants/SystemContext.ts @@ -1,9 +1,12 @@ import { CloudflareKey, + EmailServiceProviderKey, type HostnameProviderData, type StorageProviderData, + type EmailServiceData, hostnameProviderDataGuard, storageProviderDataGuard, + emailServiceDataGuard, StorageProviderKey, type SystemKey, } from '@logto/schemas'; @@ -17,6 +20,7 @@ export default class SystemContext { static shared = new SystemContext(); public storageProviderConfig?: StorageProviderData; public hostnameProviderConfig?: HostnameProviderData; + public emailServiceProviderConfig?: EmailServiceData; async loadProviderConfigs(pool: CommonQueryMethods) { await Promise.all([ @@ -34,6 +38,13 @@ export default class SystemContext { hostnameProviderDataGuard ); })(), + (async () => { + this.emailServiceProviderConfig = await this.loadConfig( + pool, + EmailServiceProviderKey.EmailServiceProvider, + emailServiceDataGuard + ); + })(), ]); } diff --git a/packages/schemas/src/types/connector.ts b/packages/schemas/src/types/connector.ts index 46bdf4f2e..5e3ca2416 100644 --- a/packages/schemas/src/types/connector.ts +++ b/packages/schemas/src/types/connector.ts @@ -19,6 +19,7 @@ export const connectorResponseGuard = Connectors.guard z.object({ type: z.nativeEnum(ConnectorType), isDemo: z.boolean().optional(), + extraInfo: z.record(z.unknown()).optional(), }) ); diff --git a/packages/schemas/src/types/system.ts b/packages/schemas/src/types/system.ts index 4971b9f73..554fa82db 100644 --- a/packages/schemas/src/types/system.ts +++ b/packages/schemas/src/types/system.ts @@ -1,3 +1,4 @@ +import { verificationCodeTypeGuard } from '@logto/connector-kit'; import type { ZodType } from 'zod'; import { z } from 'zod'; @@ -64,6 +65,54 @@ export const storageProviderGuard: Readonly<{ [StorageProviderKey.StorageProvider]: storageProviderDataGuard, }); +// Email service provider +export enum EmailServiceProvider { + SendGrid = 'SendGrid', +} + +/** + * `General` is now used as a fallback scenario. + * This will be extended in the future since we will send different emails for + * different purposes (such as webhook that inform users of suspicious account activities). + */ +export enum OtherEmailTemplate { + General = 'General', +} + +export const otherEmailTemplateGuard = z.nativeEnum(OtherEmailTemplate); + +export const emailServiceDataGuard = z.discriminatedUnion('provider', [ + z.object({ + provider: z.literal(EmailServiceProvider.SendGrid), + appId: z.string(), + appSecret: z.string(), + fromEmail: z.string(), + templates: z.record( + verificationCodeTypeGuard.or(otherEmailTemplateGuard), + z.object({ + subject: z.string(), + content: z.string(), + }) + ), + }), +]); + +export type EmailServiceData = z.infer; + +export enum EmailServiceProviderKey { + EmailServiceProvider = 'EmailServiceProvider', +} + +export type EmailServiceProviderType = { + [EmailServiceProviderKey.EmailServiceProvider]: EmailServiceData; +}; + +export const emailServiceProviderGuard: Readonly<{ + [key in EmailServiceProviderKey]: ZodType; +}> = Object.freeze({ + [EmailServiceProviderKey.EmailServiceProvider]: emailServiceDataGuard, +}); + // Demo social connectors export enum DemoSocialProvider { Google = 'google', @@ -120,22 +169,30 @@ export const cloudflareGuard: Readonly<{ }); // Summary -export type SystemKey = AlterationStateKey | StorageProviderKey | DemoSocialKey | CloudflareKey; +export type SystemKey = + | AlterationStateKey + | StorageProviderKey + | DemoSocialKey + | CloudflareKey + | EmailServiceProviderKey; export type SystemType = | AlterationStateType | StorageProviderType | DemoSocialType - | CloudflareType; + | CloudflareType + | EmailServiceProviderType; export type SystemGuard = typeof alterationStateGuard & typeof storageProviderGuard & typeof demoSocialGuard & - typeof cloudflareGuard; + typeof cloudflareGuard & + typeof emailServiceProviderGuard; export const systemKeys: readonly SystemKey[] = Object.freeze([ ...Object.values(AlterationStateKey), ...Object.values(StorageProviderKey), ...Object.values(DemoSocialKey), ...Object.values(CloudflareKey), + ...Object.values(EmailServiceProviderKey), ]); export const systemGuards: SystemGuard = Object.freeze({ @@ -143,4 +200,5 @@ export const systemGuards: SystemGuard = Object.freeze({ ...storageProviderGuard, ...demoSocialGuard, ...cloudflareGuard, + ...emailServiceProviderGuard, }); diff --git a/packages/toolkit/connector-kit/src/types.ts b/packages/toolkit/connector-kit/src/types.ts index 310aeccc9..3624d86eb 100644 --- a/packages/toolkit/connector-kit/src/types.ts +++ b/packages/toolkit/connector-kit/src/types.ts @@ -229,9 +229,11 @@ export type EmailServiceBranding = z.infer; export const sendMessagePayloadGuard = z.object({ to: z.string(), type: verificationCodeTypeGuard, - payload: z.object({ - code: z.string(), - }), + payload: z + .object({ + code: z.string(), + }) + .merge(emailServiceBrandingGuard), }); export type SendMessagePayload = z.infer;