From 3ff6554c081fde39a57146391bce60d7ca6791d9 Mon Sep 17 00:00:00 2001 From: simeng-li Date: Tue, 10 Jan 2023 13:57:24 +0800 Subject: [PATCH] refactor(core,console,schemas): remove logDto (#2889) --- .../src/components/AuditLogTable/index.tsx | 6 +- .../src/pages/AuditLogDetails/index.tsx | 5 +- packages/core/src/middleware/koa-audit-log.ts | 2 +- .../interaction/actions/submit-interaction.ts | 8 +- packages/core/src/routes/interaction/index.ts | 6 +- packages/core/src/routes/log.test.ts | 6 +- .../schemas/src/foundations/jsonb-types.ts | 30 ++ packages/schemas/src/types/log-legacy.ts | 286 ------------------ packages/schemas/src/types/log/index.ts | 35 --- packages/schemas/tables/logs.sql | 2 +- 10 files changed, 48 insertions(+), 338 deletions(-) delete mode 100644 packages/schemas/src/types/log-legacy.ts diff --git a/packages/console/src/components/AuditLogTable/index.tsx b/packages/console/src/components/AuditLogTable/index.tsx index 2ed9863e8..d7c8a5882 100644 --- a/packages/console/src/components/AuditLogTable/index.tsx +++ b/packages/console/src/components/AuditLogTable/index.tsx @@ -1,5 +1,5 @@ +import type { Log } from '@logto/schemas'; import { LogResult } from '@logto/schemas'; -import type { LogDto } from '@logto/schemas/lib/types/log-legacy'; import { conditional, conditionalString } from '@silverhand/essentials'; import classNames from 'classnames'; import { useTranslation } from 'react-i18next'; @@ -45,9 +45,7 @@ const AuditLogTable = ({ userId, className }: Props) => { ] .filter(Boolean) .join('&'); - const { data, error, mutate } = useSWR<[LogDto[], number], RequestError>( - `/api/logs?${queryString}` - ); + const { data, error, mutate } = useSWR<[Log[], number], RequestError>(`/api/logs?${queryString}`); const isLoading = !data && !error; const navigate = useNavigate(); const [logs, totalCount] = data ?? []; diff --git a/packages/console/src/pages/AuditLogDetails/index.tsx b/packages/console/src/pages/AuditLogDetails/index.tsx index def4417a5..bc43021ae 100644 --- a/packages/console/src/pages/AuditLogDetails/index.tsx +++ b/packages/console/src/pages/AuditLogDetails/index.tsx @@ -1,5 +1,4 @@ -import type { User } from '@logto/schemas'; -import type { LogDto } from '@logto/schemas/lib/types/log-legacy'; +import type { User, Log } from '@logto/schemas'; import classNames from 'classnames'; import { useTranslation } from 'react-i18next'; import { useLocation, useParams } from 'react-router-dom'; @@ -31,7 +30,7 @@ const AuditLogDetails = () => { const { id, logId } = useParams(); const { pathname } = useLocation(); const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' }); - const { data, error } = useSWR(logId && `/api/logs/${logId}`); + const { data, error } = useSWR(logId && `/api/logs/${logId}`); const { data: userData } = useSWR(id && `/api/users/${id}`); const isLoading = !data && !error; diff --git a/packages/core/src/middleware/koa-audit-log.ts b/packages/core/src/middleware/koa-audit-log.ts index 2a3e768c0..bb8817aa7 100644 --- a/packages/core/src/middleware/koa-audit-log.ts +++ b/packages/core/src/middleware/koa-audit-log.ts @@ -38,7 +38,7 @@ export class LogEntry { } } -export type LogPayload = Partial & Record; +export type LogPayload = Partial; export type LogContext = { createLog: (key: LogKey) => LogEntry; diff --git a/packages/core/src/routes/interaction/actions/submit-interaction.ts b/packages/core/src/routes/interaction/actions/submit-interaction.ts index 1951b7fbb..5e41ea490 100644 --- a/packages/core/src/routes/interaction/actions/submit-interaction.ts +++ b/packages/core/src/routes/interaction/actions/submit-interaction.ts @@ -5,6 +5,7 @@ import { conditional } from '@silverhand/essentials'; import { getLogtoConnectorById } from '#src/libraries/connector.js'; import { assignInteractionResults } from '#src/libraries/session.js'; import { encryptUserPassword } from '#src/libraries/user.js'; +import type { LogEntry } from '#src/middleware/koa-audit-log.js'; import type TenantContext from '#src/tenants/TenantContext.js'; import type { WithInteractionDetailsContext } from '../middleware/koa-interaction-details.js'; @@ -129,9 +130,11 @@ const parseUserProfile = async ( export default async function submitInteraction( interaction: VerifiedInteractionResult, ctx: WithInteractionDetailsContext, - { provider, libraries, queries }: TenantContext + { provider, libraries, queries }: TenantContext, + log?: LogEntry ) { const { hasActiveUsers, findUserById, updateUserById } = queries.users; + const { users: { generateUserId, insertUser }, } = libraries; @@ -155,10 +158,13 @@ export default async function submitInteraction( await assignInteractionResults(ctx, provider, { login: { accountId: id } }); + log?.append({ userId: id }); + return; } const { accountId } = interaction; + log?.append({ userId: accountId }); if (event === InteractionEvent.SignIn) { const user = await findUserById(accountId); diff --git a/packages/core/src/routes/interaction/index.ts b/packages/core/src/routes/interaction/index.ts index 0d4b8d067..8549c9006 100644 --- a/packages/core/src/routes/interaction/index.ts +++ b/packages/core/src/routes/interaction/index.ts @@ -291,17 +291,13 @@ export default function interactionRoutes( const accountVerifiedInteraction = await verifyIdentifier(ctx, provider, interactionStorage); - if (event !== InteractionEvent.Register) { - log.append({ accountId: accountVerifiedInteraction.accountId }); - } - const verifiedInteraction = await verifyProfile(accountVerifiedInteraction); if (event !== InteractionEvent.ForgotPassword) { await validateMandatoryUserProfile(ctx, verifiedInteraction); } - await submitInteraction(verifiedInteraction, ctx, tenant); + await submitInteraction(verifiedInteraction, ctx, tenant, log); return next(); } diff --git a/packages/core/src/routes/log.test.ts b/packages/core/src/routes/log.test.ts index 257db9cf4..a5773848e 100644 --- a/packages/core/src/routes/log.test.ts +++ b/packages/core/src/routes/log.test.ts @@ -1,3 +1,5 @@ +import { LogResult } from '@logto/schemas'; +import type { Log } from '@logto/schemas'; import { pickDefault, createMockUtils } from '@logto/shared/esm'; import { createRequester } from '#src/utils/test-utils.js'; @@ -5,8 +7,8 @@ import { createRequester } from '#src/utils/test-utils.js'; const { jest } = import.meta; const { mockEsm } = createMockUtils(jest); -const mockBody = { key: 'a', payload: {}, createdAt: 123 }; -const mockLog = { id: '1', ...mockBody }; +const mockBody = { key: 'a', payload: { key: 'a', result: LogResult.Success }, createdAt: 123 }; +const mockLog: Log = { id: '1', ...mockBody }; const mockLogs = [mockLog, { id: '2', ...mockBody }]; const { countLogs, findLogs, findLogById } = mockEsm('#src/queries/log.js', () => ({ diff --git a/packages/schemas/src/foundations/jsonb-types.ts b/packages/schemas/src/foundations/jsonb-types.ts index 7d43a75cf..6dbdfc98a 100644 --- a/packages/schemas/src/foundations/jsonb-types.ts +++ b/packages/schemas/src/foundations/jsonb-types.ts @@ -188,3 +188,33 @@ export type Translation = { export const translationGuard: z.ZodType = z.lazy(() => z.record(z.string().or(translationGuard)) ); + +/** + * Logs + */ + +export enum LogResult { + Success = 'Success', + Error = 'Error', +} + +export const logContextPayloadGuard = z + .object({ + key: z.string(), + result: z.nativeEnum(LogResult), + error: z.record(z.string(), z.unknown()).or(z.string()).optional(), + ip: z.string().optional(), + userAgent: z.string().optional(), + userId: z.string().optional(), + applicationId: z.string().optional(), + sessionId: z.string().optional(), + }) + .catchall(z.unknown()); + +/** + * 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 = z.infer; diff --git a/packages/schemas/src/types/log-legacy.ts b/packages/schemas/src/types/log-legacy.ts deleted file mode 100644 index 3fe03fc44..000000000 --- a/packages/schemas/src/types/log-legacy.ts +++ /dev/null @@ -1,286 +0,0 @@ -import { z } from 'zod'; - -import type { Log } from '../db-entries/index.js'; - -/** @deprecated This will be removed soon. Use log types that can be directly imported from `@logto/schemas` instead. */ -export enum LogResult { - Success = 'Success', - Error = 'Error', -} - -/** @deprecated This will be removed soon. Use log types that can be directly imported from `@logto/schemas` instead. */ -export const logResultGuard = z.nativeEnum(LogResult); - -/** @deprecated This will be removed soon. Use log types that can be directly imported from `@logto/schemas` instead. */ -export const baseLogPayloadGuard = z.object({ - result: logResultGuard.optional(), - error: z.record(z.string(), z.unknown()).optional(), - ip: z.string().optional(), - userAgent: z.string().optional(), - applicationId: z.string().optional(), - sessionId: z.string().optional(), -}); - -/** @deprecated This will be removed soon. Use log types that can be directly imported from `@logto/schemas` instead. */ -export type BaseLogPayload = z.infer; - -const arbitraryLogPayloadGuard = z.record(z.string(), z.unknown()); - -/** @deprecated This will be removed soon. Use log types that can be directly imported from `@logto/schemas` instead. */ -export type ArbitraryLogPayload = z.infer; - -const registerUsernamePasswordLogPayloadGuard = arbitraryLogPayloadGuard.and( - z.object({ userId: z.string().optional(), username: z.string().optional() }) -); - -const registerEmailSendPasscodeLogPayloadGuard = arbitraryLogPayloadGuard.and( - z.object({ email: z.string().optional(), connectorId: z.string().optional() }) -); - -const registerEmailLogPayloadGuard = arbitraryLogPayloadGuard.and( - z.object({ - email: z.string().optional(), - code: z.string().optional(), - userId: z.string().optional(), - }) -); - -const registerSmsSendPasscodeLogPayloadGuard = arbitraryLogPayloadGuard.and( - z.object({ - phone: z.string().optional(), - connectorId: z.string().optional(), - }) -); - -const registerSmsLogPayloadGuard = arbitraryLogPayloadGuard.and( - z.object({ - phone: z.string().optional(), - code: z.string().optional(), - userId: z.string().optional(), - }) -); - -const registerSocialBindLogPayloadGuard = arbitraryLogPayloadGuard.and( - z.object({ - connectorId: z.string().optional(), - userId: z.string().optional(), - userInfo: z.unknown().optional(), - }) -); - -const registerSocialLogPayloadGuard = registerSocialBindLogPayloadGuard.and( - z.object({ - code: z.string().optional(), - state: z.string().optional(), - redirectUri: z.string().optional(), - redirectTo: z.string().optional(), - }) -); - -const signInUsernamePasswordLogPayloadGuard = arbitraryLogPayloadGuard.and( - z.object({ - userId: z.string().optional(), - username: z.string().optional(), - }) -); - -const signInEmailPasswordLogPayloadGuard = arbitraryLogPayloadGuard.and( - z.object({ - userId: z.string().optional(), - email: z.string().optional(), - }) -); - -const signInSmsPasswordLogPayloadGuard = arbitraryLogPayloadGuard.and( - z.object({ - userId: z.string().optional(), - sms: z.string().optional(), - }) -); - -const signInEmailSendPasscodeLogPayloadGuard = arbitraryLogPayloadGuard.and( - z.object({ - email: z.string().optional(), - connectorId: z.string().optional(), - }) -); - -const signInEmailLogPayloadGuard = arbitraryLogPayloadGuard.and( - z.object({ - email: z.string().optional(), - code: z.string().optional(), - userId: z.string().optional(), - }) -); - -const signInSmsSendPasscodeLogPayloadGuard = arbitraryLogPayloadGuard.and( - z.object({ - phone: z.string().optional(), - connectorId: z.string().optional(), - }) -); - -const signInSmsLogPayloadGuard = arbitraryLogPayloadGuard.and( - z.object({ - phone: z.string().optional(), - code: z.string().optional(), - userId: z.string().optional(), - }) -); - -const signInSocialBindLogPayloadGuard = arbitraryLogPayloadGuard.and( - z.object({ - connectorId: z.string().optional(), - userId: z.string().optional(), - userInfo: z.unknown().optional(), - }) -); - -const signInSocialLogPayloadGuard = signInSocialBindLogPayloadGuard.and( - z.object({ - code: z.string().optional(), - state: z.string().optional(), - redirectUri: z.string().optional(), - redirectTo: z.string().optional(), - }) -); - -const forgotPasswordSmsSendPasscodeLogPayloadGuard = arbitraryLogPayloadGuard.and( - z.object({ - phone: z.string().optional(), - connectorId: z.string().optional(), - }) -); - -const forgotPasswordSmsLogPayloadGuard = arbitraryLogPayloadGuard.and( - z.object({ - phone: z.string().optional(), - code: z.string().optional(), - userId: z.string().optional(), - }) -); - -const forgotPasswordEmailSendPasscodeLogPayloadGuard = arbitraryLogPayloadGuard.and( - z.object({ - email: z.string().optional(), - connectorId: z.string().optional(), - }) -); - -const forgotPasswordEmailLogPayloadGuard = arbitraryLogPayloadGuard.and( - z.object({ - email: z.string().optional(), - code: z.string().optional(), - userId: z.string().optional(), - }) -); - -const forgotPasswordResetLogPayloadGuard = arbitraryLogPayloadGuard.and( - z.object({ - userId: z.string().optional(), - }) -); - -const continueEmailSendPasscodeLogPayloadGuard = arbitraryLogPayloadGuard.and( - z.object({ - email: z.string().optional(), - }) -); - -const continueSmsSendPasscodeLogPayloadGuard = arbitraryLogPayloadGuard.and( - z.object({ - phone: z.string().optional(), - }) -); - -const continueEmailLogPayloadGuard = arbitraryLogPayloadGuard.and( - z.object({ - email: z.string().optional(), - }) -); - -const continueSmsLogPayloadGuard = arbitraryLogPayloadGuard.and( - z.object({ - phone: z.string().optional(), - }) -); - -export enum TokenType { - AccessToken = 'AccessToken', - RefreshToken = 'RefreshToken', - IdToken = 'IdToken', -} - -export const tokenTypeGuard = z.nativeEnum(TokenType); - -const exchangeTokenLogPayloadGuard = arbitraryLogPayloadGuard.and( - z.object({ - userId: z.string().optional(), - params: z.record(z.string(), z.unknown()).optional(), - issued: tokenTypeGuard.array().optional(), - scope: z.string().optional(), - }) -); - -const revokeTokenLogPayloadGuard = arbitraryLogPayloadGuard.and( - z.object({ - userId: z.string().optional(), - params: z.record(z.string(), z.unknown()).optional(), - tokenType: tokenTypeGuard.optional(), - grantId: z.string().optional(), - }) -); - -const logPayloadsGuard = z.object({ - RegisterUsernamePassword: registerUsernamePasswordLogPayloadGuard, - RegisterEmailSendPasscode: registerEmailSendPasscodeLogPayloadGuard, - RegisterEmail: registerEmailLogPayloadGuard, - RegisterSmsSendPasscode: registerSmsSendPasscodeLogPayloadGuard, - RegisterSms: registerSmsLogPayloadGuard, - RegisterSocialBind: registerSocialBindLogPayloadGuard, - RegisterSocial: registerSocialLogPayloadGuard, - SignInUsernamePassword: signInUsernamePasswordLogPayloadGuard, - SignInEmailPassword: signInEmailPasswordLogPayloadGuard, - SignInSmsPassword: signInSmsPasswordLogPayloadGuard, - SignInEmailSendPasscode: signInEmailSendPasscodeLogPayloadGuard, - SignInEmail: signInEmailLogPayloadGuard, - SignInSmsSendPasscode: signInSmsSendPasscodeLogPayloadGuard, - SignInSms: signInSmsLogPayloadGuard, - SignInSocialBind: signInSocialBindLogPayloadGuard, - SignInSocial: signInSocialLogPayloadGuard, - ForgotPasswordSmsSendPasscode: forgotPasswordSmsSendPasscodeLogPayloadGuard, - ForgotPasswordSms: forgotPasswordSmsLogPayloadGuard, - ForgotPasswordEmailSendPasscode: forgotPasswordEmailSendPasscodeLogPayloadGuard, - ForgotPasswordEmail: forgotPasswordEmailLogPayloadGuard, - ForgotPasswordReset: forgotPasswordResetLogPayloadGuard, - ContinueEmailSendPasscode: continueEmailSendPasscodeLogPayloadGuard, - ContinueSmsSendPasscode: continueSmsSendPasscodeLogPayloadGuard, - ContinueEmail: continueEmailLogPayloadGuard, - ContinueSms: continueSmsLogPayloadGuard, - CodeExchangeToken: exchangeTokenLogPayloadGuard, - RefreshTokenExchangeToken: exchangeTokenLogPayloadGuard, - RevokeToken: revokeTokenLogPayloadGuard, -}); - -/** @deprecated This will be removed soon. Use log types that can be directly imported from `@logto/schemas` instead. */ -export type LogPayloads = z.infer; - -/** @deprecated This will be removed soon. Use log types that can be directly imported from `@logto/schemas` instead. */ -export const logTypeGuard = logPayloadsGuard.keyof(); - -/** @deprecated This will be removed soon. Use log types that can be directly imported from `@logto/schemas` instead. */ -export type LogType = z.infer; - -/** @deprecated This will be removed soon. Use log types that can be directly imported from `@logto/schemas` instead. */ -export type LogPayload = LogPayloads[LogType]; - -/** @deprecated This will be removed soon. Use log types that can be directly imported from `@logto/schemas` instead. */ -export type LogDto = Omit & { - payload: { - userId?: string; - applicationId?: string; - result?: string; - userAgent?: string; - ip?: string; - }; -}; diff --git a/packages/schemas/src/types/log/index.ts b/packages/schemas/src/types/log/index.ts index 89b8f9c6b..88d0176d2 100644 --- a/packages/schemas/src/types/log/index.ts +++ b/packages/schemas/src/types/log/index.ts @@ -1,6 +1,3 @@ -import type { ZodType } from 'zod'; -import { z } from 'zod'; - import type * as hook from './hook.js'; import type * as interaction from './interaction.js'; import type * as token from './token.js'; @@ -20,35 +17,3 @@ export const LogKeyUnknown = 'Unknown'; * @see {@link token.LogKey} for token log keys. **/ export type LogKey = typeof LogKeyUnknown | interaction.LogKey | token.LogKey | hook.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; - error?: Record | string; - ip?: string; - userAgent?: string; - applicationId?: string; - sessionId?: string; -}; - -/** Type guard for {@link LogContextPayload} */ -export const logContextGuard: ZodType = z.object({ - key: z.string(), - result: z.nativeEnum(LogResult), - error: z.record(z.string(), z.unknown()).or(z.string()).optional(), - ip: z.string().optional(), - userAgent: z.string().optional(), - applicationId: z.string().optional(), - sessionId: z.string().optional(), -}); diff --git a/packages/schemas/tables/logs.sql b/packages/schemas/tables/logs.sql index 7c5344995..2f82b5648 100644 --- a/packages/schemas/tables/logs.sql +++ b/packages/schemas/tables/logs.sql @@ -2,7 +2,7 @@ create table logs ( id varchar(21) not null, key varchar(128) not null, - payload jsonb /* @use ArbitraryObject */ not null default '{}'::jsonb, + payload jsonb /* @use LogContextPayload */ not null default '{}'::jsonb, created_at timestamptz not null default (now()), primary key (id) );