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 isProduction = getEnv('NODE_ENV') === 'production';
|
||||||
export const port = Number(getEnv('PORT', '3001'));
|
export const port = Number(getEnv('PORT', '3001'));
|
||||||
export const mountedApps = Object.freeze(['api', 'oidc']);
|
export const mountedApps = Object.freeze(['api', 'oidc']);
|
||||||
|
export const developmentUserId = getEnv('DEVELOPMENT_USER_ID');
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
import assert from 'assert';
|
import assert from 'assert';
|
||||||
|
import { IncomingHttpHeaders } from 'http';
|
||||||
import RequestError from '@/errors/RequestError';
|
import RequestError from '@/errors/RequestError';
|
||||||
import { MiddlewareType } from 'koa';
|
import { MiddlewareType, Request } from 'koa';
|
||||||
import { jwtVerify } from 'jose/jwt/verify';
|
import { jwtVerify } from 'jose/jwt/verify';
|
||||||
import { publicKey, issuer, adminResource } from '@/oidc/consts';
|
import { publicKey, issuer, adminResource } from '@/oidc/consts';
|
||||||
import { IRouterParamContext } from 'koa-router';
|
import { IRouterParamContext } from 'koa-router';
|
||||||
import { UserInfo, userInfoSelectFields } from '@logto/schemas';
|
import { UserInfo, userInfoSelectFields } from '@logto/schemas';
|
||||||
import { findUserById } from '@/queries/user';
|
import { findUserById } from '@/queries/user';
|
||||||
import pick from 'lodash.pick';
|
import pick from 'lodash.pick';
|
||||||
|
import { developmentUserId, isProduction } from '@/env/consts';
|
||||||
|
|
||||||
export type WithAuthContext<ContextT extends IRouterParamContext = IRouterParamContext> =
|
export type WithAuthContext<ContextT extends IRouterParamContext = IRouterParamContext> =
|
||||||
ContextT & {
|
ContextT & {
|
||||||
|
@ -15,13 +17,7 @@ export type WithAuthContext<ContextT extends IRouterParamContext = IRouterParamC
|
||||||
|
|
||||||
const bearerToken = 'Bearer';
|
const bearerToken = 'Bearer';
|
||||||
|
|
||||||
export default function koaAuth<
|
const extractBearerTokenFromHeaders = ({ authorization }: IncomingHttpHeaders) => {
|
||||||
StateT,
|
|
||||||
ContextT extends IRouterParamContext,
|
|
||||||
ResponseBodyT
|
|
||||||
>(): MiddlewareType<StateT, WithAuthContext<ContextT>, ResponseBodyT> {
|
|
||||||
return async (ctx, next) => {
|
|
||||||
const { authorization } = ctx.request.headers;
|
|
||||||
assert(
|
assert(
|
||||||
authorization,
|
authorization,
|
||||||
new RequestError({ code: 'auth.authorization_header_missing', status: 401 })
|
new RequestError({ code: 'auth.authorization_header_missing', status: 401 })
|
||||||
|
@ -33,17 +29,33 @@ export default function koaAuth<
|
||||||
{ supportedTypes: [bearerToken] }
|
{ supportedTypes: [bearerToken] }
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
const jwt = authorization.slice(bearerToken.length + 1);
|
return authorization.slice(bearerToken.length + 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getUserIdFromRequest = async (request: Request) => {
|
||||||
|
if (!isProduction && developmentUserId) {
|
||||||
|
return developmentUserId;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
const {
|
const {
|
||||||
payload: { sub },
|
payload: { sub },
|
||||||
} = await jwtVerify(jwt, publicKey, {
|
} = await jwtVerify(extractBearerTokenFromHeaders(request.headers), publicKey, {
|
||||||
issuer,
|
issuer,
|
||||||
audience: adminResource,
|
audience: adminResource,
|
||||||
});
|
});
|
||||||
assert(sub);
|
assert(sub);
|
||||||
const user = await findUserById(sub);
|
return sub;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function koaAuth<
|
||||||
|
StateT,
|
||||||
|
ContextT extends IRouterParamContext,
|
||||||
|
ResponseBodyT
|
||||||
|
>(): MiddlewareType<StateT, WithAuthContext<ContextT>, ResponseBodyT> {
|
||||||
|
return async (ctx, next) => {
|
||||||
|
try {
|
||||||
|
const userId = await getUserIdFromRequest(ctx.request);
|
||||||
|
const user = await findUserById(userId);
|
||||||
ctx.user = pick(user, ...userInfoSelectFields);
|
ctx.user = pick(user, ...userInfoSelectFields);
|
||||||
} catch {
|
} catch {
|
||||||
throw new RequestError({ code: 'auth.unauthorized', status: 401 });
|
throw new RequestError({ code: 'auth.unauthorized', status: 401 });
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { buildInsertInto } from '@/database/insert';
|
import { buildInsertInto } from '@/database/insert';
|
||||||
import pool from '@/database/pool';
|
import pool from '@/database/pool';
|
||||||
import { convertToIdentifiers } from '@/database/utils';
|
import { convertToIdentifiers } from '@/database/utils';
|
||||||
|
import RequestError from '@/errors/RequestError';
|
||||||
import { ApplicationDBEntry, Applications } from '@logto/schemas';
|
import { ApplicationDBEntry, Applications } from '@logto/schemas';
|
||||||
import { sql } from 'slonik';
|
import { sql } from 'slonik';
|
||||||
|
|
||||||
|
@ -11,8 +12,18 @@ export const findApplicationById = async (id: string) =>
|
||||||
select ${sql.join(Object.values(fields), sql`, `)}
|
select ${sql.join(Object.values(fields), sql`, `)}
|
||||||
from ${table}
|
from ${table}
|
||||||
where ${fields.id}=${id}
|
where ${fields.id}=${id}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
export const insertApplication = buildInsertInto<ApplicationDBEntry>(pool, Applications, {
|
export const insertApplication = buildInsertInto<ApplicationDBEntry>(pool, Applications, {
|
||||||
returning: true,
|
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 { nativeEnum, object, string } from 'zod';
|
||||||
import { ApplicationType } from '@logto/schemas';
|
import { ApplicationType } from '@logto/schemas';
|
||||||
import koaGuard from '@/middleware/koa-guard';
|
import koaGuard from '@/middleware/koa-guard';
|
||||||
import { insertApplication } from '@/queries/application';
|
import { deleteApplicationById, insertApplication } from '@/queries/application';
|
||||||
import { buildIdGenerator } from '@/utils/id';
|
import { buildIdGenerator } from '@/utils/id';
|
||||||
import { generateOidcClientMetadata } from '@/oidc/utils';
|
import { generateOidcClientMetadata } from '@/oidc/utils';
|
||||||
|
|
||||||
|
@ -29,4 +29,16 @@ export default function applicationRoutes<StateT, ContextT>(router: Router<State
|
||||||
return next();
|
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: {
|
entity: {
|
||||||
create_failed: 'Failed to create {{name}}.',
|
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 配置。',
|
invalid_zod_type: '无效的 Zod 类型,请检查路由 guard 配置。',
|
||||||
},
|
},
|
||||||
entity: {
|
entity: {
|
||||||
create_failed: '创建{{name}}失败。',
|
create_failed: '创建 {{name}} 失败。',
|
||||||
|
not_exists: 'ID 为 `{{id}}` 的 {{name}} 不存在。',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue