From 9c04da0ffe36fdb9ceed8bb927558eb1bccb6e36 Mon Sep 17 00:00:00 2001 From: Gao Sun Date: Fri, 16 Dec 2022 16:43:48 +0800 Subject: [PATCH] refactor: add comments and fix build --- .../src/components/AuditLogTable/index.tsx | 2 +- .../src/pages/AuditLogDetails/index.tsx | 3 +- .../core/src/middleware/koa-audit-log.test.ts | 2 +- packages/core/src/middleware/koa-audit-log.ts | 37 ++++++++++- .../interaction/utils/passcode-validation.ts | 4 +- .../src/utils/oidc-provider-event-listener.ts | 20 +++--- packages/schemas/src/types/interactions.ts | 6 +- packages/schemas/src/types/log/index.ts | 17 ++++- packages/schemas/src/types/log/interaction.ts | 63 ++++++++++++++++++- packages/schemas/src/types/log/token.ts | 6 +- 10 files changed, 134 insertions(+), 26 deletions(-) diff --git a/packages/console/src/components/AuditLogTable/index.tsx b/packages/console/src/components/AuditLogTable/index.tsx index a8a23a212..ffe94e2a4 100644 --- a/packages/console/src/components/AuditLogTable/index.tsx +++ b/packages/console/src/components/AuditLogTable/index.tsx @@ -1,5 +1,5 @@ -import type { LogDto } from '@logto/schemas'; import { LogResult } from '@logto/schemas'; +import type { LogDto } from '@logto/schemas/lib/types/log-legacy.js'; import { conditional, conditionalString } from '@silverhand/essentials'; import classNames from 'classnames'; import { useTranslation } from 'react-i18next'; diff --git a/packages/console/src/pages/AuditLogDetails/index.tsx b/packages/console/src/pages/AuditLogDetails/index.tsx index 764c5dc77..de82e18d2 100644 --- a/packages/console/src/pages/AuditLogDetails/index.tsx +++ b/packages/console/src/pages/AuditLogDetails/index.tsx @@ -1,4 +1,5 @@ -import type { LogDto, User } from '@logto/schemas'; +import type { User } from '@logto/schemas'; +import type { LogDto } from '@logto/schemas/lib/types/log-legacy.js'; import classNames from 'classnames'; import { useTranslation } from 'react-i18next'; import { useLocation, useParams } from 'react-router-dom'; diff --git a/packages/core/src/middleware/koa-audit-log.test.ts b/packages/core/src/middleware/koa-audit-log.test.ts index a611c7225..ad8abba7f 100644 --- a/packages/core/src/middleware/koa-audit-log.test.ts +++ b/packages/core/src/middleware/koa-audit-log.test.ts @@ -26,7 +26,7 @@ mockEsm('nanoid', () => ({ const koaLog = await pickDefault(import('./koa-audit-log.js')); describe('koaLog middleware', () => { - const logKey: LogKey = 'SignIn.Username.Passcode.Submit'; + const logKey: LogKey = 'SignIn.Username.VerificationCode.Submit'; const mockPayload: LogPayload = { userId: 'foo', username: 'Bar', diff --git a/packages/core/src/middleware/koa-audit-log.ts b/packages/core/src/middleware/koa-audit-log.ts index 19de2825d..c8130d074 100644 --- a/packages/core/src/middleware/koa-audit-log.ts +++ b/packages/core/src/middleware/koa-audit-log.ts @@ -13,7 +13,7 @@ const removeUndefinedKeys = (object: Record) => export type LogPayload = Partial & Record; -type LogFunction = { +export type LogFunction = { (data: Readonly): void; setKey: (key: LogKey) => void; }; @@ -25,6 +25,41 @@ export type LogContext = { export type WithLogContext = ContextT & LogContext; +/** + * The factory to create a new audit log middleware function. + * It will inject a {@link LogFunction} property named `log` to the context to enable audit logging. + * + * --- + * + * You need to explicitly call `ctx.log.setKey()` to set a {@link LogKey} thus the log can be categorized and indexed in database: + * + * ```ts + * ctx.log.setKey('SignIn.Submit'); // Key is typed + * ``` + * + * To log data, call `ctx.log()`. It'll use object spread operators to update data (i.e. merge with one-level overwrite and shallow copy). + * + * ```ts + * ctx.log({ applicationId: 'foo' }); + * ``` + * + * The data has a initial value: + * + * ```ts + * { + * key: LogKeyUnknown, + * result: LogResult.Success, + * ip, // Extract from request + * userAgent, // Extract from request + * } + * ``` + * + * Note: Both of the functions can be called multiple times. + * + * @see {@link LogKey} for all available log keys, and {@link LogResult} for result enums. + * @see {@link LogContextPayload} for the basic type suggestion of log data. + * @returns An audit log middleware function. + */ export default function koaAuditLog< StateT, ContextT extends IRouterParamContext, diff --git a/packages/core/src/routes/interaction/utils/passcode-validation.ts b/packages/core/src/routes/interaction/utils/passcode-validation.ts index 10a164bd8..a0522cfaf 100644 --- a/packages/core/src/routes/interaction/utils/passcode-validation.ts +++ b/packages/core/src/routes/interaction/utils/passcode-validation.ts @@ -29,7 +29,7 @@ export const sendPasscodeToIdentifier = async ( const identifierType = 'email' in identifier ? interaction.Identifier.Email : interaction.Identifier.Phone; - log.setKey(`${event}.${identifierType}.Passcode.Create`); + log.setKey(`${event}.${identifierType}.VerificationCode.Create`); log(identifier); const passcode = await createPasscode(jti, passcodeType, identifier); @@ -51,7 +51,7 @@ export const verifyIdentifierByPasscode = async ( const identifierType = 'email' in identifier ? interaction.Identifier.Email : interaction.Identifier.Phone; - log.setKey(`${event}.${identifierType}.Passcode.Submit`); + log.setKey(`${event}.${identifierType}.VerificationCode.Submit`); await verifyPasscode(jti, passcodeType, passcode, identifier); }; diff --git a/packages/core/src/utils/oidc-provider-event-listener.ts b/packages/core/src/utils/oidc-provider-event-listener.ts index e5c10ae6f..28fc48cef 100644 --- a/packages/core/src/utils/oidc-provider-event-listener.ts +++ b/packages/core/src/utils/oidc-provider-event-listener.ts @@ -7,9 +7,8 @@ import { stringifyError } from './format.js'; import { isEnum } from './type.js'; /** - * OIDC provider listeners and events - * https://github.com/panva/node-oidc-provider/blob/main/docs/README.md#im-getting-a-client-authentication-failed-error-with-no-details - * https://github.com/panva/node-oidc-provider/blob/v7.x/docs/events.md + * @see {@link https://github.com/panva/node-oidc-provider/blob/v7.x/docs/README.md#im-getting-a-client-authentication-failed-error-with-no-details Getting auth error with no details?} + * @see {@link https://github.com/panva/node-oidc-provider/blob/v7.x/docs/events.md OIDC Provider events} */ export const addOidcEventListeners = (provider: Provider) => { provider.addListener('grant.success', grantListener); @@ -17,6 +16,10 @@ export const addOidcEventListeners = (provider: Provider) => { provider.addListener('grant.revoked', grantRevocationListener); }; +/** + * @see {@link https://github.com/panva/node-oidc-provider/blob/v7.x/lib/actions/token.js#L71 Success event emission} + * @see {@link https://github.com/panva/node-oidc-provider/blob/v7.x/lib/shared/error_handler.js OIDC Provider error handler} + */ export const grantListener = async ( ctx: KoaContextWithOIDC & WithLogContext & { body: GrantBody }, error?: errors.OIDCProviderError @@ -47,7 +50,7 @@ export const grantListener = async ( }); }; -// The grant.revoked event is emitted at https://github.com/panva/node-oidc-provider/blob/564b1095ee869c89381d63dfdb5875c99f870f5f/lib/helpers/revoke.js#L25 +// The grant.revoked event is emitted at https://github.com/panva/node-oidc-provider/blob/v7.x/lib/helpers/revoke.js#L25 export const grantRevocationListener = async ( ctx: KoaContextWithOIDC & WithLogContext, grantId: string @@ -65,9 +68,7 @@ export const grantRevocationListener = async ( }; /** - * See https://github.com/panva/node-oidc-provider/tree/main/lib/actions/grants - * - https://github.com/panva/node-oidc-provider/blob/564b1095ee869c89381d63dfdb5875c99f870f5f/lib/actions/grants/authorization_code.js#L209 - * - https://github.com/panva/node-oidc-provider/blob/564b1095ee869c89381d63dfdb5875c99f870f5f/lib/actions/grants/refresh_token.js#L225 + * @see {@link https://github.com/panva/node-oidc-provider/tree/v7.x/lib/actions/grants grants source code} for predefined grant implementations and types. */ type GrantBody = { access_token?: string; @@ -91,12 +92,11 @@ const getExchangeByType = (grantType: unknown): token.ExchangeByType => { }; /** - * See [OAuth 2.0 Token Revocation](https://datatracker.ietf.org/doc/html/rfc7009) for RFC reference. - * * Note the revocation may revoke related tokens as well. In oidc-provider, it will revoke the whole Grant when revoking Refresh Token. * So we don't assume the token type here. * - * See [this function](https://github.com/panva/node-oidc-provider/blob/433d131989558e24c0c74970d2d700af2199485d/lib/actions/revocation.js#L56) for code reference. + * @see {@link https://datatracker.ietf.org/doc/html/rfc7009 OAuth 2.0 Token Revocation} for RFC reference. + * @see {@link https://github.com/panva/node-oidc-provider/blob/v7.x/lib/actions/revocation.js#L56 this function} for code reference. **/ const getRevocationTokenTypes = (oidc: KoaContextWithOIDC['oidc']): token.TokenType[] => { return Object.values(token.TokenType).filter((value) => oidc.entities[value]); diff --git a/packages/schemas/src/types/interactions.ts b/packages/schemas/src/types/interactions.ts index 082f62c89..d9cf6a97d 100644 --- a/packages/schemas/src/types/interactions.ts +++ b/packages/schemas/src/types/interactions.ts @@ -47,9 +47,9 @@ export const socialIdentityPayloadGuard = z.object({ }); export type SocialIdentityPayload = z.infer; -/** - * Interaction Payload Guard - */ +// Interaction Payload Guard + +/** Interaction flow (main flow) types. */ export enum Event { SignIn = 'SignIn', Register = 'Register', diff --git a/packages/schemas/src/types/log/index.ts b/packages/schemas/src/types/log/index.ts index 0b3726d86..2e88f08e2 100644 --- a/packages/schemas/src/types/log/index.ts +++ b/packages/schemas/src/types/log/index.ts @@ -7,15 +7,29 @@ import type * as token from './token.js'; export * as interaction from './interaction.js'; export * as token from './token.js'; +/** Fallback for empty or unrecognized log keys. */ export const LogKeyUnknown = 'Unknown'; -export type LogKey = interaction.LogKey | token.LogKey | typeof LogKeyUnknown; +/** + * The union type of all available log keys. + * Note duplicate keys are allowed but should be avoided. + * + * @see {@link interaction.LogKey} for interaction log keys. + * @see {@link token.LogKey} for token log keys. + **/ +export type LogKey = typeof LogKeyUnknown | interaction.LogKey | token.LogKey; export enum LogResult { Success = 'Success', Error = 'Error', } +/** + * The basic log context type. It's more about a type hint instead of forcing the log shape. + * + * Note when setting up a log function, the type of log key in function arguments should be `LogKey`. + * Here we use `string` to make it compatible with the Zod guard. + **/ export type LogContextPayload = { key: string; result: LogResult; @@ -26,6 +40,7 @@ export type LogContextPayload = { sessionId?: string; }; +/** Type guard for {@link LogContextPayload} */ export const logContextGuard: ZodType = z.object({ key: z.string(), result: z.nativeEnum(LogResult), diff --git a/packages/schemas/src/types/log/interaction.ts b/packages/schemas/src/types/log/interaction.ts index ffaf949d4..9fce78674 100644 --- a/packages/schemas/src/types/log/interaction.ts +++ b/packages/schemas/src/types/log/interaction.ts @@ -4,6 +4,7 @@ import Flow = Event; export { Flow }; +/** Identifier to verify. */ export enum Identifier { Username = 'Username', Email = 'Email', @@ -11,29 +12,85 @@ export enum Identifier { SocialId = 'SocialId', } +/** Method to verify the identifier */ export enum Method { Password = 'Password', - Passcode = 'Passcode', + VerificationCode = 'VerificationCode', Social = 'Social', } export enum Action { + /** Submit updated info to an entity, or submit to the system. (E.g. submit an interaction, submit a verification code to get verified) */ Submit = 'Submit', + /** Create a new entity. (E.g. create an interaction, create a verification code) */ Create = 'Create', } +/** + * The forgot password flow log key type. Format: + * + * ```ts + * `ForgotPassword.${Identifier}.VerificationCode.${Action}` + * ``` + * + * Since we can use only verification code for resetting password, + * {@link Identifier.SocialId} is excluded and the method is fixed to {@link Method.VerificationCode}. + * + * {@link Action} definition: + * + * - {@link Action.Create} indicates the process of generating and sending a verification code. + * - {@link Action.Submit} indicates the process of submitting a verification code to get verified. + */ export type ForgotPasswordLogKey = `${Flow.ForgotPassword}.${Exclude< Identifier, 'SocialId' ->}.${Method.Passcode}.${Action}`; +>}.${Method.VerificationCode}.${Action}`; type SignInRegisterFlow = Exclude; +/** + * The sign-in / register flow log key type. Format: + * + * ```ts + * `${Flow}.${Identifier}.${Method}.${Action}` + * ``` + * + * Restrictions: + * + * - For social identifier and method, the value can only be `SignIn.SocialId.Social.Submit`. + * - For password method, the action can only be `Submit`. + * + * @see {@link SignInRegisterFlow}, {@link Identifier}, {@link Method}, {@link Action} for all available enums. + */ export type SignInRegisterLogKey = | `${Flow.SignIn}.${Identifier.SocialId}.${Method.Social}.${Action.Submit}` | `${SignInRegisterFlow}.${Exclude}.${Method.Password}.${Action.Submit}` - | `${SignInRegisterFlow}.${Exclude}.${Method.Passcode}.${Action}`; + | `${SignInRegisterFlow}.${Exclude}.${Method.VerificationCode}.${Action}`; export type FlowLogKey = `${Flow}.${Action}`; +/** + * The union type of all available log keys for interaction. + * + * There are two formats of a key: + * + * ```ts + * `${Flow}.${Identifier}.${Method}.${Action}` + * `${Flow}.${Action}` + * ``` + * + * The key MUST describe an {@link Action}: + * + * - {@link Action.Submit} (submit updated info to an entity, or submit to the system); + * - {@link Action.Create} (create a new entity). + * + * In an interaction, ONLY the interaction itself and verification codes can be created, i.e.: + * + * ```ts + * `${Flow}.Create` + * `${Flow}.${Identifier}.VerificationCode.Create` + * ``` + * + * There may be more restrictions, please see the specific type to learn more. + */ export type LogKey = ForgotPasswordLogKey | SignInRegisterLogKey | FlowLogKey; diff --git a/packages/schemas/src/types/log/token.ts b/packages/schemas/src/types/log/token.ts index 24b87b9ef..8be1fcd2f 100644 --- a/packages/schemas/src/types/log/token.ts +++ b/packages/schemas/src/types/log/token.ts @@ -1,11 +1,10 @@ +/** The type of a token flow. */ export enum Flow { ExchangeTokenBy = 'ExchangeTokenBy', RevokeToken = 'RevokeToken', } -/** - * Default grant type extracted from [oidc-provider](https://github.com/panva/node-oidc-provider/blob/564b1095ee869c89381d63dfdb5875c99f870f5f/lib/helpers/revoke.js#L13). - */ +/** Available grant token types extracted from [oidc-provider](https://github.com/panva/node-oidc-provider/blob/564b1095ee869c89381d63dfdb5875c99f870f5f/lib/helpers/revoke.js#L13). */ export enum TokenType { AccessToken = 'AccessToken', RefreshToken = 'RefreshToken', @@ -15,6 +14,7 @@ export enum TokenType { BackchannelAuthenticationRequest = 'BackchannelAuthenticationRequest', } +/** The credential to request a grant. */ export enum ExchangeByType { Unknown = 'Unknown', AuthorizationCode = 'AuthorizationCode',