From a5c9bf61d736a5fed27d43d132d8045b539773a4 Mon Sep 17 00:00:00 2001 From: simeng-li Date: Tue, 8 Feb 2022 15:31:16 +0800 Subject: [PATCH] refactor(core): add OIDCRequestError (#214) * refactor(core): add OIDCRequestError inplement OIDCRequestError to normalize OIDCProviderError * fix(coer): cr fix code review update --- .../core/src/errors/OIDCRequestError/index.ts | 65 +++++++++++++++++++ .../core/src/middleware/koa-error-handler.ts | 16 ++--- packages/phrases/src/locales/en.ts | 15 ++++- packages/phrases/src/locales/zh-cn.ts | 12 ++++ 4 files changed, 98 insertions(+), 10 deletions(-) create mode 100644 packages/core/src/errors/OIDCRequestError/index.ts diff --git a/packages/core/src/errors/OIDCRequestError/index.ts b/packages/core/src/errors/OIDCRequestError/index.ts new file mode 100644 index 000000000..2558e70b7 --- /dev/null +++ b/packages/core/src/errors/OIDCRequestError/index.ts @@ -0,0 +1,65 @@ +import { LogtoErrorCode, LogtoErrorI18nKey } from '@logto/phrases'; +import { RequestErrorBody } from '@logto/schemas'; +import decamelize from 'decamelize'; +import i18next from 'i18next'; +import pick from 'lodash.pick'; +import { errors } from 'oidc-provider'; + +export default class OIDCRequestError extends Error { + code: LogtoErrorCode; + status: number; + expose: boolean; + data: unknown; + + constructor(error: errors.OIDCProviderError) { + const { + status = 400, + message, + error_description, + error_detail, + name, + expose = true, + constructor, + ...interpolation + } = error; + + super(message); + + switch (constructor) { + case errors.InvalidScope: + case errors.InvalidTarget: + case errors.InvalidToken: + case errors.InvalidClientMetadata: + case errors.InvalidGrant: + this.code = `oidc.${decamelize(name)}` as LogtoErrorCode; + this.message = i18next.t(`errors:${this.code}`, interpolation); + break; + case errors.SessionNotFound: + this.code = 'session.not_found'; + this.message = i18next.t(`errors:${this.code}`, interpolation); + break; + case errors.InsufficientScope: + this.code = 'oidc.insufficient_scope'; + this.message = i18next.t(`errors:${this.code}`, { + scopes: error_detail, + }); + break; + default: + this.code = 'oidc.provider_error'; + this.message = i18next.t(`errors:${this.code}`, { + message: this.message, + }); + break; + } + + this.status = status; + this.expose = expose; + + // Original OIDCProvider Error description and details are provided in the data field + this.data = { error_description, error_detail }; + } + + get body(): RequestErrorBody { + return pick(this, 'message', 'code', 'data'); + } +} diff --git a/packages/core/src/middleware/koa-error-handler.ts b/packages/core/src/middleware/koa-error-handler.ts index 95dce578b..48e740aa0 100644 --- a/packages/core/src/middleware/koa-error-handler.ts +++ b/packages/core/src/middleware/koa-error-handler.ts @@ -1,10 +1,9 @@ -import { LogtoErrorCode } from '@logto/phrases'; import { RequestErrorBody } from '@logto/schemas'; -import decamelize from 'decamelize'; import { Middleware } from 'koa'; import { errors } from 'oidc-provider'; import { NotFoundError } from 'slonik'; +import OIDCRequestError from '@/errors/OIDCRequestError'; import RequestError from '@/errors/RequestError'; export default function koaErrorHandler(): Middleware< @@ -24,17 +23,14 @@ export default function koaErrorHandler(): Middleware< } if (error instanceof errors.OIDCProviderError) { - ctx.status = error.status; - ctx.body = { - message: error.error_description ?? error.message, - // Assert error type of OIDCProviderError, code key should all covered in @logto/phrases - code: `oidc.${decamelize(error.name)}` as LogtoErrorCode, - data: error.error_detail, - }; + const oidcError = new OIDCRequestError(error); + ctx.status = oidcError.status; + ctx.body = oidcError.body; return; } + // TODO: Slonik Error if (error instanceof NotFoundError) { const error = new RequestError({ code: 'entity.not_found', status: 404 }); ctx.status = error.status; @@ -43,6 +39,8 @@ export default function koaErrorHandler(): Middleware< return; } + // TODO: Zod Error + throw error; } }; diff --git a/packages/phrases/src/locales/en.ts b/packages/phrases/src/locales/en.ts index 6cf8e4d2f..6e9d45787 100644 --- a/packages/phrases/src/locales/en.ts +++ b/packages/phrases/src/locales/en.ts @@ -27,8 +27,21 @@ const errors = { }, oidc: { aborted: 'The end-user aborted interaction.', - invalid_scope: 'Scope {{scopes}} is not supported.', + invalid_scope: 'Scope {{scope}} is not supported.', invalid_scope_plural: 'Scope {{scopes}} are not supported.', + invalid_token: 'Invalid token provided.', + invalid_client_metadata: 'Invalid client metadata provided.', + insufficient_scope: 'Access token missing requested scope {{scopes}}.', + invalid_request: 'Request is invalid.', + invalid_grant: 'Grant request is invalid.', + invalid_redirect_uri: + "`redirect_uri` did not match any of the client's registered `redirect_uris`.", + access_denied: 'Access denied.', + invalid_target: 'Invalid resource indicator.', + unsupported_grant_type: 'Unsupported `grant_type` requested.', + unsupported_response_mode: 'Unsupported `response_mode` requested.', + unsupported_response_type: 'Unsupported `response_type` requested.', + provider_error: 'OIDC Internal Error: {{message}}.', }, user: { username_exists: 'The username already exists.', diff --git a/packages/phrases/src/locales/zh-cn.ts b/packages/phrases/src/locales/zh-cn.ts index c7c351753..644367cb9 100644 --- a/packages/phrases/src/locales/zh-cn.ts +++ b/packages/phrases/src/locales/zh-cn.ts @@ -31,6 +31,18 @@ const errors = { aborted: '用户终止了交互。', invalid_scope: '不支持的 scope: {{scopes}}。', invalid_scope_plural: '不支持的 scope: {{scopes}}。', + invalid_token: 'token 无效。', + invalid_client_metadata: '无效 client metadata。', + insufficient_scope: '请求 token 缺少一下权限: {{scopes}}。', + invalid_request: '请求失败。', + invalid_grant: '授权失败。', + invalid_redirect_uri: '无效返回链接, 该 redirect_uri 未被此应用注册。', + access_denied: '拒绝访问。', + invalid_target: '请求资源无效。', + unsupported_grant_type: '不支持的 grant_type。', + unsupported_response_mode: '不支持的 response_mode。', + unsupported_response_type: '不支持的 response_type。', + provider_error: 'OIDC 错误: {{message}}。', }, user: { username_exists: '用户名已存在。',