0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-01-20 21:32:31 -05:00
logto/packages/core/src/oidc/init.ts

199 lines
6.3 KiB
TypeScript
Raw Normal View History

/* istanbul ignore file */
import { readFileSync } from 'fs';
import { userClaims } from '@logto/core-kit';
import { CustomClientMetadataKey } from '@logto/schemas';
import { tryThat } from '@logto/shared';
2022-10-21 13:14:17 +08:00
import type Koa from 'koa';
2021-06-27 20:44:05 +08:00
import mount from 'koa-mount';
import { Provider, errors } from 'oidc-provider';
import snakecaseKeys from 'snakecase-keys';
2021-06-27 20:44:05 +08:00
2022-11-21 16:38:24 +08:00
import envSet from '#src/env-set/index.js';
import koaAuditLog from '#src/middleware/koa-audit-log.js';
2022-11-21 16:38:24 +08:00
import postgresAdapter from '#src/oidc/adapter.js';
import { isOriginAllowed, validateCustomClientMetadata } from '#src/oidc/utils.js';
import { findApplicationById } from '#src/queries/application.js';
import { findResourceByIndicator } from '#src/queries/resource.js';
import { findUserById } from '#src/queries/user.js';
import { routes } from '#src/routes/consts.js';
import assertThat from '#src/utils/assert-that.js';
import { addOidcEventListeners } from '#src/utils/oidc-provider-event-listener.js';
2021-08-30 11:30:54 +08:00
2022-11-21 16:38:24 +08:00
import { claimToUserKey, getUserClaims } from './scope.js';
export default async function initOidc(app: Koa): Promise<Provider> {
const { issuer, cookieKeys, privateJwks, defaultIdTokenTtl, defaultRefreshTokenTtl } =
envSet.oidc;
const logoutSource = readFileSync('static/html/logout.html', 'utf8');
2021-07-04 15:01:02 +08:00
const cookieConfig = Object.freeze({
sameSite: 'lax',
path: '/',
signed: true,
} as const);
2021-08-15 23:39:03 +08:00
const oidc = new Provider(issuer, {
2021-06-27 20:44:05 +08:00
adapter: postgresAdapter,
renderError: (_ctx, _out, error) => {
2021-07-04 15:01:02 +08:00
console.log('OIDC error', error);
throw error;
2021-06-27 20:44:05 +08:00
},
cookies: {
keys: cookieKeys,
2021-07-04 15:01:02 +08:00
long: cookieConfig,
short: cookieConfig,
2021-06-27 20:44:05 +08:00
},
jwks: {
keys: privateJwks,
2021-06-27 20:44:05 +08:00
},
conformIdTokenClaims: false,
features: {
userinfo: { enabled: true },
revocation: { enabled: true },
devInteractions: { enabled: false },
2022-09-21 13:06:56 +08:00
clientCredentials: { enabled: true },
rpInitiatedLogout: {
logoutSource: (ctx, form) => {
// eslint-disable-next-line no-template-curly-in-string
ctx.body = logoutSource.replace('${form}', form);
},
},
// https://github.com/panva/node-oidc-provider/blob/main/docs/README.md#featuresresourceindicators
2021-08-15 23:39:03 +08:00
resourceIndicators: {
enabled: true,
defaultResource: () => '',
// Disable the auto use of authorization_code granted resource feature
useGrantedResource: () => false,
2022-04-08 16:07:34 +08:00
getResourceServerInfo: async (_, indicator) => {
const resourceServer = await findResourceByIndicator(indicator);
if (!resourceServer) {
throw new errors.InvalidTarget();
}
2022-04-08 16:07:34 +08:00
const { accessTokenTtl: accessTokenTTL } = resourceServer;
return {
accessTokenFormat: 'jwt',
2022-04-08 16:07:34 +08:00
scope: '',
accessTokenTTL,
};
},
2021-08-15 23:39:03 +08:00
},
},
interactions: {
2021-07-04 15:01:02 +08:00
url: (_, interaction) => {
switch (interaction.prompt.name) {
case 'login':
return routes.signIn.credentials;
case 'consent':
return routes.signIn.consent;
default:
throw new Error(`Prompt not supported: ${interaction.prompt.name}`);
}
},
},
extraClientMetadata: {
properties: Object.values(CustomClientMetadataKey),
validator: (_, key, value) => {
validateCustomClientMetadata(key, value);
},
},
// https://github.com/panva/node-oidc-provider/blob/main/recipes/client_based_origins.md
clientBasedCORS: (ctx, origin, client) =>
ctx.request.origin === origin ||
isOriginAllowed(origin, client.metadata(), client.redirectUris),
// https://github.com/panva/node-oidc-provider/blob/main/recipes/claim_configuration.md
// Note node-provider will append `claims` here to the default claims instead of overriding
claims: userClaims,
// https://github.com/panva/node-oidc-provider/tree/main/docs#findaccount
findAccount: async (_ctx, sub) => {
const user = await findUserById(sub);
2021-06-27 20:44:05 +08:00
return {
accountId: sub,
claims: async (use, scope, claims, rejected) => {
return snakecaseKeys(
{
/**
* This line is required because:
* 1. TypeScript will complain since `Object.fromEntries()` has a fixed key type `string`
* 2. Scope `openid` is removed from `UserScope` enum
*/
sub,
...Object.fromEntries(
getUserClaims(use, scope, claims, rejected).map((claim) => [
claim,
user[claimToUserKey[claim]],
])
),
},
{
deep: false,
}
);
},
2021-06-27 20:44:05 +08:00
};
},
ttl: {
/**
* [OIDC Provider Default Settings](https://github.com/panva/node-oidc-provider/blob/main/docs/README.md#ttl)
*/
IdToken: (_ctx, _token, client) => {
const { idTokenTtl } = client.metadata();
2022-01-27 19:26:34 +08:00
return idTokenTtl ?? defaultIdTokenTtl;
},
RefreshToken: (_ctx, _token, client) => {
const { refreshTokenTtl } = client.metadata();
2022-01-27 19:26:34 +08:00
return refreshTokenTtl ?? defaultRefreshTokenTtl;
},
AccessToken: (ctx, token) => {
if (token.resourceServer) {
return token.resourceServer.accessTokenTTL ?? 60 * 60; // 1 hour in seconds
}
return 60 * 60; // 1 hour in seconds
},
Interaction: 3600 /* 1 hour in seconds */,
Session: 1_209_600 /* 14 days in seconds */,
Grant: 1_209_600 /* 14 days in seconds */,
},
extraTokenClaims: async (_ctx, token) => {
if (token.kind === 'AccessToken') {
const { accountId } = token;
const { roleNames } = await tryThat(
findUserById(accountId),
new errors.InvalidClient(`invalid user ${accountId}`)
);
return snakecaseKeys({
roleNames,
});
}
2022-09-21 13:06:56 +08:00
// `token.kind === 'ClientCredentials'`
const { clientId } = token;
assertThat(clientId, 'oidc.invalid_grant');
const { roleNames } = await tryThat(
findApplicationById(clientId),
new errors.InvalidClient(`invalid client ${clientId}`)
);
2022-09-21 13:06:56 +08:00
return snakecaseKeys({ roleNames });
},
2021-06-27 20:44:05 +08:00
});
addOidcEventListeners(oidc);
// Session audit logs
oidc.use(koaAuditLog());
2021-06-27 20:44:05 +08:00
app.use(mount('/oidc', oidc.app));
2022-01-27 19:26:34 +08:00
return oidc;
2021-06-27 20:44:05 +08:00
}