0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-02-17 22:04:19 -05:00

Merge pull request #87 from logto-io/gao-log-22

This commit is contained in:
Gao Sun 2021-08-26 13:39:55 +08:00 committed by GitHub
commit ee1133cf49
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 69 additions and 31 deletions

View file

@ -24,4 +24,4 @@ jobs:
run: commitlint --from HEAD~${{ github.event.pull_request.commits }} --to HEAD
- name: Commitlint on PR title
run: echo ${{ github.event.pull_request.title }} | commitlint
run: echo '${{ github.event.pull_request.title }}' | commitlint

View file

@ -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');

View file

@ -1,19 +1,51 @@
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 & {
user: UserInfo;
};
const bearerToken = 'Bearer';
const bearerTokenIdentifier = 'Bearer';
const extractBearerTokenFromHeaders = ({ authorization }: IncomingHttpHeaders) => {
assert(
authorization,
new RequestError({ code: 'auth.authorization_header_missing', status: 401 })
);
assert(
authorization.startsWith(bearerTokenIdentifier),
new RequestError(
{ code: 'auth.authorization_type_not_supported', status: 401 },
{ supportedTypes: [bearerTokenIdentifier] }
)
);
return authorization.slice(bearerTokenIdentifier.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,
@ -21,29 +53,9 @@ export default function koaAuth<
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 });

View file

@ -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 });
}
};

View file

@ -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();
}
);
}

View file

@ -39,6 +39,7 @@ const errors = {
},
entity: {
create_failed: 'Failed to create {{name}}.',
not_exists: 'The {{name}} with ID `{{id}}` does not exist.',
},
};

View file

@ -40,7 +40,8 @@ const errors = {
invalid_zod_type: '无效的 Zod 类型,请检查路由 guard 配置。',
},
entity: {
create_failed: '创建{{name}}失败。',
create_failed: '创建 {{name}} 失败。',
not_exists: 'ID 为 `{{id}}` 的 {{name}} 不存在。',
},
};