feat: overhaul oidc work
This commit is contained in:
parent
bda9dedbfd
commit
eb4669498a
@ -22,6 +22,10 @@
|
||||
- Switch our build processes to use TypeScript Go and Rolldown Vite for better build and type-check performance.
|
||||
- Cookies are now encrypted JWTs, preserving API key secrets (*GHSA-wrqq-v7qw-r5w7*)
|
||||
- OIDC profile pictures are now available from Gravatar by setting `oidc.profile_picture_source` to `gravatar` (closes [#232](https://github.com/tale/headplane/issues/232)).
|
||||
- OIDC now allows passing many custom parameters:
|
||||
- `oidc.authorization_endpoint`, `oidc.token_endpoint`, and `oidc.userinfo_endpoint` can be overridden to support non-standard providers or scenarios without discovery (closes [#117](https://github.com/tale/headplane/issues/117)).
|
||||
- `oidc.scope` can be set to specify custom scopes (defaults to `openid email profile`).
|
||||
- `oidc.extra_params` can be set to pass arbitrary query parameters to the authorization endpoint (closes [#197](https://github.com/tale/headplane/issues/197)).
|
||||
|
||||
### 0.6.0 (May 25, 2025)
|
||||
- Headplane 0.6.0 now requires **Headscale 0.26.0** or newer.
|
||||
|
||||
@ -25,7 +25,8 @@ export async function loader({
|
||||
const data = await beginAuthFlow(
|
||||
context.oidc,
|
||||
redirectUri,
|
||||
context.config.oidc.token_endpoint_auth_method,
|
||||
context.config.oidc.scope,
|
||||
context.config.oidc.extra_params,
|
||||
);
|
||||
|
||||
return redirect(data.url, {
|
||||
|
||||
@ -65,6 +65,11 @@ const oidcConfig = type({
|
||||
headscale_api_key_path: 'string?',
|
||||
profile_picture_source: '"oidc" | "gravatar" = "oidc"',
|
||||
strict_validation: stringToBool.default(true),
|
||||
scope: 'string = "openid email profile"',
|
||||
extra_params: 'Record<string, string>?',
|
||||
authorization_endpoint: 'string.url?',
|
||||
token_endpoint: 'string.url?',
|
||||
userinfo_endpoint: 'string.url?',
|
||||
})
|
||||
.narrow((obj: Record<string, unknown>, ctx: any) => {
|
||||
const hasVal =
|
||||
@ -97,6 +102,11 @@ const partialOidcConfig = type({
|
||||
headscale_api_key_path: 'string?',
|
||||
profile_picture_source: '("oidc" | "gravatar")?',
|
||||
strict_validation: stringToBool.default(true),
|
||||
scope: 'string?',
|
||||
extra_params: 'Record<string, string>?',
|
||||
authorization_endpoint: 'string.url?',
|
||||
token_endpoint: 'string.url?',
|
||||
userinfo_endpoint: 'string.url?',
|
||||
});
|
||||
|
||||
const headscaleConfig = type({
|
||||
|
||||
@ -9,7 +9,7 @@ import { createDbClient } from './db/client.server';
|
||||
import { createApiClient } from './headscale/api-client';
|
||||
import { loadHeadscaleConfig } from './headscale/config-loader';
|
||||
import { createHeadplaneAgent } from './hp-agent';
|
||||
import { createOidcClient } from './web/oidc';
|
||||
import { configureOidcAuth } from './web/oidc';
|
||||
import { createSessionStorage } from './web/sessions';
|
||||
|
||||
declare global {
|
||||
@ -68,7 +68,7 @@ const appLoadContext = {
|
||||
|
||||
agents,
|
||||
integration: await loadIntegration(config.integration),
|
||||
oidc: config.oidc ? await createOidcClient(config.oidc) : undefined,
|
||||
oidc: config.oidc ? await configureOidcAuth(config.oidc) : undefined,
|
||||
db,
|
||||
};
|
||||
|
||||
|
||||
@ -1,90 +1,37 @@
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import * as client from 'openid-client';
|
||||
import * as oidc from 'openid-client';
|
||||
import log from '~/utils/log';
|
||||
import type { HeadplaneConfig } from '../config/schema';
|
||||
import { HeadplaneConfig } from '../config/schema';
|
||||
|
||||
async function loadClientSecret(path: string) {
|
||||
// We need to interpolate environment variables into the path
|
||||
// Path formatting can be like ${ENV_NAME}/path/to/secret
|
||||
const matches = path.match(/\${(.*?)}/g);
|
||||
let resolvedPath = path;
|
||||
export type OidcConfig = NonNullable<HeadplaneConfig['oidc']>;
|
||||
|
||||
if (matches) {
|
||||
for (const match of matches) {
|
||||
const env = match.slice(2, -1);
|
||||
const value = process.env[env];
|
||||
if (!value) {
|
||||
log.error('config', 'Environment variable %s is not set', env);
|
||||
return;
|
||||
}
|
||||
|
||||
log.debug('config', 'Interpolating %s with %s', match, value);
|
||||
resolvedPath = resolvedPath.replace(match, value);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
log.debug('config', 'Reading client secret from %s', resolvedPath);
|
||||
const secret = await readFile(resolvedPath, 'utf-8');
|
||||
if (secret.trim().length === 0) {
|
||||
log.error('config', 'Empty OIDC client secret');
|
||||
return;
|
||||
}
|
||||
|
||||
return secret.trim();
|
||||
} catch (error) {
|
||||
log.error('config', 'Failed to read client secret from %s', path);
|
||||
log.error('config', 'Error: %s', error);
|
||||
log.debug('config', 'Error details: %o', error);
|
||||
}
|
||||
}
|
||||
|
||||
function clientAuthMethod(
|
||||
method: string,
|
||||
): (secret: string) => client.ClientAuth {
|
||||
switch (method) {
|
||||
case 'client_secret_post':
|
||||
return client.ClientSecretPost;
|
||||
export async function configureOidcAuth(config: OidcConfig) {
|
||||
log.debug('config', 'Running OIDC discovery for %s', config.issuer);
|
||||
let clientAuthMethod: oidc.ClientAuth;
|
||||
switch (config.token_endpoint_auth_method) {
|
||||
case 'client_secret_basic':
|
||||
return client.ClientSecretBasic;
|
||||
clientAuthMethod = oidc.ClientSecretBasic(config.client_secret!);
|
||||
break;
|
||||
case 'client_secret_post':
|
||||
clientAuthMethod = oidc.ClientSecretPost(config.client_secret!);
|
||||
break;
|
||||
case 'client_secret_jwt':
|
||||
return client.ClientSecretJwt;
|
||||
clientAuthMethod = oidc.ClientSecretJwt(config.client_secret!);
|
||||
break;
|
||||
default:
|
||||
throw new Error('Invalid client authentication method');
|
||||
}
|
||||
}
|
||||
|
||||
// Loads and configures an OIDC client to support OIDC authentication.
|
||||
// This runs under the assumption the OIDC configuration exists and is valid.
|
||||
// If it is invalid, Headplane automatically disables it.
|
||||
//
|
||||
// TODO: Support custom endpoints instead of relying on OIDC discovery.
|
||||
// This will enable us to support servers like GitHub that do not support
|
||||
// nor advertise a .well-known endpoint.
|
||||
export async function createOidcClient(
|
||||
config: NonNullable<HeadplaneConfig['oidc']>,
|
||||
) {
|
||||
// const secret = await loadClientSecret(oidc);
|
||||
const secret = config.client_secret_path
|
||||
? await loadClientSecret(config.client_secret_path)
|
||||
: config.client_secret;
|
||||
|
||||
if (!secret) {
|
||||
log.error('config', 'Missing an OIDC client secret');
|
||||
return;
|
||||
}
|
||||
|
||||
log.debug('config', 'Running OIDC discovery for %s', config.issuer);
|
||||
let oidcClient: oidc.Configuration;
|
||||
try {
|
||||
const oidc = await client.discovery(
|
||||
const discovery = await oidc.discovery(
|
||||
new URL(config.issuer),
|
||||
config.client_id,
|
||||
secret,
|
||||
clientAuthMethod(config.token_endpoint_auth_method)(secret),
|
||||
config.client_secret!, // TODO: Fix this config schema
|
||||
clientAuthMethod,
|
||||
);
|
||||
|
||||
const metadata = oidc.serverMetadata();
|
||||
if (!metadata.authorization_endpoint) {
|
||||
const meta = discovery.serverMetadata();
|
||||
if (!meta.authorization_endpoint) {
|
||||
log.error(
|
||||
'config',
|
||||
'Issuer discovery did not return `authorization_endpoint`',
|
||||
@ -93,70 +40,118 @@ export async function createOidcClient(
|
||||
'config',
|
||||
'OIDC server does not support authorization code flow',
|
||||
);
|
||||
log.error('config', 'You may need to set this manually in the config');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!metadata.token_endpoint) {
|
||||
if (!meta.token_endpoint) {
|
||||
log.error('config', 'Issuer discovery did not return `token_endpoint`');
|
||||
log.error('config', 'OIDC server does not support token exchange');
|
||||
log.error(
|
||||
'config',
|
||||
'OIDC server does not support authorization code flow',
|
||||
);
|
||||
log.error('config', 'You may need to set this manually in the config');
|
||||
return;
|
||||
}
|
||||
|
||||
// If this field is missing, assume the server supports all response types
|
||||
// and that we can continue safely.
|
||||
if (metadata.response_types_supported) {
|
||||
if (!metadata.response_types_supported.includes('code')) {
|
||||
log.error(
|
||||
'config',
|
||||
'Issuer discovery `response_types_supported` does not include `code`',
|
||||
);
|
||||
log.error('config', 'OIDC server does not support code flow');
|
||||
return;
|
||||
}
|
||||
if (!meta.userinfo_endpoint) {
|
||||
log.error(
|
||||
'config',
|
||||
'Issuer discovery did not return `userinfo_endpoint`',
|
||||
);
|
||||
log.error('config', 'OIDC server does not support user info endpoint');
|
||||
log.error('config', 'You may need to set this manually in the config');
|
||||
return;
|
||||
}
|
||||
|
||||
if (metadata.token_endpoint_auth_methods_supported) {
|
||||
if (meta.token_endpoint_auth_methods_supported) {
|
||||
if (
|
||||
!metadata.token_endpoint_auth_methods_supported.includes(
|
||||
!meta.token_endpoint_auth_methods_supported.includes(
|
||||
config.token_endpoint_auth_method,
|
||||
)
|
||||
) {
|
||||
log.error(
|
||||
'config',
|
||||
'Issuer discovery `token_endpoint_auth_methods_supported` does not include `%s`',
|
||||
'OIDC server does not support client authentication method %s',
|
||||
config.token_endpoint_auth_method,
|
||||
);
|
||||
log.error(
|
||||
'config',
|
||||
'OIDC server does not support %s',
|
||||
config.token_endpoint_auth_method,
|
||||
'Supported methods: %s',
|
||||
meta.token_endpoint_auth_methods_supported.join(', '),
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!metadata.userinfo_endpoint) {
|
||||
log.error(
|
||||
'config',
|
||||
'Issuer discovery did not return `userinfo_endpoint`',
|
||||
);
|
||||
log.error('config', 'OIDC server does not support userinfo endpoint');
|
||||
return;
|
||||
}
|
||||
|
||||
log.debug('config', 'OIDC client created successfully');
|
||||
log.info('config', 'Using %s as the OIDC issuer', config.issuer);
|
||||
log.debug('config', 'OIDC discovery successful');
|
||||
log.debug(
|
||||
'config',
|
||||
'Authorization endpoint: %s',
|
||||
metadata.authorization_endpoint,
|
||||
meta.authorization_endpoint,
|
||||
);
|
||||
log.debug('config', 'Token endpoint: %s', metadata.token_endpoint);
|
||||
log.debug('config', 'Userinfo endpoint: %s', metadata.userinfo_endpoint);
|
||||
return oidc;
|
||||
} catch (error) {
|
||||
log.error('config', 'Failed to discover OIDC issuer');
|
||||
log.error('config', 'Error: %s', error);
|
||||
log.debug('config', 'Error details: %o', error);
|
||||
log.debug('config', 'Token endpoint: %s', meta.token_endpoint);
|
||||
log.debug('config', 'Userinfo endpoint: %s', meta.userinfo_endpoint);
|
||||
|
||||
// Manually construct the endpoints to coalesce with config if needed
|
||||
oidcClient = new oidc.Configuration(
|
||||
{
|
||||
issuer: config.issuer,
|
||||
authorization_endpoint:
|
||||
config.authorization_endpoint || meta.authorization_endpoint,
|
||||
token_endpoint: config.token_endpoint || meta.token_endpoint,
|
||||
userinfo_endpoint: config.userinfo_endpoint || meta.userinfo_endpoint,
|
||||
},
|
||||
config.client_id,
|
||||
config.client_secret!,
|
||||
clientAuthMethod,
|
||||
);
|
||||
} catch (err) {
|
||||
log.error('config', 'OIDC discovery failed: %s', err);
|
||||
log.debug('config', 'Error details: %o', err);
|
||||
log.error(
|
||||
'config',
|
||||
'This may be an error, or the server may not support discovery',
|
||||
);
|
||||
|
||||
if (
|
||||
!config.authorization_endpoint ||
|
||||
!config.token_endpoint ||
|
||||
!config.userinfo_endpoint
|
||||
) {
|
||||
log.error(
|
||||
'config',
|
||||
'Endpoints are not fully configured, cannot continue',
|
||||
);
|
||||
log.error(
|
||||
'config',
|
||||
'You must set authorization_endpoint, token_endpoint and userinfo_endpoint manually in the config or fix the discovery issue',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
oidcClient = new oidc.Configuration(
|
||||
{
|
||||
issuer: config.issuer,
|
||||
authorization_endpoint: config.authorization_endpoint,
|
||||
token_endpoint: config.token_endpoint,
|
||||
userinfo_endpoint: config.userinfo_endpoint,
|
||||
},
|
||||
config.client_id,
|
||||
config.client_secret!,
|
||||
clientAuthMethod,
|
||||
);
|
||||
|
||||
log.debug('config', 'Using manually configured endpoints');
|
||||
log.debug(
|
||||
'config',
|
||||
'Authorization endpoint: %s',
|
||||
config.authorization_endpoint,
|
||||
);
|
||||
log.debug('config', 'Token endpoint: %s', config.token_endpoint);
|
||||
log.debug('config', 'Userinfo endpoint: %s', config.userinfo_endpoint);
|
||||
}
|
||||
|
||||
log.info('config', 'Successfully configured OIDC authentication');
|
||||
return oidcClient;
|
||||
}
|
||||
|
||||
@ -33,22 +33,21 @@ export function getRedirectUri(req: Request) {
|
||||
export async function beginAuthFlow(
|
||||
config: Configuration,
|
||||
redirect_uri: string,
|
||||
token_endpoint_auth_method: string,
|
||||
scope: string,
|
||||
extra_params: Record<string, string> = {},
|
||||
) {
|
||||
const codeVerifier = client.randomPKCECodeVerifier();
|
||||
const codeChallenge = await client.calculatePKCECodeChallenge(codeVerifier);
|
||||
|
||||
const params: Record<string, string> = {
|
||||
...extra_params,
|
||||
scope,
|
||||
redirect_uri,
|
||||
scope: 'openid profile email',
|
||||
code_challenge: codeChallenge,
|
||||
code_challenge_method: 'S256',
|
||||
token_endpoint_auth_method,
|
||||
state: client.randomState(),
|
||||
};
|
||||
|
||||
// PKCE is backwards compatible with non-PKCE servers
|
||||
// so if we don't support it, just set our nonce
|
||||
if (!config.serverMetadata().supportsPKCE()) {
|
||||
params.nonce = client.randomNonce();
|
||||
}
|
||||
|
||||
@ -136,6 +136,18 @@ integration:
|
||||
# (This is optional, but recommended for the best experience)
|
||||
oidc:
|
||||
issuer: "https://accounts.google.com"
|
||||
|
||||
# If your OIDC provider does not support discovery (does not have the URL at
|
||||
# `/.well-known/openid-configuration`), you need to manually set endpoints.
|
||||
# This also works to override endpoints if you so desire or if your OIDC
|
||||
# discovery is missing certain endpoints (ie GitHub).
|
||||
# For some typical providers, see the documentation.
|
||||
|
||||
# authorization_endpoint: ""
|
||||
# token_endpoint: ""
|
||||
# userinfo_endpoint: ""
|
||||
|
||||
# The client ID for the OIDC client
|
||||
client_id: "your-client-id"
|
||||
|
||||
# The client secret for the OIDC client
|
||||
@ -146,6 +158,9 @@ oidc:
|
||||
# with systemd's `LoadCredential` straightforward:
|
||||
# client_secret_path: "${CREDENTIALS_DIRECTORY}/oidc_client_secret"
|
||||
|
||||
# Defaults to 'openid email profile'
|
||||
# scope: "openid email profile"
|
||||
|
||||
disable_api_key_login: false
|
||||
token_endpoint_auth_method: "client_secret_post"
|
||||
|
||||
@ -166,3 +181,9 @@ oidc:
|
||||
# we go to fetch the userinfo endpoint. Optionally, this can be set to
|
||||
# "oidc" or "gravatar" as of 0.6.1.
|
||||
# profile_picture_source: "gravatar"
|
||||
|
||||
# Extra query parameters can be passed to the authorization endpoint
|
||||
# by setting them here. This is useful for providers that require any kind
|
||||
# of custom hinting.
|
||||
# extra_params:
|
||||
# prompt: "select_account" # Example: force account selection on Google
|
||||
|
||||
@ -28,7 +28,7 @@ in
|
||||
|
||||
pnpmDeps = pnpm_10.fetchDeps {
|
||||
inherit (finalAttrs) pname version src;
|
||||
hash = "sha256-Xi4IZEFFHx2Wp7r1qleGj8BHZOKLWsSfFT4UKbF+9Y8=";
|
||||
hash = "sha256-mNVDgVfGNnBRtUVacVwjyww/XWqFTlayHtv6TLgXTT4=";
|
||||
fetcherVersion = 1;
|
||||
};
|
||||
|
||||
|
||||
@ -44,10 +44,10 @@
|
||||
"dotenv": "17.2.1",
|
||||
"drizzle-orm": "0.44.4",
|
||||
"isbot": "5.1.30",
|
||||
"jose": "6.0.13",
|
||||
"jose": "6.1.0",
|
||||
"lucide-react": "^0.540.0",
|
||||
"mime": "^4.0.7",
|
||||
"openid-client": "6.6.4",
|
||||
"openid-client": "6.7.0",
|
||||
"react": "19.1.1",
|
||||
"react-aria": "3.42.0",
|
||||
"react-codemirror-merge": "4.25.1",
|
||||
|
||||
67
pnpm-lock.yaml
generated
67
pnpm-lock.yaml
generated
@ -98,8 +98,8 @@ importers:
|
||||
specifier: 5.1.30
|
||||
version: 5.1.30
|
||||
jose:
|
||||
specifier: 6.0.13
|
||||
version: 6.0.13
|
||||
specifier: 6.1.0
|
||||
version: 6.1.0
|
||||
lucide-react:
|
||||
specifier: ^0.540.0
|
||||
version: 0.540.0(react@19.1.1)
|
||||
@ -107,8 +107,8 @@ importers:
|
||||
specifier: ^4.0.7
|
||||
version: 4.0.7
|
||||
openid-client:
|
||||
specifier: 6.6.4
|
||||
version: 6.6.4
|
||||
specifier: 6.7.0
|
||||
version: 6.7.0
|
||||
react:
|
||||
specifier: 19.1.1
|
||||
version: 19.1.1
|
||||
@ -138,7 +138,7 @@ importers:
|
||||
version: 3.40.0(react@19.1.1)
|
||||
remix-utils:
|
||||
specifier: ^8.8.0
|
||||
version: 8.8.0(react-router@7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)
|
||||
version: 8.8.0(@oslojs/crypto@1.0.1)(@oslojs/encoding@1.1.0)(react-router@7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)
|
||||
tailwind-merge:
|
||||
specifier: 3.3.1
|
||||
version: 3.3.1
|
||||
@ -1038,6 +1038,18 @@ packages:
|
||||
resolution: {integrity: sha512-gGq0NJkIGSwdbUt4yhdF8ZrmkGKVz9vAdVzpOfnom+V8PLSmSOVhZwbNvZZS1EYcJN5hzzKBxmmVVAInM6HQLg==}
|
||||
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
|
||||
|
||||
'@oslojs/asn1@1.0.0':
|
||||
resolution: {integrity: sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA==}
|
||||
|
||||
'@oslojs/binary@1.0.0':
|
||||
resolution: {integrity: sha512-9RCU6OwXU6p67H4NODbuxv2S3eenuQ4/WFLrsq+K/k682xrznH5EVWA7N4VFk9VYVcbFtKqur5YQQZc0ySGhsQ==}
|
||||
|
||||
'@oslojs/crypto@1.0.1':
|
||||
resolution: {integrity: sha512-7n08G8nWjAr/Yu3vu9zzrd0L9XnrJfpMioQcvCMxBIiF5orECHe5/3J0jmXRVvgfqMm/+4oxlQ+Sq39COYLcNQ==}
|
||||
|
||||
'@oslojs/encoding@1.1.0':
|
||||
resolution: {integrity: sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==}
|
||||
|
||||
'@oxc-project/runtime@0.82.2':
|
||||
resolution: {integrity: sha512-cYxcj5CPn/vo5QSpCZcYzBiLidU5+GlFSqIeNaMgBDtcVRBsBJHZg3pHw999W6nHamFQ1EHuPPByB26tjaJiJw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
@ -2898,8 +2910,8 @@ packages:
|
||||
resolution: {integrity: sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==}
|
||||
hasBin: true
|
||||
|
||||
jose@6.0.13:
|
||||
resolution: {integrity: sha512-Yms4GpbmdANamS51kKK6w4hRlKx8KTxbWyAAKT/MhUMtqbIqh5mb2HjhTNUbk7TFL8/MBB5zWSDohL7ed4k/UA==}
|
||||
jose@6.1.0:
|
||||
resolution: {integrity: sha512-TTQJyoEoKcC1lscpVDCSsVgYzUDg/0Bt3WE//WiTPK6uOCQC2KZS4MpugbMWt/zyjkopgZoXhZuCi00gLudfUA==}
|
||||
|
||||
js-base64@3.7.7:
|
||||
resolution: {integrity: sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==}
|
||||
@ -3232,8 +3244,8 @@ packages:
|
||||
resolution: {integrity: sha512-1dKY+86/AIiq1tkKVD3l0WI+Gd3vkknVGAggsFeBkTvbhMQ1OND/LKkYv4JtXPKUJ8bOTCyLiqEg2P6QNdK+Gg==}
|
||||
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
|
||||
|
||||
oauth4webapi@3.7.0:
|
||||
resolution: {integrity: sha512-Q52wTPUWPsVLVVmTViXPQFMW2h2xv2jnDGxypjpelCFKaOjLsm7AxYuOk1oQgFm95VNDbuggasu9htXrz6XwKw==}
|
||||
oauth4webapi@3.8.0:
|
||||
resolution: {integrity: sha512-RSu64fjsBIs6D7s9g5LOCnOohOkI0nnPtlIp/4rrHj2Vb8jGepq+fujkv2Srw4tuw9qa02aETXQzyQUle8nfnQ==}
|
||||
|
||||
once@1.4.0:
|
||||
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
|
||||
@ -3244,8 +3256,8 @@ packages:
|
||||
oniguruma-to-es@4.3.3:
|
||||
resolution: {integrity: sha512-rPiZhzC3wXwE59YQMRDodUwwT9FZ9nNBwQQfsd1wfdtlKEyCdRV0avrTcSZ5xlIvGRVPd/cx6ZN45ECmS39xvg==}
|
||||
|
||||
openid-client@6.6.4:
|
||||
resolution: {integrity: sha512-PLWVhRksRnNH05sqeuCX/PR+1J70NyZcAcPske+FeF732KKONd3v0p5Utx1ro1iLfCglH8B3/+dA1vqIHDoIiA==}
|
||||
openid-client@6.7.0:
|
||||
resolution: {integrity: sha512-mA8Cex9wGK6Jaqq4vd1sPDK/a+cQ6G+lExVJ9IRjwInN4NcEEntYqLAZsBhZOyHwDx7V1nmPKOvnxOcdLtSpoA==}
|
||||
|
||||
package-json-from-dist@1.0.1:
|
||||
resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
|
||||
@ -4775,7 +4787,7 @@ snapshots:
|
||||
js-yaml: 4.1.0
|
||||
jsonpath-plus: 10.3.0
|
||||
node-fetch: 2.7.0
|
||||
openid-client: 6.6.4
|
||||
openid-client: 6.7.0
|
||||
rfc4648: 1.5.4
|
||||
socks-proxy-agent: 8.0.5
|
||||
stream-buffers: 3.0.3
|
||||
@ -4904,6 +4916,23 @@ snapshots:
|
||||
dependencies:
|
||||
which: 3.0.1
|
||||
|
||||
'@oslojs/asn1@1.0.0':
|
||||
dependencies:
|
||||
'@oslojs/binary': 1.0.0
|
||||
optional: true
|
||||
|
||||
'@oslojs/binary@1.0.0':
|
||||
optional: true
|
||||
|
||||
'@oslojs/crypto@1.0.1':
|
||||
dependencies:
|
||||
'@oslojs/asn1': 1.0.0
|
||||
'@oslojs/binary': 1.0.0
|
||||
optional: true
|
||||
|
||||
'@oslojs/encoding@1.1.0':
|
||||
optional: true
|
||||
|
||||
'@oxc-project/runtime@0.82.2': {}
|
||||
|
||||
'@oxc-project/types@0.82.2': {}
|
||||
@ -7081,7 +7110,7 @@ snapshots:
|
||||
|
||||
jiti@2.5.1: {}
|
||||
|
||||
jose@6.0.13: {}
|
||||
jose@6.1.0: {}
|
||||
|
||||
js-base64@3.7.7: {}
|
||||
|
||||
@ -7362,7 +7391,7 @@ snapshots:
|
||||
npm-package-arg: 10.1.0
|
||||
semver: 7.7.2
|
||||
|
||||
oauth4webapi@3.7.0: {}
|
||||
oauth4webapi@3.8.0: {}
|
||||
|
||||
once@1.4.0:
|
||||
dependencies:
|
||||
@ -7376,10 +7405,10 @@ snapshots:
|
||||
regex: 6.0.1
|
||||
regex-recursion: 6.0.2
|
||||
|
||||
openid-client@6.6.4:
|
||||
openid-client@6.7.0:
|
||||
dependencies:
|
||||
jose: 6.0.13
|
||||
oauth4webapi: 3.7.0
|
||||
jose: 6.1.0
|
||||
oauth4webapi: 3.8.0
|
||||
|
||||
package-json-from-dist@1.0.1: {}
|
||||
|
||||
@ -7666,10 +7695,12 @@ snapshots:
|
||||
dependencies:
|
||||
regex-utilities: 2.3.0
|
||||
|
||||
remix-utils@8.8.0(react-router@7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1):
|
||||
remix-utils@8.8.0(@oslojs/crypto@1.0.1)(@oslojs/encoding@1.1.0)(react-router@7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1):
|
||||
dependencies:
|
||||
type-fest: 4.41.0
|
||||
optionalDependencies:
|
||||
'@oslojs/crypto': 1.0.1
|
||||
'@oslojs/encoding': 1.1.0
|
||||
react: 19.1.1
|
||||
react-router: 7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user