diff --git a/packages/core/src/errors/RequestError/index.ts b/packages/core/src/errors/RequestError/index.ts index 2179a77c8..d3992240b 100644 --- a/packages/core/src/errors/RequestError/index.ts +++ b/packages/core/src/errors/RequestError/index.ts @@ -1,6 +1,6 @@ import pick from 'lodash.pick'; import i18next from 'i18next'; -import { LogtoErrorCode } from '@logto/phrases'; +import { LogtoErrorCode, LogtoErrorI18nKey } from '@logto/phrases'; import { RequestErrorBody, RequestErrorMetadata } from '@logto/schemas'; export default class RequestError extends Error { @@ -11,7 +11,7 @@ export default class RequestError extends Error { constructor(input: RequestErrorMetadata | LogtoErrorCode, data?: unknown) { const { code, status = 400 } = typeof input === 'string' ? { code: input } : input; - const message = i18next.t(code); + const message = i18next.t(`errors:${code}`); super(message); diff --git a/packages/core/src/middleware/koa-auth.ts b/packages/core/src/middleware/koa-auth.ts new file mode 100644 index 000000000..eaab60ce4 --- /dev/null +++ b/packages/core/src/middleware/koa-auth.ts @@ -0,0 +1,28 @@ +import assert from 'assert'; +import RequestError from '@/errors/RequestError'; +import { RequestErrorBody } from '@logto/schemas'; +import { Middleware } from 'koa'; + +const bearerToken = 'Bearer'; + +export default function koaAuth(): Middleware< + StateT, + ContextT, + RequestErrorBody +> { + return async (ctx, next) => { + const { authorization } = ctx.request.headers; + assert( + authorization, + new RequestError({ code: 'auth.authorization_header_missing', status: 401 }) + ); + assert( + authorization.startsWith(bearerToken), + new RequestError( + { code: 'auth.authorization_type_not_supported', status: 401 }, + { supportedTypes: [bearerToken] } + ) + ); + return next(); + }; +} diff --git a/packages/core/src/routes/application.ts b/packages/core/src/routes/application.ts new file mode 100644 index 000000000..29d4ee9b1 --- /dev/null +++ b/packages/core/src/routes/application.ts @@ -0,0 +1,24 @@ +import Router from 'koa-router'; +import { nativeEnum, object, string } from 'zod'; +import { ApplicationType } from '@logto/schemas'; +import koaGuard from '@/middleware/koa-guard'; +import koaAuth from '@/middleware/koa-auth'; + +export default function applicationRoutes(router: Router) { + router.use('/application', koaAuth()); + router.post( + '/application', + koaGuard({ + body: object({ + name: string().min(1), + type: nativeEnum(ApplicationType), + }), + }), + async (ctx, next) => { + const { name, type } = ctx.guard.body; + + ctx.body = { name, type }; + return next(); + } + ); +} diff --git a/packages/core/src/routes/init.ts b/packages/core/src/routes/init.ts index cc96065e7..281e74d89 100644 --- a/packages/core/src/routes/init.ts +++ b/packages/core/src/routes/init.ts @@ -5,12 +5,14 @@ import sessionRoutes from '@/routes/session'; import userRoutes from '@/routes/user'; import swaggerRoutes from '@/routes/swagger'; import mount from 'koa-mount'; +import applicationRoutes from './application'; const createRouter = (provider: Provider): Router => { const router = new Router(); sessionRoutes(router, provider); userRoutes(router); + applicationRoutes(router); swaggerRoutes(router); return router; diff --git a/packages/phrases/src/index.ts b/packages/phrases/src/index.ts index d9ecc0917..c10b277f8 100644 --- a/packages/phrases/src/index.ts +++ b/packages/phrases/src/index.ts @@ -3,6 +3,7 @@ import zhCN from './locales/zh-cn'; import { Normalize, Resource } from './types'; export type LogtoErrorCode = Normalize; +export type LogtoErrorI18nKey = `errors:${LogtoErrorCode}`; const resource: Resource = { en, diff --git a/packages/phrases/src/locales/en.ts b/packages/phrases/src/locales/en.ts index 4cd80d9a4..7598e447f 100644 --- a/packages/phrases/src/locales/en.ts +++ b/packages/phrases/src/locales/en.ts @@ -15,6 +15,11 @@ const translation = { }; const errors = { + auth: { + authorization_header_missing: 'Authorization header is missing.', + authorization_type_not_supported: 'Authorization type is not supported.', + unauthorized: 'Unauthorized. Please check credentils and its scope.', + }, guard: { invalid_input: 'The request input is invalid.', }, diff --git a/packages/phrases/src/locales/zh-cn.ts b/packages/phrases/src/locales/zh-cn.ts index baa03bee6..52785c233 100644 --- a/packages/phrases/src/locales/zh-cn.ts +++ b/packages/phrases/src/locales/zh-cn.ts @@ -17,6 +17,11 @@ const translation = { }; const errors = { + auth: { + authorization_header_missing: 'Authorization 请求 header 遗漏。', + authorization_type_not_supported: '不支持的 authorization 类型。', + unauthorized: '未授权。请检查相关 credentials 和 scope。', + }, guard: { invalid_input: '请求内容有误。', },