2021-08-24 00:11:25 +08:00
|
|
|
import { IncomingHttpHeaders } from 'http';
|
2021-08-30 11:30:54 +08:00
|
|
|
|
|
|
|
import { UserInfo, userInfoSelectFields } from '@logto/schemas';
|
2021-08-15 23:39:03 +08:00
|
|
|
import { jwtVerify } from 'jose/jwt/verify';
|
2021-08-30 11:30:54 +08:00
|
|
|
import { MiddlewareType, Request } from 'koa';
|
2021-08-15 23:39:03 +08:00
|
|
|
import { IRouterParamContext } from 'koa-router';
|
|
|
|
import pick from 'lodash.pick';
|
2021-08-30 11:30:54 +08:00
|
|
|
|
2021-08-24 00:11:25 +08:00
|
|
|
import { developmentUserId, isProduction } from '@/env/consts';
|
2021-08-30 11:30:54 +08:00
|
|
|
import RequestError from '@/errors/RequestError';
|
|
|
|
import { publicKey, issuer, adminResource } from '@/oidc/consts';
|
|
|
|
import { findUserById } from '@/queries/user';
|
2021-08-31 22:45:28 +08:00
|
|
|
import assert from '@/utils/assert';
|
2021-08-15 23:39:03 +08:00
|
|
|
|
|
|
|
export type WithAuthContext<ContextT extends IRouterParamContext = IRouterParamContext> =
|
|
|
|
ContextT & {
|
|
|
|
user: UserInfo;
|
|
|
|
};
|
2021-08-14 21:39:37 +08:00
|
|
|
|
2021-08-25 23:40:19 +08:00
|
|
|
const bearerTokenIdentifier = 'Bearer';
|
2021-08-14 21:39:37 +08:00
|
|
|
|
2021-08-24 00:11:25 +08:00
|
|
|
const extractBearerTokenFromHeaders = ({ authorization }: IncomingHttpHeaders) => {
|
|
|
|
assert(
|
|
|
|
authorization,
|
|
|
|
new RequestError({ code: 'auth.authorization_header_missing', status: 401 })
|
|
|
|
);
|
|
|
|
assert(
|
2021-08-25 23:40:19 +08:00
|
|
|
authorization.startsWith(bearerTokenIdentifier),
|
2021-08-24 00:11:25 +08:00
|
|
|
new RequestError(
|
|
|
|
{ code: 'auth.authorization_type_not_supported', status: 401 },
|
2021-08-25 23:40:19 +08:00
|
|
|
{ supportedTypes: [bearerTokenIdentifier] }
|
2021-08-24 00:11:25 +08:00
|
|
|
)
|
|
|
|
);
|
2021-08-25 23:40:19 +08:00
|
|
|
return authorization.slice(bearerTokenIdentifier.length + 1);
|
2021-08-24 00:11:25 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
const getUserIdFromRequest = async (request: Request) => {
|
|
|
|
if (!isProduction && developmentUserId) {
|
|
|
|
return developmentUserId;
|
|
|
|
}
|
|
|
|
|
|
|
|
const {
|
|
|
|
payload: { sub },
|
|
|
|
} = await jwtVerify(extractBearerTokenFromHeaders(request.headers), publicKey, {
|
|
|
|
issuer,
|
|
|
|
audience: adminResource,
|
|
|
|
});
|
2021-08-31 22:45:28 +08:00
|
|
|
assert(sub, new RequestError({ code: 'auth.jwt_sub_missing', status: 401 }));
|
2021-08-24 00:11:25 +08:00
|
|
|
return sub;
|
|
|
|
};
|
|
|
|
|
2021-08-15 23:39:03 +08:00
|
|
|
export default function koaAuth<
|
2021-08-14 21:39:37 +08:00
|
|
|
StateT,
|
2021-08-15 23:39:03 +08:00
|
|
|
ContextT extends IRouterParamContext,
|
|
|
|
ResponseBodyT
|
|
|
|
>(): MiddlewareType<StateT, WithAuthContext<ContextT>, ResponseBodyT> {
|
2021-08-14 21:39:37 +08:00
|
|
|
return async (ctx, next) => {
|
2021-08-15 23:39:03 +08:00
|
|
|
try {
|
2021-08-24 00:11:25 +08:00
|
|
|
const userId = await getUserIdFromRequest(ctx.request);
|
|
|
|
const user = await findUserById(userId);
|
2021-08-15 23:39:03 +08:00
|
|
|
ctx.user = pick(user, ...userInfoSelectFields);
|
|
|
|
} catch {
|
|
|
|
throw new RequestError({ code: 'auth.unauthorized', status: 401 });
|
|
|
|
}
|
|
|
|
|
2021-08-14 21:39:37 +08:00
|
|
|
return next();
|
|
|
|
};
|
|
|
|
}
|