0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-01-20 21:32:31 -05:00

refactor: add comments and fix build

This commit is contained in:
Gao Sun 2022-12-16 16:43:48 +08:00
parent 1183e66f95
commit 9c04da0ffe
No known key found for this signature in database
GPG key ID: 13EBE123E4773688
10 changed files with 134 additions and 26 deletions

View file

@ -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';

View file

@ -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';

View file

@ -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',

View file

@ -13,7 +13,7 @@ const removeUndefinedKeys = (object: Record<string, unknown>) =>
export type LogPayload = Partial<LogContextPayload> & Record<string, unknown>;
type LogFunction = {
export type LogFunction = {
(data: Readonly<LogPayload>): void;
setKey: (key: LogKey) => void;
};
@ -25,6 +25,41 @@ export type LogContext = {
export type WithLogContext<ContextT extends IRouterParamContext = IRouterParamContext> = 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,

View file

@ -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);
};

View file

@ -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]);

View file

@ -47,9 +47,9 @@ export const socialIdentityPayloadGuard = z.object({
});
export type SocialIdentityPayload = z.infer<typeof socialIdentityPayloadGuard>;
/**
* Interaction Payload Guard
*/
// Interaction Payload Guard
/** Interaction flow (main flow) types. */
export enum Event {
SignIn = 'SignIn',
Register = 'Register',

View file

@ -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<LogContextPayload> = z.object({
key: z.string(),
result: z.nativeEnum(LogResult),

View file

@ -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<Flow, 'ForgotPassword'>;
/**
* 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<Identifier, 'SocialId'>}.${Method.Password}.${Action.Submit}`
| `${SignInRegisterFlow}.${Exclude<Identifier, 'SocialId'>}.${Method.Passcode}.${Action}`;
| `${SignInRegisterFlow}.${Exclude<Identifier, 'SocialId'>}.${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;

View file

@ -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',