diff --git a/packages/core/package.json b/packages/core/package.json index 962dd20f9..8bd36017e 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -42,8 +42,8 @@ "zod": "^3.8.1" }, "devDependencies": { - "@logto/eslint-config": "^0.1.1", - "@logto/ts-config": "^0.1.1", + "@logto/eslint-config": "^0.1.2", + "@logto/ts-config": "^0.1.2", "@types/jest": "^27.0.1", "@types/koa": "^2.13.3", "@types/koa-logger": "^3.1.1", diff --git a/packages/core/src/database/insert-into.ts b/packages/core/src/database/insert-into.ts index 1c0b6e4e4..b2cc66092 100644 --- a/packages/core/src/database/insert-into.ts +++ b/packages/core/src/database/insert-into.ts @@ -1,9 +1,7 @@ -import assert from 'assert'; - import { SchemaLike, GeneratedSchema } from '@logto/schemas'; import { DatabasePoolType, IdentifierSqlTokenType, sql } from 'slonik'; -import RequestError from '@/errors/RequestError'; +import assert from '@/utils/assert'; import { conditionalSql, @@ -79,10 +77,7 @@ export const buildInsertInto: BuildInsertInto = ( )} `); - assert( - !returning || entry, - new RequestError({ code: 'entity.create_failed', name: rest.tableSingular }) - ); + assert(!returning || entry, 'entity.create_failed', { name: rest.tableSingular }); return entry; }; }; diff --git a/packages/core/src/database/update-where.ts b/packages/core/src/database/update-where.ts index 784f18c22..1d0938cdc 100644 --- a/packages/core/src/database/update-where.ts +++ b/packages/core/src/database/update-where.ts @@ -53,13 +53,12 @@ export const buildUpdateWhere: BuildUpdateWhere = ( assert( !returning || entry, - () => - new RequestError({ - code: where.id ? 'entity.not_exists_with_id' : 'entity.not_exists', - name: schema.tableSingular, - id: where.id, - status: 404, - }) + new RequestError({ + code: where.id ? 'entity.not_exists_with_id' : 'entity.not_exists', + name: schema.tableSingular, + id: where.id, + status: 404, + }) ); return entry; }; diff --git a/packages/core/src/middleware/koa-auth.ts b/packages/core/src/middleware/koa-auth.ts index 82cf2c77d..1b6dd6dff 100644 --- a/packages/core/src/middleware/koa-auth.ts +++ b/packages/core/src/middleware/koa-auth.ts @@ -1,4 +1,3 @@ -import assert from 'assert'; import { IncomingHttpHeaders } from 'http'; import { UserInfo, userInfoSelectFields } from '@logto/schemas'; @@ -11,6 +10,7 @@ import { developmentUserId, isProduction } from '@/env/consts'; import RequestError from '@/errors/RequestError'; import { publicKey, issuer, adminResource } from '@/oidc/consts'; import { findUserById } from '@/queries/user'; +import assert from '@/utils/assert'; export type WithAuthContext = ContextT & { @@ -45,7 +45,7 @@ const getUserIdFromRequest = async (request: Request) => { issuer, audience: adminResource, }); - assert(sub); + assert(sub, new RequestError({ code: 'auth.jwt_sub_missing', status: 401 })); return sub; }; diff --git a/packages/core/src/routes/session.ts b/packages/core/src/routes/session.ts index d20c143e6..2493436fd 100644 --- a/packages/core/src/routes/session.ts +++ b/packages/core/src/routes/session.ts @@ -1,5 +1,3 @@ -import assert from 'assert'; - import { conditional } from '@logto/essentials'; import { LogtoErrorCode } from '@logto/phrases'; import Router from 'koa-router'; @@ -9,6 +7,7 @@ import { object, string } from 'zod'; import RequestError from '@/errors/RequestError'; import koaGuard from '@/middleware/koa-guard'; import { findUserByUsername } from '@/queries/user'; +import assert from '@/utils/assert'; import { encryptPassword } from '@/utils/password'; export default function sessionRoutes(router: Router, provider: Provider) { @@ -24,7 +23,7 @@ export default function sessionRoutes(router: Router, provider: Provider) { if (name === 'login') { const { username, password } = ctx.guard.body; - assert(username && password, new RequestError('session.insufficient_info')); + assert(username && password, 'session.insufficient_info'); try { const { id, passwordEncrypted, passwordEncryptionMethod, passwordEncryptionSalt } = @@ -32,12 +31,12 @@ export default function sessionRoutes(router: Router, provider: Provider) { assert( passwordEncrypted && passwordEncryptionMethod && passwordEncryptionSalt, - new RequestError('session.invalid_sign_in_method') + 'session.invalid_sign_in_method' ); assert( encryptPassword(id, password, passwordEncryptionSalt, passwordEncryptionMethod) === passwordEncrypted, - new RequestError('session.invalid_credentials') + 'session.invalid_credentials' ); const redirectTo = await provider.interactionResult( @@ -69,6 +68,7 @@ export default function sessionRoutes(router: Router, provider: Provider) { router.post('/session/consent', async (ctx, next) => { const interaction = await provider.interactionDetails(ctx.req, ctx.res); const { session, grantId, params, prompt } = interaction; + assert(session, 'session.not_found'); const { scope } = object({ scope: string().optional(), @@ -77,16 +77,11 @@ export default function sessionRoutes(router: Router, provider: Provider) { // LOG-49: Connect and check scope with resource indicators const scopes = scope?.split(' ') ?? []; const invalidScopes = scopes.filter((scope) => !['openid', 'offline_access'].includes(scope)); - assert( - invalidScopes.length === 0, - new RequestError({ - code: 'oidc.invalid_scope', - count: invalidScopes.length, - scopes: invalidScopes.join(', '), - }) - ); + assert(invalidScopes.length === 0, 'oidc.invalid_scope', { + count: invalidScopes.length, + scopes: invalidScopes.join(', '), + }); - assert(session, 'Session not found'); const { accountId } = session; const grant = conditional(grantId && (await provider.Grant.find(grantId))) ?? diff --git a/packages/core/src/utils/assert.ts b/packages/core/src/utils/assert.ts index 9d50314ae..3b975be5c 100644 --- a/packages/core/src/utils/assert.ts +++ b/packages/core/src/utils/assert.ts @@ -1,15 +1,24 @@ // https://github.com/facebook/jest/issues/7547 +import { LogtoErrorCode } from '@logto/phrases'; + +import RequestError from '@/errors/RequestError'; + export type AssertFunction = ( value: unknown, - buildError: () => E + error: E | LogtoErrorCode, + interpolation?: Record ) => asserts value; -const assert: AssertFunction = (value, buildError): asserts value => { +const assert: AssertFunction = (value, error, interpolation): asserts value => { if (!value) { - // https://github.com/typescript-eslint/typescript-eslint/issues/3814 - // eslint-disable-next-line @typescript-eslint/no-throw-literal - throw buildError(); + if (error instanceof Error) { + // https://github.com/typescript-eslint/typescript-eslint/issues/3814 + // eslint-disable-next-line @typescript-eslint/no-throw-literal + throw error; + } + + throw new RequestError({ code: error, ...interpolation }); } }; diff --git a/packages/core/src/utils/env.ts b/packages/core/src/utils/env.ts index 901c183be..084844f70 100644 --- a/packages/core/src/utils/env.ts +++ b/packages/core/src/utils/env.ts @@ -1,8 +1,8 @@ -import assert from 'assert'; +import assert from '@/utils/assert'; export const getEnv = (key: string, fallback = ''): string => process.env[key] ?? fallback; export const assertEnv = (key: string): string => { const value = process.env[key]; - assert(value, `env variable ${key} not found`); + assert(value, new Error(`env variable ${key} not found`)); return value; }; diff --git a/packages/core/src/utils/password.ts b/packages/core/src/utils/password.ts index f7402e818..3a0c07f1a 100644 --- a/packages/core/src/utils/password.ts +++ b/packages/core/src/utils/password.ts @@ -1,9 +1,10 @@ -import assert from 'assert'; import { createHash } from 'crypto'; import { PasswordEncryptionMethod } from '@logto/schemas'; import { number, string } from 'zod'; +import assert from '@/utils/assert'; + import { assertEnv } from './env'; const peppers = string() @@ -21,13 +22,14 @@ export const encryptPassword = ( ): string => { assert( method === PasswordEncryptionMethod.SaltAndPepper, - 'Unsupported password encryption method' + 'password.unsupported_encryption_method', + { method } ); const sum = [...id].reduce((accumulator, current) => accumulator + current.charCodeAt(0), 0); const pepper = peppers[sum % peppers.length]; - assert(pepper, 'Password pepper not found'); + assert(pepper, 'password.pepper_not_found'); let result = password; diff --git a/packages/phrases/package.json b/packages/phrases/package.json index 8f53513f5..9a3029d14 100644 --- a/packages/phrases/package.json +++ b/packages/phrases/package.json @@ -25,8 +25,8 @@ "url": "https://github.com/logto-io/logto/issues" }, "devDependencies": { - "@logto/eslint-config": "^0.1.1", - "@logto/ts-config": "^0.1.1", + "@logto/eslint-config": "^0.1.2", + "@logto/ts-config": "^0.1.2", "eslint": "^7.32.0", "lint-staged": "^11.1.1", "prettier": "^2.3.2", diff --git a/packages/phrases/src/locales/en.ts b/packages/phrases/src/locales/en.ts index 998366beb..e0863e438 100644 --- a/packages/phrases/src/locales/en.ts +++ b/packages/phrases/src/locales/en.ts @@ -19,6 +19,7 @@ const errors = { authorization_header_missing: 'Authorization header is missing.', authorization_type_not_supported: 'Authorization type is not supported.', unauthorized: 'Unauthorized. Please check credentils and its scope.', + jwt_sub_missing: 'Missing `sub` in JWT.', }, guard: { invalid_input: 'The request input is invalid.', @@ -31,7 +32,12 @@ const errors = { user: { username_exists: 'The username already exists.', }, + password: { + unsupported_encryption_method: 'The encryption method {{name}} is not supported.', + pepper_not_found: 'Password pepper not found. Please check your core envs.', + }, session: { + not_found: 'Session not found. Please go back and sign in again.', invalid_credentials: 'Invalid credentials. Please check your input.', invalid_sign_in_method: 'Current sign-in method is not available.', insufficient_info: 'Insufficent sign-in info.', diff --git a/packages/phrases/src/locales/zh-cn.ts b/packages/phrases/src/locales/zh-cn.ts index 92c7c379f..51743ae9b 100644 --- a/packages/phrases/src/locales/zh-cn.ts +++ b/packages/phrases/src/locales/zh-cn.ts @@ -21,6 +21,7 @@ const errors = { authorization_header_missing: 'Authorization 请求 header 遗漏。', authorization_type_not_supported: '不支持的 authorization 类型。', unauthorized: '未授权。请检查相关 credentials 和 scope。', + jwt_sub_missing: 'JWT 中找不到 `sub`。', }, guard: { invalid_input: '请求内容有误。', @@ -33,7 +34,12 @@ const errors = { user: { username_exists: '用户名已存在。', }, + password: { + unsupported_encryption_method: '不支持的加密方法 {{name}}。', + pepper_not_found: '密码 pepper 未找到。请检查 core 的环境变量。', + }, session: { + not_found: 'Session not found. Please go back and sign in again.', invalid_credentials: '用户名或密码错误,请检查您的输入。', invalid_sign_in_method: '当前登录方式不可用。', insufficient_info: '登录信息缺失,请检查您的输入。', diff --git a/packages/schemas/package.json b/packages/schemas/package.json index 97bf4c2d9..052bba7f8 100644 --- a/packages/schemas/package.json +++ b/packages/schemas/package.json @@ -21,9 +21,9 @@ "node": ">=14.15.0" }, "devDependencies": { - "@logto/eslint-config": "^0.1.1", + "@logto/eslint-config": "^0.1.2", "@logto/essentials": "^1.1.0-rc.2", - "@logto/ts-config": "^0.1.1", + "@logto/ts-config": "^0.1.2", "@types/lodash.uniq": "^4.5.6", "@types/node": "14", "@types/pluralize": "^0.0.29", diff --git a/packages/schemas/src/gen/index.ts b/packages/schemas/src/gen/index.ts index d2d8267c3..4e3bf48f3 100644 --- a/packages/schemas/src/gen/index.ts +++ b/packages/schemas/src/gen/index.ts @@ -1,3 +1,5 @@ +// Consider add the better assert into `essentials` package +// eslint-disable-next-line no-restricted-imports import assert from 'assert'; import fs from 'fs/promises'; import path from 'path'; diff --git a/packages/ui/package.json b/packages/ui/package.json index 6929d7f46..855951564 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -31,10 +31,10 @@ "devDependencies": { "@babel/core": "^7.14.6", "@jest/types": "^27.0.6", - "@logto/eslint-config": "^0.1.1", - "@logto/eslint-config-react": "^0.1.1", - "@logto/ts-config": "^0.1.1", - "@logto/ts-config-react": "^0.1.1", + "@logto/eslint-config": "^0.1.2", + "@logto/eslint-config-react": "^0.1.2", + "@logto/ts-config": "^0.1.2", + "@logto/ts-config-react": "^0.1.2", "@testing-library/react": "^12.0.0", "@types/jest": "^26.0.24", "@types/react": "^17.0.14", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 83c63113b..330ef139f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -20,11 +20,11 @@ importers: packages/core: specifiers: - '@logto/eslint-config': ^0.1.1 + '@logto/eslint-config': ^0.1.2 '@logto/essentials': ^1.1.0-rc.2 '@logto/phrases': ^0.1.0 '@logto/schemas': ^0.1.0 - '@logto/ts-config': ^0.1.1 + '@logto/ts-config': ^0.1.2 '@types/jest': ^27.0.1 '@types/koa': ^2.13.3 '@types/koa-logger': ^3.1.1 @@ -90,8 +90,8 @@ importers: slonik-interceptor-preset: 1.2.10 zod: 3.8.1 devDependencies: - '@logto/eslint-config': 0.1.1_aff669e8eb0d21fc4e2068e6112ef4d0 - '@logto/ts-config': 0.1.1_typescript@4.3.5 + '@logto/eslint-config': 0.1.2_aff669e8eb0d21fc4e2068e6112ef4d0 + '@logto/ts-config': 0.1.2_typescript@4.3.5 '@types/jest': 27.0.1 '@types/koa': 2.13.4 '@types/koa-logger': 3.1.1 @@ -113,15 +113,15 @@ importers: packages/phrases: specifiers: - '@logto/eslint-config': ^0.1.1 - '@logto/ts-config': ^0.1.1 + '@logto/eslint-config': ^0.1.2 + '@logto/ts-config': ^0.1.2 eslint: ^7.32.0 lint-staged: ^11.1.1 prettier: ^2.3.2 typescript: ^4.3.5 devDependencies: - '@logto/eslint-config': 0.1.1_aff669e8eb0d21fc4e2068e6112ef4d0 - '@logto/ts-config': 0.1.1_typescript@4.3.5 + '@logto/eslint-config': 0.1.2_aff669e8eb0d21fc4e2068e6112ef4d0 + '@logto/ts-config': 0.1.2_typescript@4.3.5 eslint: 7.32.0 lint-staged: 11.1.1 prettier: 2.3.2 @@ -129,10 +129,10 @@ importers: packages/schemas: specifiers: - '@logto/eslint-config': ^0.1.1 + '@logto/eslint-config': ^0.1.2 '@logto/essentials': ^1.1.0-rc.2 '@logto/phrases': ^0.1.0 - '@logto/ts-config': ^0.1.1 + '@logto/ts-config': ^0.1.2 '@types/lodash.uniq': ^4.5.6 '@types/node': '14' '@types/pluralize': ^0.0.29 @@ -148,9 +148,9 @@ importers: dependencies: '@logto/phrases': link:../phrases devDependencies: - '@logto/eslint-config': 0.1.1_aff669e8eb0d21fc4e2068e6112ef4d0 + '@logto/eslint-config': 0.1.2_aff669e8eb0d21fc4e2068e6112ef4d0 '@logto/essentials': 1.1.0-rc.2 - '@logto/ts-config': 0.1.1_typescript@4.3.5 + '@logto/ts-config': 0.1.2_typescript@4.3.5 '@types/lodash.uniq': 4.5.6 '@types/node': 14.17.6 '@types/pluralize': 0.0.29 @@ -168,12 +168,12 @@ importers: specifiers: '@babel/core': ^7.14.6 '@jest/types': ^27.0.6 - '@logto/eslint-config': ^0.1.1 - '@logto/eslint-config-react': ^0.1.1 + '@logto/eslint-config': ^0.1.2 + '@logto/eslint-config-react': ^0.1.2 '@logto/phrases': ^0.1.0 '@logto/schemas': ^0.1.0 - '@logto/ts-config': ^0.1.1 - '@logto/ts-config-react': ^0.1.1 + '@logto/ts-config': ^0.1.2 + '@logto/ts-config-react': ^0.1.2 '@testing-library/react': ^12.0.0 '@types/jest': ^26.0.24 '@types/react': ^17.0.14 @@ -218,10 +218,10 @@ importers: devDependencies: '@babel/core': 7.14.8 '@jest/types': 27.0.6 - '@logto/eslint-config': 0.1.1_aff669e8eb0d21fc4e2068e6112ef4d0 - '@logto/eslint-config-react': 0.1.1_8e322dd0e62beacbfb7b944fe3d15c43 - '@logto/ts-config': 0.1.1_typescript@4.3.5 - '@logto/ts-config-react': 0.1.1_typescript@4.3.5 + '@logto/eslint-config': 0.1.2_aff669e8eb0d21fc4e2068e6112ef4d0 + '@logto/eslint-config-react': 0.1.2_8e322dd0e62beacbfb7b944fe3d15c43 + '@logto/ts-config': 0.1.2_typescript@4.3.5 + '@logto/ts-config-react': 0.1.2_typescript@4.3.5 '@testing-library/react': 12.0.0_react-dom@17.0.2+react@17.0.2 '@types/jest': 26.0.24 '@types/react': 17.0.15 @@ -2792,12 +2792,12 @@ packages: write-file-atomic: 3.0.3 dev: true - /@logto/eslint-config-react/0.1.1_8e322dd0e62beacbfb7b944fe3d15c43: - resolution: {integrity: sha512-ay/zXF5HDUIF0wpQCKwfqtLnPQzI2iBtpoBb4X+vNwTtcdCvyZixAPEp5tj07sCiov0eluTDMdUTsLDSP6zQcw==} + /@logto/eslint-config-react/0.1.2_8e322dd0e62beacbfb7b944fe3d15c43: + resolution: {integrity: sha512-8bBRmfv6zg+W9vFLhBidX5qTFxtz2eKaCNCz5+Ax3KGPKQ+aBNOjULZ8ZO5Xt28+EvQktEMUBEm6P5Pq5vlboQ==} peerDependencies: stylelint: ^13.13.1 dependencies: - '@logto/eslint-config': 0.1.1_aff669e8eb0d21fc4e2068e6112ef4d0 + '@logto/eslint-config': 0.1.2_aff669e8eb0d21fc4e2068e6112ef4d0 eslint-config-xo-react: 0.25.0_34cd3168eeae4de23db8343b5dfd9fdd eslint-plugin-react: 7.25.0_eslint@7.32.0 eslint-plugin-react-hooks: 4.2.0_eslint@7.32.0 @@ -2810,8 +2810,8 @@ packages: - typescript dev: true - /@logto/eslint-config/0.1.1_aff669e8eb0d21fc4e2068e6112ef4d0: - resolution: {integrity: sha512-d7KQTsvSOWxk1otaN8Rzdwf2hBU43pB2MIPBFneuj60k5s4eI8AUTJK4LTq9pssqcdchZC3ne9lZ5gSg1iRYwQ==} + /@logto/eslint-config/0.1.2_aff669e8eb0d21fc4e2068e6112ef4d0: + resolution: {integrity: sha512-RnyBvzZjm6osGkKPMwZ30mZCd0jsOKCg43dMeoQsrnZIQAbdNKn0TiKx5VHQITOyq2gwVnugWIKtKU7vK5Vt7g==} engines: {node: '>=14.15.0'} peerDependencies: eslint: ^7.32.0 @@ -2846,18 +2846,18 @@ packages: lodash.orderby: 4.6.0 lodash.pick: 4.4.0 - /@logto/ts-config-react/0.1.1_typescript@4.3.5: - resolution: {integrity: sha512-IL1asAq18+VZ99rJDVW+nUS9Wv/eCpFrIStlLTGH4J7+0WaOdLYgQdyAuDq/GDvv5oDXFPb+3HmjgrwVeTu74g==} + /@logto/ts-config-react/0.1.2_typescript@4.3.5: + resolution: {integrity: sha512-Ivwce+4w5mOi1FdfUdgVElAd19iieD6r9LKUqgePipuCc9jJpVxeLc9xHVxxhaAohJdSd0rLoRICJhCzqZBwlA==} engines: {node: '>=14.15.0'} peerDependencies: typescript: ^4.3.5 dependencies: - '@logto/ts-config': 0.1.1_typescript@4.3.5 + '@logto/ts-config': 0.1.2_typescript@4.3.5 typescript: 4.3.5 dev: true - /@logto/ts-config/0.1.1_typescript@4.3.5: - resolution: {integrity: sha512-AkI5tWU1YGNKyjYjR2DeduNBWVAXqO8vyQIUBl1yn9z7P2o14l/AJoQLN4xYORY5HH6U0hzJ80hHPhdl9IUSwQ==} + /@logto/ts-config/0.1.2_typescript@4.3.5: + resolution: {integrity: sha512-aJZAhaQIVKxG1Ixexj/ksUTNhJVcE10EZHczF47bwsNGlRYR4FBHATca3S5bgEVnfRIJnR3uPwpbKnJu7TvM+w==} engines: {node: '>=14.15.0'} peerDependencies: typescript: ^4.3.5