2024-03-26 13:23:41 +08:00
|
|
|
import path from 'node:path';
|
|
|
|
|
|
|
|
import type { CustomClientMetadata, ExtraParamsObject, OidcClientMetadata } from '@logto/schemas';
|
|
|
|
import {
|
|
|
|
ApplicationType,
|
|
|
|
customClientMetadataGuard,
|
|
|
|
GrantType,
|
|
|
|
ExtraParamsKey,
|
|
|
|
FirstScreen,
|
|
|
|
experience,
|
|
|
|
} from '@logto/schemas';
|
2022-09-21 13:06:56 +08:00
|
|
|
import { conditional } from '@silverhand/essentials';
|
2023-11-08 15:30:05 +08:00
|
|
|
import { type AllClientMetadata, type ClientAuthMethod, errors } from 'oidc-provider';
|
2021-08-18 00:24:00 +08:00
|
|
|
|
2023-01-11 16:41:53 +08:00
|
|
|
import type { EnvSet } from '#src/env-set/index.js';
|
2023-01-09 17:34:13 +08:00
|
|
|
|
2023-01-11 16:41:53 +08:00
|
|
|
export const getConstantClientMetadata = (
|
|
|
|
envSet: EnvSet,
|
|
|
|
type: ApplicationType
|
|
|
|
): AllClientMetadata => {
|
2023-01-09 17:34:13 +08:00
|
|
|
const { jwkSigningAlg } = envSet.oidc;
|
|
|
|
|
2022-09-21 13:06:56 +08:00
|
|
|
const getTokenEndpointAuthMethod = (): ClientAuthMethod => {
|
|
|
|
switch (type) {
|
|
|
|
case ApplicationType.Native:
|
2023-02-16 23:49:03 +08:00
|
|
|
case ApplicationType.SPA: {
|
2022-09-21 13:06:56 +08:00
|
|
|
return 'none';
|
2023-02-16 23:49:03 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
default: {
|
2022-09-21 13:06:56 +08:00
|
|
|
return 'client_secret_basic';
|
2023-02-16 23:49:03 +08:00
|
|
|
}
|
2022-09-21 13:06:56 +08:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
return {
|
|
|
|
application_type: type === ApplicationType.Native ? 'native' : 'web',
|
|
|
|
grant_types:
|
|
|
|
type === ApplicationType.MachineToMachine
|
|
|
|
? [GrantType.ClientCredentials]
|
2024-07-01 16:36:34 +08:00
|
|
|
: [GrantType.AuthorizationCode, GrantType.RefreshToken, GrantType.TokenExchange],
|
2022-09-21 13:06:56 +08:00
|
|
|
token_endpoint_auth_method: getTokenEndpointAuthMethod(),
|
|
|
|
response_types: conditional(type === ApplicationType.MachineToMachine && []),
|
2023-01-09 17:34:13 +08:00
|
|
|
// https://www.scottbrady91.com/jose/jwts-which-signing-algorithm-should-i-use
|
|
|
|
authorization_signed_response_alg: jwkSigningAlg,
|
|
|
|
userinfo_signed_response_alg: jwkSigningAlg,
|
|
|
|
id_token_signed_response_alg: jwkSigningAlg,
|
|
|
|
introspection_signed_response_alg: jwkSigningAlg,
|
2022-09-21 13:06:56 +08:00
|
|
|
};
|
|
|
|
};
|
2021-10-11 17:55:17 +08:00
|
|
|
|
2022-01-11 11:58:58 +08:00
|
|
|
export const buildOidcClientMetadata = (metadata?: OidcClientMetadata): OidcClientMetadata => ({
|
2021-08-26 13:05:23 +08:00
|
|
|
redirectUris: [],
|
|
|
|
postLogoutRedirectUris: [],
|
2021-10-11 17:55:17 +08:00
|
|
|
...metadata,
|
2021-08-18 00:24:00 +08:00
|
|
|
});
|
2022-04-08 18:16:20 +08:00
|
|
|
|
2024-07-13 21:30:35 +08:00
|
|
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
|
|
const isKeyOf = <T extends object>(object: T, key: string | number | symbol): key is keyof T =>
|
|
|
|
key in object;
|
2022-04-08 18:16:20 +08:00
|
|
|
|
2024-07-13 21:30:35 +08:00
|
|
|
export const validateCustomClientMetadata = (key: string, value: unknown) => {
|
|
|
|
if (isKeyOf(customClientMetadataGuard.shape, key)) {
|
|
|
|
const result = customClientMetadataGuard.shape[key].safeParse(value);
|
|
|
|
if (result.success) {
|
|
|
|
return;
|
|
|
|
}
|
2022-04-08 18:16:20 +08:00
|
|
|
}
|
2024-07-13 21:30:35 +08:00
|
|
|
|
|
|
|
throw new errors.InvalidClientMetadata(key);
|
2022-04-08 18:16:20 +08:00
|
|
|
};
|
|
|
|
|
2022-07-05 17:36:43 +08:00
|
|
|
export const isOriginAllowed = (
|
|
|
|
origin: string,
|
2022-07-05 21:09:57 +08:00
|
|
|
{ corsAllowedOrigins = [] }: CustomClientMetadata,
|
2022-07-05 17:36:43 +08:00
|
|
|
redirectUris: string[] = []
|
|
|
|
) => {
|
|
|
|
const redirectUriOrigins = redirectUris.map((uri) => new URL(uri).origin);
|
|
|
|
|
|
|
|
return [...corsAllowedOrigins, ...redirectUriOrigins].includes(origin);
|
|
|
|
};
|
2023-07-07 15:14:29 +08:00
|
|
|
|
2023-12-25 12:56:53 +08:00
|
|
|
export const getUtcStartOfTheDay = (date: Date) => {
|
|
|
|
return new Date(
|
|
|
|
Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), 0, 0, 0, 0)
|
|
|
|
);
|
2023-07-07 15:14:29 +08:00
|
|
|
};
|
2024-03-26 13:23:41 +08:00
|
|
|
|
|
|
|
export const buildLoginPromptUrl = (params: ExtraParamsObject, appId?: unknown): string => {
|
|
|
|
const firstScreenKey =
|
|
|
|
params[ExtraParamsKey.FirstScreen] ??
|
|
|
|
params[ExtraParamsKey.InteractionMode] ??
|
|
|
|
FirstScreen.SignIn;
|
|
|
|
const firstScreen =
|
|
|
|
firstScreenKey === 'signUp' ? experience.routes.register : experience.routes[firstScreenKey];
|
|
|
|
const directSignIn = params[ExtraParamsKey.DirectSignIn];
|
|
|
|
const searchParams = new URLSearchParams();
|
|
|
|
const getSearchParamString = () => (searchParams.size > 0 ? `?${searchParams.toString()}` : '');
|
|
|
|
|
2024-07-08 16:52:15 +08:00
|
|
|
if (appId) {
|
|
|
|
searchParams.append('app_id', String(appId));
|
|
|
|
}
|
|
|
|
|
2024-07-12 19:00:36 +08:00
|
|
|
if (params[ExtraParamsKey.OrganizationId]) {
|
|
|
|
searchParams.append(ExtraParamsKey.OrganizationId, params[ExtraParamsKey.OrganizationId]);
|
|
|
|
}
|
|
|
|
|
2024-08-09 13:39:02 +08:00
|
|
|
if (params[ExtraParamsKey.LoginHint]) {
|
|
|
|
searchParams.append(ExtraParamsKey.LoginHint, params[ExtraParamsKey.LoginHint]);
|
|
|
|
}
|
|
|
|
|
2024-03-26 13:23:41 +08:00
|
|
|
if (directSignIn) {
|
|
|
|
searchParams.append('fallback', firstScreen);
|
|
|
|
const [method, target] = directSignIn.split(':');
|
|
|
|
return path.join('direct', method ?? '', target ?? '') + getSearchParamString();
|
|
|
|
}
|
|
|
|
|
|
|
|
return firstScreen + getSearchParamString();
|
|
|
|
};
|