2022-01-13 01:15:13 -05:00
|
|
|
import { customClientMetadataGuard, CustomClientMetadataType } from '@logto/schemas';
|
2021-08-29 22:30:54 -05:00
|
|
|
import { fromKeyLike } from 'jose/jwk/from_key_like';
|
2021-06-27 07:44:05 -05:00
|
|
|
import Koa from 'koa';
|
|
|
|
import mount from 'koa-mount';
|
2021-12-02 01:08:15 -05:00
|
|
|
import { Provider, errors } from 'oidc-provider';
|
2021-06-27 07:44:05 -05:00
|
|
|
|
2021-08-29 22:30:54 -05:00
|
|
|
import postgresAdapter from '@/oidc/adapter';
|
2022-01-13 22:54:09 -05:00
|
|
|
import { findResourceByIdentifier } from '@/queries/resource';
|
|
|
|
import { findAllScopesWithResourceId } from '@/queries/scope';
|
2021-07-02 09:55:14 -05:00
|
|
|
import { findUserById } from '@/queries/user';
|
2021-08-11 09:37:21 -05:00
|
|
|
import { routes } from '@/routes/consts';
|
2021-08-29 22:30:54 -05:00
|
|
|
|
2022-01-13 01:15:13 -05:00
|
|
|
import { issuer, privateKey, defaultIdTokenTtl, defaultRefreshTokenTtl } from './consts';
|
2021-06-27 07:44:05 -05:00
|
|
|
|
2021-07-09 10:25:24 -05:00
|
|
|
export default async function initOidc(app: Koa): Promise<Provider> {
|
2021-06-27 07:44:05 -05:00
|
|
|
const keys = [await fromKeyLike(privateKey)];
|
2021-07-04 02:01:02 -05:00
|
|
|
const cookieConfig = Object.freeze({
|
|
|
|
sameSite: 'lax',
|
|
|
|
path: '/',
|
|
|
|
signed: true,
|
|
|
|
} as const);
|
2021-08-15 10:39:03 -05:00
|
|
|
const oidc = new Provider(issuer, {
|
2021-06-27 07:44:05 -05:00
|
|
|
adapter: postgresAdapter,
|
|
|
|
renderError: (ctx, out, error) => {
|
2021-07-04 02:01:02 -05:00
|
|
|
console.log('OIDC error', error);
|
2021-10-12 04:57:22 -05:00
|
|
|
throw error;
|
2021-06-27 07:44:05 -05:00
|
|
|
},
|
|
|
|
cookies: {
|
|
|
|
// V2: Rotate this when necessary
|
|
|
|
// https://github.com/panva/node-oidc-provider/blob/main/docs/README.md#cookieskeys
|
|
|
|
keys: ['LOGTOSEKRIT1'],
|
2021-07-04 02:01:02 -05:00
|
|
|
long: cookieConfig,
|
|
|
|
short: cookieConfig,
|
2021-06-27 07:44:05 -05:00
|
|
|
},
|
|
|
|
jwks: {
|
|
|
|
keys,
|
|
|
|
},
|
2021-07-02 09:09:38 -05:00
|
|
|
features: {
|
2022-01-23 21:13:18 -05:00
|
|
|
userinfo: { enabled: true },
|
2021-07-02 09:09:38 -05:00
|
|
|
revocation: { enabled: true },
|
|
|
|
introspection: { enabled: true },
|
|
|
|
devInteractions: { enabled: false },
|
2021-08-15 10:39:03 -05:00
|
|
|
resourceIndicators: {
|
|
|
|
enabled: true,
|
2022-01-23 21:13:18 -05:00
|
|
|
// Disable the auto use of authorization_code granted resource feature
|
|
|
|
// https://github.com/panva/node-oidc-provider/blob/main/docs/README.md#usegrantedresource
|
|
|
|
useGrantedResource: () => false,
|
2021-12-02 01:08:15 -05:00
|
|
|
getResourceServerInfo: async (ctx, indicator) => {
|
|
|
|
const resourceServer = await findResourceByIdentifier(indicator);
|
|
|
|
|
|
|
|
if (!resourceServer) {
|
|
|
|
throw new errors.InvalidTarget();
|
|
|
|
}
|
|
|
|
|
2022-01-13 22:54:09 -05:00
|
|
|
const { id, accessTokenTtl: accessTokenTTL } = resourceServer;
|
2021-12-02 01:08:15 -05:00
|
|
|
const scopes = await findAllScopesWithResourceId(id);
|
|
|
|
const scope = scopes.map(({ name }) => name).join(' ');
|
|
|
|
|
|
|
|
return {
|
|
|
|
scope,
|
2022-01-13 22:54:09 -05:00
|
|
|
accessTokenTTL,
|
2021-12-02 01:08:15 -05:00
|
|
|
};
|
|
|
|
},
|
2021-08-15 10:39:03 -05:00
|
|
|
},
|
2021-07-02 09:09:38 -05:00
|
|
|
},
|
|
|
|
interactions: {
|
2021-07-04 02:01:02 -05: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}`);
|
|
|
|
}
|
|
|
|
},
|
2021-07-02 09:09:38 -05:00
|
|
|
},
|
2022-01-13 01:15:13 -05:00
|
|
|
extraClientMetadata: {
|
|
|
|
properties: Object.keys(CustomClientMetadataType),
|
|
|
|
validator: (_ctx, key, value) => {
|
|
|
|
const result = customClientMetadataGuard.pick({ [key]: true }).safeParse({ key: value });
|
|
|
|
if (!result.success) {
|
|
|
|
throw new errors.InvalidClientMetadata(key);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
},
|
2021-07-02 09:09:38 -05:00
|
|
|
clientBasedCORS: (_, origin) => {
|
2021-07-02 08:14:18 -05:00
|
|
|
console.log('origin', origin);
|
2021-06-29 09:58:59 -05:00
|
|
|
return origin.startsWith('http://localhost:3000');
|
|
|
|
},
|
2021-07-02 08:14:18 -05:00
|
|
|
findAccount: async (ctx, sub) => {
|
|
|
|
await findUserById(sub);
|
|
|
|
|
2021-06-27 07:44:05 -05:00
|
|
|
return {
|
|
|
|
accountId: sub,
|
2021-07-02 09:09:38 -05:00
|
|
|
claims: async (use, scope, claims, rejected) => {
|
|
|
|
console.log('use:', use);
|
|
|
|
console.log('scope:', scope);
|
|
|
|
console.log('claims:', claims);
|
|
|
|
console.log('rejected:', rejected);
|
2021-06-27 07:44:05 -05:00
|
|
|
return { sub };
|
|
|
|
},
|
|
|
|
};
|
|
|
|
},
|
2022-01-13 01:15:13 -05: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();
|
|
|
|
return idTokenTtl ?? defaultIdTokenTtl;
|
|
|
|
},
|
|
|
|
RefreshToken: (ctx, token, client) => {
|
|
|
|
const { refreshTokenTtl } = client.metadata();
|
|
|
|
return refreshTokenTtl ?? defaultRefreshTokenTtl;
|
|
|
|
},
|
|
|
|
},
|
2021-06-27 07:44:05 -05:00
|
|
|
});
|
|
|
|
app.use(mount('/oidc', oidc.app));
|
2021-07-03 08:19:20 -05:00
|
|
|
return oidc;
|
2021-06-27 07:44:05 -05:00
|
|
|
}
|