mirror of
https://github.com/logto-io/logto.git
synced 2025-02-17 22:04:19 -05:00
feat: DELETE /application/:id
This commit is contained in:
parent
27ec6fcb00
commit
af11f18e40
6 changed files with 67 additions and 29 deletions
1
packages/core/src/env/consts.ts
vendored
1
packages/core/src/env/consts.ts
vendored
|
@ -4,3 +4,4 @@ export const signIn = assertEnv('UI_SIGN_IN_ROUTE');
|
|||
export const isProduction = getEnv('NODE_ENV') === 'production';
|
||||
export const port = Number(getEnv('PORT', '3001'));
|
||||
export const mountedApps = Object.freeze(['api', 'oidc']);
|
||||
export const developmentUserId = getEnv('DEVELOPMENT_USER_ID');
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
import assert from 'assert';
|
||||
import { IncomingHttpHeaders } from 'http';
|
||||
import RequestError from '@/errors/RequestError';
|
||||
import { MiddlewareType } from 'koa';
|
||||
import { MiddlewareType, Request } from 'koa';
|
||||
import { jwtVerify } from 'jose/jwt/verify';
|
||||
import { publicKey, issuer, adminResource } from '@/oidc/consts';
|
||||
import { IRouterParamContext } from 'koa-router';
|
||||
import { UserInfo, userInfoSelectFields } from '@logto/schemas';
|
||||
import { findUserById } from '@/queries/user';
|
||||
import pick from 'lodash.pick';
|
||||
import { developmentUserId, isProduction } from '@/env/consts';
|
||||
|
||||
export type WithAuthContext<ContextT extends IRouterParamContext = IRouterParamContext> =
|
||||
ContextT & {
|
||||
|
@ -15,35 +17,45 @@ export type WithAuthContext<ContextT extends IRouterParamContext = IRouterParamC
|
|||
|
||||
const bearerToken = 'Bearer';
|
||||
|
||||
const extractBearerTokenFromHeaders = ({ authorization }: IncomingHttpHeaders) => {
|
||||
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 authorization.slice(bearerToken.length + 1);
|
||||
};
|
||||
|
||||
const getUserIdFromRequest = async (request: Request) => {
|
||||
if (!isProduction && developmentUserId) {
|
||||
return developmentUserId;
|
||||
}
|
||||
|
||||
const {
|
||||
payload: { sub },
|
||||
} = await jwtVerify(extractBearerTokenFromHeaders(request.headers), publicKey, {
|
||||
issuer,
|
||||
audience: adminResource,
|
||||
});
|
||||
assert(sub);
|
||||
return sub;
|
||||
};
|
||||
|
||||
export default function koaAuth<
|
||||
StateT,
|
||||
ContextT extends IRouterParamContext,
|
||||
ResponseBodyT
|
||||
>(): MiddlewareType<StateT, WithAuthContext<ContextT>, ResponseBodyT> {
|
||||
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] }
|
||||
)
|
||||
);
|
||||
const jwt = authorization.slice(bearerToken.length + 1);
|
||||
|
||||
try {
|
||||
const {
|
||||
payload: { sub },
|
||||
} = await jwtVerify(jwt, publicKey, {
|
||||
issuer,
|
||||
audience: adminResource,
|
||||
});
|
||||
assert(sub);
|
||||
const user = await findUserById(sub);
|
||||
const userId = await getUserIdFromRequest(ctx.request);
|
||||
const user = await findUserById(userId);
|
||||
ctx.user = pick(user, ...userInfoSelectFields);
|
||||
} catch {
|
||||
throw new RequestError({ code: 'auth.unauthorized', status: 401 });
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { buildInsertInto } from '@/database/insert';
|
||||
import pool from '@/database/pool';
|
||||
import { convertToIdentifiers } from '@/database/utils';
|
||||
import RequestError from '@/errors/RequestError';
|
||||
import { ApplicationDBEntry, Applications } from '@logto/schemas';
|
||||
import { sql } from 'slonik';
|
||||
|
||||
|
@ -8,11 +9,21 @@ const { table, fields } = convertToIdentifiers(Applications);
|
|||
|
||||
export const findApplicationById = async (id: string) =>
|
||||
pool.one<ApplicationDBEntry>(sql`
|
||||
select ${sql.join(Object.values(fields), sql`, `)}
|
||||
from ${table}
|
||||
where ${fields.id}=${id}
|
||||
`);
|
||||
select ${sql.join(Object.values(fields), sql`, `)}
|
||||
from ${table}
|
||||
where ${fields.id}=${id}
|
||||
`);
|
||||
|
||||
export const insertApplication = buildInsertInto<ApplicationDBEntry>(pool, Applications, {
|
||||
returning: true,
|
||||
});
|
||||
|
||||
export const deleteApplicationById = async (id: string) => {
|
||||
const { rowCount } = await pool.query(sql`
|
||||
delete from ${table}
|
||||
where id=${id}
|
||||
`);
|
||||
if (rowCount < 1) {
|
||||
throw new RequestError({ code: 'entity.not_exists', name: Applications.tableSingular, id });
|
||||
}
|
||||
};
|
||||
|
|
|
@ -2,7 +2,7 @@ import Router from 'koa-router';
|
|||
import { nativeEnum, object, string } from 'zod';
|
||||
import { ApplicationType } from '@logto/schemas';
|
||||
import koaGuard from '@/middleware/koa-guard';
|
||||
import { insertApplication } from '@/queries/application';
|
||||
import { deleteApplicationById, insertApplication } from '@/queries/application';
|
||||
import { buildIdGenerator } from '@/utils/id';
|
||||
import { generateOidcClientMetadata } from '@/oidc/utils';
|
||||
|
||||
|
@ -29,4 +29,16 @@ export default function applicationRoutes<StateT, ContextT>(router: Router<State
|
|||
return next();
|
||||
}
|
||||
);
|
||||
|
||||
router.delete(
|
||||
'/application/:id',
|
||||
koaGuard({ params: object({ id: string().min(1) }) }),
|
||||
async (ctx, next) => {
|
||||
const { id } = ctx.guard.params;
|
||||
// Note: will need delete cascade when application is joint with other tables
|
||||
await deleteApplicationById(id);
|
||||
ctx.status = 204;
|
||||
return next();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
@ -39,6 +39,7 @@ const errors = {
|
|||
},
|
||||
entity: {
|
||||
create_failed: 'Failed to create {{name}}.',
|
||||
not_exists: 'The {{name}} with ID `{{id}}` does not exist.',
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -40,7 +40,8 @@ const errors = {
|
|||
invalid_zod_type: '无效的 Zod 类型,请检查路由 guard 配置。',
|
||||
},
|
||||
entity: {
|
||||
create_failed: '创建{{name}}失败。',
|
||||
create_failed: '创建 {{name}} 失败。',
|
||||
not_exists: 'ID 为 `{{id}}` 的 {{name}} 不存在。',
|
||||
},
|
||||
};
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue