@maronn-openid-connect/cli 0.4.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +102 -5
- package/dist/features.d.ts +9 -3
- package/dist/features.d.ts.map +1 -1
- package/dist/features.js +28 -2
- package/dist/features.js.map +1 -1
- package/dist/frameworks/express/index.d.ts.map +1 -1
- package/dist/frameworks/express/index.js +1 -1
- package/dist/frameworks/express/index.js.map +1 -1
- package/dist/frameworks/fastify/index.d.ts.map +1 -1
- package/dist/frameworks/fastify/index.js +1 -1
- package/dist/frameworks/fastify/index.js.map +1 -1
- package/dist/frameworks/hono/index.d.ts.map +1 -1
- package/dist/frameworks/hono/index.js +37 -8
- package/dist/frameworks/hono/index.js.map +1 -1
- package/dist/frameworks/hono/pages.d.ts +10 -0
- package/dist/frameworks/hono/pages.d.ts.map +1 -0
- package/dist/frameworks/hono/pages.js +806 -0
- package/dist/frameworks/hono/pages.js.map +1 -0
- package/dist/frameworks/hono/templates.d.ts +21 -9
- package/dist/frameworks/hono/templates.d.ts.map +1 -1
- package/dist/frameworks/hono/templates.js +13311 -8856
- package/dist/frameworks/hono/templates.js.map +1 -1
- package/dist/frameworks/nextjs/index.d.ts.map +1 -1
- package/dist/frameworks/nextjs/index.js +1 -1
- package/dist/frameworks/nextjs/index.js.map +1 -1
- package/dist/frameworks/types.d.ts +1 -0
- package/dist/frameworks/types.d.ts.map +1 -1
- package/dist/frameworks/web-standard/templates.d.ts +6 -6
- package/dist/frameworks/web-standard/templates.d.ts.map +1 -1
- package/dist/frameworks/web-standard/templates.js +332 -56
- package/dist/frameworks/web-standard/templates.js.map +1 -1
- package/dist/generator.d.ts +1 -0
- package/dist/generator.d.ts.map +1 -1
- package/dist/generator.js +2 -2
- package/dist/generator.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +130 -20
- package/dist/index.js.map +1 -1
- package/dist/scopes.d.ts +5 -0
- package/dist/scopes.d.ts.map +1 -0
- package/dist/scopes.js +38 -0
- package/dist/scopes.js.map +1 -0
- package/package.json +4 -3
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { DEFAULT_FEATURES } from '../../features.js';
|
|
2
|
-
import { authorizationCodeConformanceHelper, authorizeRouteTemplate, configTemplate, conformanceTestClientsBlock, consentDecisionConformanceBlock, consentWithdrawalConformanceBlock, consentRouteTemplate, customViewConformanceTestBlock, deviceAuthorizationConformanceBlock, deviceAuthorizationRouteTemplate, deviceVerificationRouteTemplate, discoveryRouteTemplate, endpointBehaviorConformanceBlock, featureDisabledDiscoveryConformanceTests, idTokenHintConformanceBlock, internalRedirectOriginConformanceBlock, introspectionConformanceBlock, introspectionRouteTemplate, jwksRouteTemplate, loginRouteTemplate, parRouteTemplate, parConformanceBlock, jarmConformanceBlock, jarmConfigTemplate, tokenExchangeConformanceBlock, idJagConformanceBlock, pkceDisabledConformanceBlock, persistentStorageConformanceBlock, requestObjectConformanceBeforeAll, requestObjectConformanceModuleSetup, resolversTemplate, onlineRefreshTokenConformanceBlock, onlineRefreshTokenConformanceStoreImport, reuseFlowConformanceTestBlock, revocationDisabledConformanceBlock, revocationRouteTemplate, scopesSupportedConformanceTest, storeTemplate, tokenEndpointAuthMethodsConformanceBlock, tokenRouteTemplate, transactionBindingConformanceBlock, userinfoRouteTemplate, viewsTemplate, } from '../hono/templates.js';
|
|
2
|
+
import { authorizationCodeConformanceHelper, cibaConformanceBlock, backchannelAuthenticationRouteTemplate, cibaVerificationRouteTemplate, authorizeRouteTemplate, configTemplate, conformanceTestClientsBlock, consentDecisionConformanceBlock, consentWithdrawalConformanceBlock, consentRouteTemplate, customScopeConformanceBlock, customScopesTemplate, customViewConformanceTestBlock, deviceAuthorizationConformanceBlock, deviceAuthorizationRouteTemplate, deviceVerificationRouteTemplate, endSessionRouteTemplate, rpInitiatedLogoutConformanceBlock, discoveryRouteTemplate, endpointBehaviorConformanceBlock, featureDisabledDiscoveryConformanceTests, idTokenHintConformanceBlock, internalRedirectOriginConformanceBlock, introspectionConformanceBlock, introspectionRouteTemplate, jwksRouteTemplate, loginRouteTemplate, parRouteTemplate, parConformanceBlock, jarmConformanceBlock, jarmConfigTemplate, jwtIntrospectionResponseConformanceBlock, googleLoginConformanceBlock, googleLoginConformanceImportsBlock, GOOGLE_LOGIN_PACKAGE, tokenExchangeConformanceBlock, idJagConformanceBlock, pkceDisabledConformanceBlock, persistentStorageConformanceBlock, requestObjectConformanceBeforeAll, requestObjectConformanceModuleSetup, resolversTemplate, onlineRefreshTokenConformanceBlock, onlineRefreshTokenConformanceStoreImport, reuseFlowConformanceTestBlock, revocationDisabledConformanceBlock, revocationRouteTemplate, scopesSupportedConformanceTest, storeTemplate, tokenEndpointAuthMethodsConformanceBlock, tokenRouteTemplate, transactionBindingConformanceBlock, userinfoRouteTemplate, viewsTemplate, } from '../hono/templates.js';
|
|
3
|
+
import { authorizePageTemplate, cibaPageTemplate, consentPageTemplate, devicePageTemplate, errorPageTemplate, loginPageTemplate, logoutPageTemplate, respondTemplate, } from '../hono/pages.js';
|
|
3
4
|
function toWebRouteTemplate(content) {
|
|
4
5
|
return content
|
|
5
6
|
.replace("import { Hono } from 'hono';", "import { WebRouter } from '../web-router.js';")
|
|
@@ -379,14 +380,14 @@ export function webAppTemplate(corePkg, features = DEFAULT_FEATURES) {
|
|
|
379
380
|
: '';
|
|
380
381
|
const deviceImport = features.deviceAuthorizationGrant
|
|
381
382
|
? `import { deviceAuthorizationApp } from './routes/device-authorization.js';
|
|
382
|
-
import {
|
|
383
|
+
import { devicePage } from './pages/device.js';\n`
|
|
383
384
|
: '';
|
|
384
385
|
const deviceCors = features.deviceAuthorizationGrant
|
|
385
386
|
? ` app.use('/device_authorization', protectedCors);\n`
|
|
386
387
|
: '';
|
|
387
388
|
const deviceMount = features.deviceAuthorizationGrant
|
|
388
389
|
? ` app.route('/device_authorization', deviceAuthorizationApp);
|
|
389
|
-
app.route('/device',
|
|
390
|
+
app.route('/device', devicePage);\n`
|
|
390
391
|
: '';
|
|
391
392
|
const deviceStorageContext = features.deviceAuthorizationGrant
|
|
392
393
|
? ` c.set('deviceAuthorizationStore', deviceAuthorizationStore);\n`
|
|
@@ -394,6 +395,78 @@ import { deviceApp } from './routes/device.js';\n`
|
|
|
394
395
|
const deviceStoreImport = features.deviceAuthorizationGrant
|
|
395
396
|
? ` deviceAuthorizationStore,\n`
|
|
396
397
|
: '';
|
|
398
|
+
const cibaImport = features.ciba
|
|
399
|
+
? `import { backchannelAuthenticationApp } from './routes/backchannel-authentication.js';
|
|
400
|
+
import { cibaPage } from './pages/ciba.js';\n`
|
|
401
|
+
: '';
|
|
402
|
+
const cibaCors = features.ciba
|
|
403
|
+
? ` app.use('/backchannel_authentication', protectedCors);\n`
|
|
404
|
+
: '';
|
|
405
|
+
const cibaMount = features.ciba
|
|
406
|
+
? ` app.route('/backchannel_authentication', backchannelAuthenticationApp);
|
|
407
|
+
app.route('/ciba', cibaPage);\n`
|
|
408
|
+
: '';
|
|
409
|
+
const cibaStorageContext = features.ciba
|
|
410
|
+
? ` c.set('cibaAuthenticationRequestStore', cibaAuthenticationRequestStore);
|
|
411
|
+
c.set('cibaLoginTransactionStore', cibaLoginTransactionStore);
|
|
412
|
+
c.set('cibaUserResolver', options.cibaUserResolver ?? (async (loginHint: string) => {
|
|
413
|
+
const claims = await stores.userStore.getClaims(loginHint);
|
|
414
|
+
return claims ? { subject: claims.sub } : null;
|
|
415
|
+
}));\n`
|
|
416
|
+
: '';
|
|
417
|
+
const cibaStoreImport = features.ciba
|
|
418
|
+
? ` cibaAuthenticationRequestStore,
|
|
419
|
+
cibaLoginTransactionStore,\n`
|
|
420
|
+
: '';
|
|
421
|
+
const cibaOptionsField = features.ciba
|
|
422
|
+
? ` /**
|
|
423
|
+
* EXPERIMENTAL (CIBA Core 1.0 §7.1): resolve a login_hint to the subject the
|
|
424
|
+
* authentication request is for. Defaults to treating the hint as a username
|
|
425
|
+
* of the configured user store. Return null when no user matches.
|
|
426
|
+
*/
|
|
427
|
+
cibaUserResolver?: (
|
|
428
|
+
loginHint: string,
|
|
429
|
+
) => Promise<{ subject: string } | null> | { subject: string } | null;
|
|
430
|
+
`
|
|
431
|
+
: '';
|
|
432
|
+
const logoutImport = features.rpInitiatedLogout
|
|
433
|
+
? `import { logoutPage } from './pages/logout.js';\n`
|
|
434
|
+
: '';
|
|
435
|
+
const logoutMount = features.rpInitiatedLogout
|
|
436
|
+
? ` app.route('/logout', logoutPage);\n`
|
|
437
|
+
: '';
|
|
438
|
+
const googleLoginImport = features.googleLogin
|
|
439
|
+
? `import {
|
|
440
|
+
getDefaultGoogleIdTokenVerifier,
|
|
441
|
+
type GoogleAccountResolver,
|
|
442
|
+
type GoogleIdTokenPayload,
|
|
443
|
+
type GoogleIdTokenVerifier,
|
|
444
|
+
} from '${GOOGLE_LOGIN_PACKAGE}';\n`
|
|
445
|
+
: '';
|
|
446
|
+
const googleLoginStorageContext = features.googleLogin
|
|
447
|
+
? ` c.set('googleLoginNonceStore', stores.googleLoginNonceStore);
|
|
448
|
+
c.set('googleIdTokenVerifier', options.googleIdTokenVerifier ?? getDefaultGoogleIdTokenVerifier());
|
|
449
|
+
c.set('googleAccountResolver', options.googleAccountResolver ?? {
|
|
450
|
+
resolveSubject: async (account: GoogleIdTokenPayload) =>
|
|
451
|
+
(await stores.userStore.linkGoogleAccount(account)).sub,
|
|
452
|
+
});\n`
|
|
453
|
+
: '';
|
|
454
|
+
const googleLoginOptionsFields = features.googleLogin
|
|
455
|
+
? ` /**
|
|
456
|
+
* EXTENSION (google-login): verifier for the ID token Google posts to
|
|
457
|
+
* /login/google. Defaults to google-auth-library (OAuth2Client.verifyIdToken)
|
|
458
|
+
* with a process-wide certificate cache; inject a custom one for tests or a
|
|
459
|
+
* proxied environment.
|
|
460
|
+
*/
|
|
461
|
+
googleIdTokenVerifier?: GoogleIdTokenVerifier;
|
|
462
|
+
/**
|
|
463
|
+
* EXTENSION (google-login): map a verified Google account to the OP subject.
|
|
464
|
+
* Defaults to just-in-time provisioning through the user store
|
|
465
|
+
* (userStore.linkGoogleAccount), keyed by the Google \`sub\`.
|
|
466
|
+
*/
|
|
467
|
+
googleAccountResolver?: GoogleAccountResolver;
|
|
468
|
+
`
|
|
469
|
+
: '';
|
|
397
470
|
const refreshStorageContext = features.refreshToken
|
|
398
471
|
? ` c.set('refreshTokenResolver', storeResolvers.refreshTokenResolver);
|
|
399
472
|
c.set('authenticationSessionResolver', storeResolvers.authenticationSessionResolver);\n`
|
|
@@ -406,13 +479,13 @@ import { deviceApp } from './routes/device.js';\n`
|
|
|
406
479
|
? ` c.set('revocationResolvers', storeResolvers.revocationResolvers);\n`
|
|
407
480
|
: '';
|
|
408
481
|
return `import { WebRouter, type WebMiddleware } from './web-router.js';
|
|
409
|
-
import {
|
|
482
|
+
import { authorizePage } from './pages/authorize.js';
|
|
410
483
|
import { tokenApp } from './routes/token.js';
|
|
411
484
|
import { userinfoApp } from './routes/userinfo.js';
|
|
412
|
-
${introspectionImport}${revocationImport}${parImport}${deviceImport}import { jwksApp } from './routes/jwks.js';
|
|
485
|
+
${introspectionImport}${revocationImport}${parImport}${deviceImport}${cibaImport}${logoutImport}import { jwksApp } from './routes/jwks.js';
|
|
413
486
|
import { discoveryApp } from './routes/discovery.js';
|
|
414
|
-
import {
|
|
415
|
-
import {
|
|
487
|
+
import { loginPage } from './pages/login.js';
|
|
488
|
+
import { consentPage } from './pages/consent.js';
|
|
416
489
|
import {
|
|
417
490
|
createInMemoryClientResolver,
|
|
418
491
|
createProviderConfig,
|
|
@@ -423,10 +496,10 @@ import {
|
|
|
423
496
|
} from './resolvers.js';
|
|
424
497
|
import {
|
|
425
498
|
defaultProviderStores,
|
|
426
|
-
${parStoreImport}${deviceStoreImport} type ProviderStores,
|
|
499
|
+
${parStoreImport}${deviceStoreImport}${cibaStoreImport} type ProviderStores,
|
|
427
500
|
} from './store.js';
|
|
428
501
|
import { createViews, type Views } from './views.js';
|
|
429
|
-
import {
|
|
502
|
+
${googleLoginImport}import {
|
|
430
503
|
assertHasRs256Key,
|
|
431
504
|
assertKeyStrength,
|
|
432
505
|
assertKidStrategyConsistent,
|
|
@@ -459,7 +532,7 @@ export interface OidcProviderOptions {
|
|
|
459
532
|
storage?: ProviderStores;
|
|
460
533
|
acrResolver?: AcrResolver;
|
|
461
534
|
jwksProvider?: () => Promise<JwkSet> | JwkSet;
|
|
462
|
-
corsOrigins?: CorsOrigins;
|
|
535
|
+
${cibaOptionsField}${googleLoginOptionsFields} corsOrigins?: CorsOrigins;
|
|
463
536
|
/**
|
|
464
537
|
* Custom UI for the login / consent / error pages.
|
|
465
538
|
* Provide any subset; omitted pages fall back to the default views.
|
|
@@ -497,7 +570,7 @@ export function createApp(options: OidcProviderOptions): WebRouter {
|
|
|
497
570
|
});
|
|
498
571
|
app.use('/token', protectedCors);
|
|
499
572
|
app.use('/userinfo', protectedCors);
|
|
500
|
-
${introspectionCors}${revocationCors}${parCors}${deviceCors} app.use('/.well-known/openid-configuration', publicCors);
|
|
573
|
+
${introspectionCors}${revocationCors}${parCors}${deviceCors}${cibaCors} app.use('/.well-known/openid-configuration', publicCors);
|
|
501
574
|
app.use('/.well-known/jwks.json', publicCors);
|
|
502
575
|
|
|
503
576
|
app.use('*', async (c, next) => {
|
|
@@ -555,7 +628,7 @@ ${introspectionCors}${revocationCors}${parCors}${deviceCors} app.use('/.well-kn
|
|
|
555
628
|
c.set('authCodeResolver', storeResolvers.authorizationCodeResolver);
|
|
556
629
|
c.set('accessTokenResolver', storeResolvers.accessTokenResolver);
|
|
557
630
|
c.set('userClaimsResolver', storeResolvers.userClaimsResolver);
|
|
558
|
-
${refreshStorageContext}${introspectionStorageContext}${revocationStorageContext}${parStorageContext}${deviceStorageContext}
|
|
631
|
+
${refreshStorageContext}${introspectionStorageContext}${revocationStorageContext}${parStorageContext}${deviceStorageContext}${cibaStorageContext}${googleLoginStorageContext}
|
|
559
632
|
if (options.acrResolver) {
|
|
560
633
|
c.set('acrResolver', options.acrResolver);
|
|
561
634
|
}
|
|
@@ -570,13 +643,15 @@ ${refreshStorageContext}${introspectionStorageContext}${revocationStorageContext
|
|
|
570
643
|
await next();
|
|
571
644
|
});
|
|
572
645
|
|
|
573
|
-
|
|
646
|
+
// Browser-facing surfaces are mounted from pages/: every GET and POST of a
|
|
647
|
+
// screen lives there, and the logic they call is in routes/.
|
|
648
|
+
app.route('/authorize', authorizePage);
|
|
574
649
|
app.route('/token', tokenApp);
|
|
575
650
|
app.route('/userinfo', userinfoApp);
|
|
576
|
-
${introspectionMount}${revocationMount}${parMount}${deviceMount} app.route('/.well-known/jwks.json', jwksApp);
|
|
651
|
+
${introspectionMount}${revocationMount}${parMount}${deviceMount}${cibaMount}${logoutMount} app.route('/.well-known/jwks.json', jwksApp);
|
|
577
652
|
app.route('/.well-known/openid-configuration', discoveryApp);
|
|
578
|
-
app.route('/login',
|
|
579
|
-
app.route('/consent',
|
|
653
|
+
app.route('/login', loginPage);
|
|
654
|
+
app.route('/consent', consentPage);
|
|
580
655
|
|
|
581
656
|
return app;
|
|
582
657
|
}
|
|
@@ -629,6 +704,10 @@ export function expressApplyTemplate(features = DEFAULT_FEATURES) {
|
|
|
629
704
|
? ` '/device_authorization',
|
|
630
705
|
'/device',\n`
|
|
631
706
|
: '';
|
|
707
|
+
const cibaEndpoints = features.ciba
|
|
708
|
+
? ` '/backchannel_authentication',
|
|
709
|
+
'/ciba',\n`
|
|
710
|
+
: '';
|
|
632
711
|
return `import type { Express } from 'express';
|
|
633
712
|
import type { Request, Response, NextFunction } from 'express';
|
|
634
713
|
import { createApp, type OidcProviderOptions } from './app.js';
|
|
@@ -640,7 +719,7 @@ const OIDC_ENDPOINTS = [
|
|
|
640
719
|
'/authorize',
|
|
641
720
|
'/token',
|
|
642
721
|
'/userinfo',
|
|
643
|
-
${introspectionEndpoint}${revocationEndpoint}${parEndpoint}${deviceEndpoints} '/.well-known/jwks.json',
|
|
722
|
+
${introspectionEndpoint}${revocationEndpoint}${parEndpoint}${deviceEndpoints}${cibaEndpoints} '/.well-known/jwks.json',
|
|
644
723
|
'/.well-known/openid-configuration',
|
|
645
724
|
'/login',
|
|
646
725
|
'/consent',
|
|
@@ -682,6 +761,15 @@ export function fastifyApplyTemplate(features = DEFAULT_FEATURES) {
|
|
|
682
761
|
app.route({ method: ['POST'], url: '/device/login', handler: handle });
|
|
683
762
|
app.route({ method: ['POST'], url: '/device/approve', handler: handle });\n`
|
|
684
763
|
: '';
|
|
764
|
+
const cibaRoutes = features.ciba
|
|
765
|
+
? ` app.route({ method: ['POST', 'OPTIONS'], url: '/backchannel_authentication', handler: handle });
|
|
766
|
+
app.route({ method: ['GET'], url: '/ciba', handler: handle });
|
|
767
|
+
app.route({ method: ['POST'], url: '/ciba/login', handler: handle });
|
|
768
|
+
app.route({ method: ['POST'], url: '/ciba/approve', handler: handle });\n`
|
|
769
|
+
: '';
|
|
770
|
+
const googleLoginRoute = features.googleLogin
|
|
771
|
+
? ` app.route({ method: ['POST'], url: '/login/google', handler: handle });\n`
|
|
772
|
+
: '';
|
|
685
773
|
return `import type { FastifyInstance } from 'fastify';
|
|
686
774
|
import type { FastifyReply, FastifyRequest } from 'fastify';
|
|
687
775
|
import { createApp, type OidcProviderOptions } from './app.js';
|
|
@@ -720,10 +808,10 @@ export async function applyOidc(app: FastifyInstance, options: ApplyOidcOptions)
|
|
|
720
808
|
app.route({ method: ['GET', 'POST', 'OPTIONS'], url: '/authorize', handler: handle });
|
|
721
809
|
app.route({ method: ['POST', 'OPTIONS'], url: '/token', handler: handle });
|
|
722
810
|
app.route({ method: ['GET', 'POST', 'OPTIONS'], url: '/userinfo', handler: handle });
|
|
723
|
-
${introspectionRoute}${revocationRoute}${parRoute}${deviceRoutes} app.route({ method: ['GET', 'OPTIONS'], url: '/.well-known/jwks.json', handler: handle });
|
|
811
|
+
${introspectionRoute}${revocationRoute}${parRoute}${deviceRoutes}${cibaRoutes} app.route({ method: ['GET', 'OPTIONS'], url: '/.well-known/jwks.json', handler: handle });
|
|
724
812
|
app.route({ method: ['GET', 'OPTIONS'], url: '/.well-known/openid-configuration', handler: handle });
|
|
725
813
|
app.route({ method: ['GET', 'POST'], url: '/login', handler: handle });
|
|
726
|
-
app.route({ method: ['GET', 'POST'], url: '/consent', handler: handle });
|
|
814
|
+
${googleLoginRoute} app.route({ method: ['GET', 'POST'], url: '/consent', handler: handle });
|
|
727
815
|
}
|
|
728
816
|
|
|
729
817
|
async function toFastifyReply(reply: FastifyReply, response: Response): Promise<void> {
|
|
@@ -979,14 +1067,33 @@ function readEnv(name: string): string | undefined {
|
|
|
979
1067
|
}
|
|
980
1068
|
`;
|
|
981
1069
|
}
|
|
982
|
-
export function nextJsRuntimeTemplate(corePkg) {
|
|
1070
|
+
export function nextJsRuntimeTemplate(corePkg, features = DEFAULT_FEATURES) {
|
|
1071
|
+
const googleLoginConfigTypeImport = features.googleLogin ? 'type GoogleLoginConfig, ' : '';
|
|
1072
|
+
const googleLoginRuntimeConfig = features.googleLogin
|
|
1073
|
+
? `
|
|
1074
|
+
// EXTENSION (google-login): set GOOGLE_CLIENT_ID (the OAuth client ID from
|
|
1075
|
+
// the Google Cloud console) to render the "Sign in with Google" button.
|
|
1076
|
+
// Register <issuer>/login/google as an authorized redirect URI there.
|
|
1077
|
+
// GOOGLE_HOSTED_DOMAIN optionally restricts sign-in to one Workspace domain.
|
|
1078
|
+
googleLogin: readGoogleLoginConfig(),`
|
|
1079
|
+
: '';
|
|
1080
|
+
const googleLoginRuntimeReader = features.googleLogin
|
|
1081
|
+
? `
|
|
1082
|
+
function readGoogleLoginConfig(): GoogleLoginConfig | undefined {
|
|
1083
|
+
const clientId = readEnv('GOOGLE_CLIENT_ID');
|
|
1084
|
+
if (!clientId) return undefined;
|
|
1085
|
+
const hostedDomain = readEnv('GOOGLE_HOSTED_DOMAIN');
|
|
1086
|
+
return hostedDomain ? { clientId, hostedDomain } : { clientId };
|
|
1087
|
+
}
|
|
1088
|
+
`
|
|
1089
|
+
: '';
|
|
983
1090
|
return `import {
|
|
984
1091
|
createCachedSigningKeyProvider,
|
|
985
1092
|
type AcrResolver,
|
|
986
1093
|
type SigningKey,
|
|
987
1094
|
type SigningKeyProvider,
|
|
988
1095
|
} from '${corePkg}';
|
|
989
|
-
import { createInMemoryClientResolver, type RegisteredClient } from './config';
|
|
1096
|
+
import { createInMemoryClientResolver, ${googleLoginConfigTypeImport}type RegisteredClient } from './config';
|
|
990
1097
|
import { createOidcRouteHandlers } from './next';
|
|
991
1098
|
import { createNextJsProviderStores } from './storage-backend';
|
|
992
1099
|
import type { OidcProviderOptions } from './app';
|
|
@@ -1039,7 +1146,7 @@ export function createOidcProviderOptions(): OidcProviderOptions {
|
|
|
1039
1146
|
// /oidc-error, which renders them via the App Router error boundary
|
|
1040
1147
|
// (app/oidc-error/error.tsx) — consistent with login/consent being real
|
|
1041
1148
|
// pages rather than HTML strings from the route handler.
|
|
1042
|
-
authorizationErrorRedirectPath: '/oidc-error'
|
|
1149
|
+
authorizationErrorRedirectPath: '/oidc-error',${googleLoginRuntimeConfig}
|
|
1043
1150
|
},
|
|
1044
1151
|
signingKeyProvider,
|
|
1045
1152
|
clientResolver,
|
|
@@ -1120,7 +1227,7 @@ function readEnv(name: string): string | undefined {
|
|
|
1120
1227
|
if (typeof process === 'undefined') return undefined;
|
|
1121
1228
|
return process.env[name];
|
|
1122
1229
|
}
|
|
1123
|
-
|
|
1230
|
+
${googleLoginRuntimeReader}
|
|
1124
1231
|
function createEphemeralRs256KeyProvider(): SigningKeyProvider {
|
|
1125
1232
|
const keyPromise = generateSigningKey();
|
|
1126
1233
|
return {
|
|
@@ -1222,13 +1329,70 @@ async function isBoundToThisBrowser(
|
|
|
1222
1329
|
}
|
|
1223
1330
|
`
|
|
1224
1331
|
: '';
|
|
1332
|
+
const googleLoginImports = features.googleLogin
|
|
1333
|
+
? `
|
|
1334
|
+
import Script from 'next/script';
|
|
1335
|
+
import { issueGoogleLoginNonce } from '${GOOGLE_LOGIN_PACKAGE}';
|
|
1336
|
+
import {
|
|
1337
|
+
buildGoogleSignInAttributes,
|
|
1338
|
+
GOOGLE_GSI_CLIENT_SCRIPT_URL,
|
|
1339
|
+
} from '${GOOGLE_LOGIN_PACKAGE}/sign-in';`
|
|
1340
|
+
: '';
|
|
1341
|
+
const loginPageStores = features.googleLogin
|
|
1342
|
+
? `const { transactionStore, googleLoginNonceStore } =
|
|
1343
|
+
oidcProviderOptions.storage ?? defaultProviderStores;`
|
|
1344
|
+
: `const transactionStore =
|
|
1345
|
+
(oidcProviderOptions.storage ?? defaultProviderStores).transactionStore;`;
|
|
1346
|
+
const googleSignInRender = features.googleLogin
|
|
1347
|
+
? `
|
|
1348
|
+
// EXTENSION (google-login): the GIS configuration (g_id_onload attributes),
|
|
1349
|
+
// built only when config.googleLogin is set. Each render issues a fresh nonce
|
|
1350
|
+
// bound to this transaction; Google echoes it in the ID token, which is how
|
|
1351
|
+
// login/google/route.ts finds the transaction. The JSX below owns the markup.
|
|
1352
|
+
const googleLogin = oidcProviderOptions.config?.googleLogin;
|
|
1353
|
+
const googleSignIn = googleLogin
|
|
1354
|
+
? buildGoogleSignInAttributes({
|
|
1355
|
+
clientId: googleLogin.clientId,
|
|
1356
|
+
// Must equal an authorized redirect URI of the Google OAuth client.
|
|
1357
|
+
loginUri: new URL(
|
|
1358
|
+
'/login/google',
|
|
1359
|
+
oidcProviderOptions.config?.issuer ?? 'http://localhost:3000',
|
|
1360
|
+
).toString(),
|
|
1361
|
+
nonce: await issueGoogleLoginNonce({
|
|
1362
|
+
transactionId,
|
|
1363
|
+
expiresAt: transaction.expiresAt,
|
|
1364
|
+
store: googleLoginNonceStore,
|
|
1365
|
+
}),
|
|
1366
|
+
loginHint: transaction.loginHint,
|
|
1367
|
+
hostedDomain:
|
|
1368
|
+
typeof googleLogin.hostedDomain === 'string' ? googleLogin.hostedDomain : undefined,
|
|
1369
|
+
})
|
|
1370
|
+
: undefined;
|
|
1371
|
+
`
|
|
1372
|
+
: '';
|
|
1373
|
+
const googleSignInJsx = features.googleLogin
|
|
1374
|
+
? `
|
|
1375
|
+
{/*
|
|
1376
|
+
EXTENSION (google-login): the three elements GIS needs for redirect mode.
|
|
1377
|
+
googleSignIn holds the g_id_onload attributes (data-ux_mode="redirect",
|
|
1378
|
+
data-login_uri, data-nonce, ...) and spreads straight onto the element;
|
|
1379
|
+
GIS replaces .g_id_signin with the button — style it through the GIS
|
|
1380
|
+
button attributes (data-theme, data-size, data-text, ...).
|
|
1381
|
+
*/}
|
|
1382
|
+
{googleSignIn ? (
|
|
1383
|
+
<section aria-label="Sign in with Google">
|
|
1384
|
+
<Script src={GOOGLE_GSI_CLIENT_SCRIPT_URL} strategy="afterInteractive" />
|
|
1385
|
+
<div {...googleSignIn} />
|
|
1386
|
+
<div className="g_id_signin" data-type="standard" />
|
|
1387
|
+
</section>
|
|
1388
|
+
) : null}`
|
|
1389
|
+
: '';
|
|
1225
1390
|
return `${bindingImports}
|
|
1226
1391
|
import { oidcProviderOptions } from '../_oidc-provider/runtime';
|
|
1227
|
-
${bindingStoreImport}
|
|
1392
|
+
${bindingStoreImport}${googleLoginImports}
|
|
1228
1393
|
import { loginAction } from './actions';
|
|
1229
1394
|
|
|
1230
|
-
|
|
1231
|
-
(oidcProviderOptions.storage ?? defaultProviderStores).transactionStore;
|
|
1395
|
+
${loginPageStores}
|
|
1232
1396
|
|
|
1233
1397
|
// Authorization redirects here with a per-request transaction_id, so the page
|
|
1234
1398
|
// must always render dynamically (never statically cached).
|
|
@@ -1273,7 +1437,7 @@ export default async function LoginPage({ searchParams }: LoginPageProps) {
|
|
|
1273
1437
|
}
|
|
1274
1438
|
|
|
1275
1439
|
const transaction = await getAuthTransaction(transactionId, transactionStore);
|
|
1276
|
-
${bindingCheck}
|
|
1440
|
+
${bindingCheck}${googleSignInRender}
|
|
1277
1441
|
const errorMessage =
|
|
1278
1442
|
error === 'invalid_credentials'
|
|
1279
1443
|
? \`Invalid credentials\${remaining ? \`. Attempts remaining: \${remaining}\` : ''}\`
|
|
@@ -1299,7 +1463,7 @@ ${bindingCheck}
|
|
|
1299
1463
|
<input type="password" id="password" name="password" required />
|
|
1300
1464
|
</div>
|
|
1301
1465
|
<button type="submit">Login</button>
|
|
1302
|
-
</form
|
|
1466
|
+
</form>${googleSignInJsx}
|
|
1303
1467
|
</main>
|
|
1304
1468
|
);
|
|
1305
1469
|
}
|
|
@@ -1481,7 +1645,28 @@ ${bindingCheck} validateCsrfToken(transaction, csrfToken);
|
|
|
1481
1645
|
}
|
|
1482
1646
|
`;
|
|
1483
1647
|
}
|
|
1484
|
-
export function nextJsConsentPageTemplate(corePkg, features = DEFAULT_FEATURES) {
|
|
1648
|
+
export function nextJsConsentPageTemplate(corePkg, features = DEFAULT_FEATURES, scopes = []) {
|
|
1649
|
+
const customScopesDeclared = scopes.length > 0;
|
|
1650
|
+
const customScopeImport = customScopesDeclared
|
|
1651
|
+
? `
|
|
1652
|
+
import { resolveGrantableScopes } from '../_oidc-provider/scopes';`
|
|
1653
|
+
: '';
|
|
1654
|
+
const consentPageScopes = customScopesDeclared
|
|
1655
|
+
? ` // The subject comes from the auth session that the login step stored for this
|
|
1656
|
+
// transaction; without one there is nothing to apply the scope policy to, so
|
|
1657
|
+
// the request is shown as-is and the Server Action stops on the same missing
|
|
1658
|
+
// session.
|
|
1659
|
+
const consentSession = await authSessionStore.get(transactionId);
|
|
1660
|
+
const requestedScopes = transaction.scope.split(' ').filter(Boolean);
|
|
1661
|
+
const scopes = consentSession
|
|
1662
|
+
? await resolveGrantableScopes(requestedScopes, consentSession.subject)
|
|
1663
|
+
: requestedScopes;`
|
|
1664
|
+
: ` const scopes = transaction.scope.split(' ').filter(Boolean);`;
|
|
1665
|
+
const consentPageStores = customScopesDeclared
|
|
1666
|
+
? `
|
|
1667
|
+
const authSessionStore =
|
|
1668
|
+
(oidcProviderOptions.storage ?? defaultProviderStores).authSessionStore;`
|
|
1669
|
+
: '';
|
|
1485
1670
|
const bindingImports = features.transactionBinding
|
|
1486
1671
|
? `import { cookies } from 'next/headers';
|
|
1487
1672
|
import { getAuthTransaction, validateTransactionBinding } from '${corePkg}';`
|
|
@@ -1534,10 +1719,10 @@ async function isBoundToThisBrowser(
|
|
|
1534
1719
|
return `${bindingImports}
|
|
1535
1720
|
import { oidcProviderOptions } from '../_oidc-provider/runtime';
|
|
1536
1721
|
${bindingStoreImport}
|
|
1537
|
-
import { consentAction } from './actions'
|
|
1722
|
+
import { consentAction } from './actions';${customScopeImport}
|
|
1538
1723
|
|
|
1539
1724
|
const transactionStore =
|
|
1540
|
-
(oidcProviderOptions.storage ?? defaultProviderStores).transactionStore
|
|
1725
|
+
(oidcProviderOptions.storage ?? defaultProviderStores).transactionStore;${consentPageStores}
|
|
1541
1726
|
|
|
1542
1727
|
export const dynamic = 'force-dynamic';
|
|
1543
1728
|
|
|
@@ -1564,7 +1749,7 @@ export default async function ConsentPage({ searchParams }: ConsentPageProps) {
|
|
|
1564
1749
|
}
|
|
1565
1750
|
|
|
1566
1751
|
const transaction = await getAuthTransaction(transactionId, transactionStore);
|
|
1567
|
-
${bindingCheck}
|
|
1752
|
+
${bindingCheck}${consentPageScopes}
|
|
1568
1753
|
|
|
1569
1754
|
return (
|
|
1570
1755
|
<main>
|
|
@@ -1600,7 +1785,23 @@ ${bindingCheck} const scopes = transaction.scope.split(' ').filter(Boolean);
|
|
|
1600
1785
|
}
|
|
1601
1786
|
`;
|
|
1602
1787
|
}
|
|
1603
|
-
export function nextJsConsentActionTemplate(corePkg, features = DEFAULT_FEATURES) {
|
|
1788
|
+
export function nextJsConsentActionTemplate(corePkg, features = DEFAULT_FEATURES, scopes = []) {
|
|
1789
|
+
const customScopesDeclared = scopes.length > 0;
|
|
1790
|
+
const customScopeImport = customScopesDeclared
|
|
1791
|
+
? `
|
|
1792
|
+
import { resolveGrantableScopes } from '../_oidc-provider/scopes';`
|
|
1793
|
+
: '';
|
|
1794
|
+
const consentGrantedScope = customScopesDeclared
|
|
1795
|
+
? `
|
|
1796
|
+
// Apply the scope policy (resolveGrantableScopes in _oidc-provider/scopes.ts —
|
|
1797
|
+
// the place to write per-user filtering). A dropped scope narrows the grant
|
|
1798
|
+
// rather than failing the request: RFC 6749 §3.3 lets the authorization server
|
|
1799
|
+
// issue a narrower scope, and the token response reports what was granted.
|
|
1800
|
+
const grantedScope = await resolveGrantableScopes(
|
|
1801
|
+
transaction.scope.split(' ').filter(Boolean),
|
|
1802
|
+
session.subject,
|
|
1803
|
+
);`
|
|
1804
|
+
: ` const grantedScope = transaction.scope.split(' ').filter(Boolean);`;
|
|
1604
1805
|
const bindingCookiesImport = features.transactionBinding
|
|
1605
1806
|
? `
|
|
1606
1807
|
import { cookies } from 'next/headers';`
|
|
@@ -1653,7 +1854,7 @@ import {
|
|
|
1653
1854
|
} from '${corePkg}';
|
|
1654
1855
|
import { oidcProviderOptions } from '../_oidc-provider/runtime';
|
|
1655
1856
|
import { createStoreResolvers } from '../_oidc-provider/resolvers';
|
|
1656
|
-
${bindingStoreImport}
|
|
1857
|
+
${bindingStoreImport}${customScopeImport}
|
|
1657
1858
|
|
|
1658
1859
|
const providerStores = oidcProviderOptions.storage ?? defaultProviderStores;
|
|
1659
1860
|
const { transactionStore, authCodeStore, authSessionStore } = providerStores;
|
|
@@ -1722,7 +1923,7 @@ ${clearBindingCookie} redirect(denyUrl.toString());
|
|
|
1722
1923
|
// transaction.scope は認可リクエスト検証時に applyOfflineAccessPolicy を通した後の値。
|
|
1723
1924
|
// offline_access の可否(OIDC Core 1.0 §11 の prompt=consent と、クライアント登録
|
|
1724
1925
|
// grant_types に refresh_token があるか)はそこで確定しているので再フィルタしない。
|
|
1725
|
-
|
|
1926
|
+
${consentGrantedScope}
|
|
1726
1927
|
|
|
1727
1928
|
// OIDC Core 1.0 Section 3.1.3.1: TTL is configurable via ProviderConfig.
|
|
1728
1929
|
const authCodeData = await createAuthorizationCode({
|
|
@@ -1756,8 +1957,12 @@ ${clearBindingCookieOnSuccess} const successUrl = new URL(responseParams.redire
|
|
|
1756
1957
|
}
|
|
1757
1958
|
`;
|
|
1758
1959
|
}
|
|
1759
|
-
export function webConformanceTestTemplate(corePkg, errorPageMode = 'html', features = DEFAULT_FEATURES, includeNodeAdapterContract = false, jarmConsentResponseMode = 'jwt') {
|
|
1960
|
+
export function webConformanceTestTemplate(corePkg, errorPageMode = 'html', features = DEFAULT_FEATURES, includeNodeAdapterContract = false, jarmConsentResponseMode = 'jwt', scopes = []) {
|
|
1760
1961
|
const usesRedirect = errorPageMode === 'redirect';
|
|
1962
|
+
const customScopeConformanceImport = scopes.length > 0
|
|
1963
|
+
? `
|
|
1964
|
+
import { RESTRICTED_SCOPE_SUBJECTS } from './scopes.js';`
|
|
1965
|
+
: '';
|
|
1761
1966
|
const createAppConfig = usesRedirect
|
|
1762
1967
|
? `
|
|
1763
1968
|
config: { authorizationErrorRedirectPath: '/oidc-error' },`
|
|
@@ -1885,14 +2090,27 @@ import { tokenExchangeConfig } from './routes/token.js';`
|
|
|
1885
2090
|
? `
|
|
1886
2091
|
import { idJagConfig } from './routes/token.js';`
|
|
1887
2092
|
: '';
|
|
1888
|
-
|
|
2093
|
+
const rpInitiatedLogoutConformanceImports = features.rpInitiatedLogout
|
|
2094
|
+
? `
|
|
2095
|
+
import { rpInitiatedLogoutConfig } from './routes/logout.js';`
|
|
2096
|
+
: '';
|
|
2097
|
+
const cibaConformanceImports = features.ciba
|
|
2098
|
+
? `
|
|
2099
|
+
import { cibaAuthenticationRequestStore } from './store.js';`
|
|
2100
|
+
: '';
|
|
2101
|
+
const vitestNames = features.ciba
|
|
2102
|
+
? 'describe, it, expect, beforeAll, afterEach'
|
|
2103
|
+
: 'describe, it, expect, beforeAll';
|
|
2104
|
+
const googleLoginConformanceImports = googleLoginConformanceImportsBlock(features);
|
|
2105
|
+
const googleLoginConfigTypeImport = features.googleLogin ? ', type GoogleLoginConfig' : '';
|
|
2106
|
+
return `import { ${vitestNames} } from 'vitest';
|
|
1889
2107
|
import type { SigningKeyProvider, SigningKey } from '${corePkg}';
|
|
1890
2108
|
${exportPublicJwkImport}import { createApp, validateSigningKeySet } from './app.js';
|
|
1891
|
-
import { createInMemoryClientResolver, type RegisteredClient } from './config.js';
|
|
2109
|
+
import { createInMemoryClientResolver, type RegisteredClient${googleLoginConfigTypeImport} } from './config.js';
|
|
1892
2110
|
import { accessTokenStore, authSessionStore, consentStore, createJsonProviderStores,${onlineRefreshTokenConformanceStoreImport(features)} refreshTokenStore, transactionStore, type JsonStoreBackend } from './store.js';
|
|
1893
2111
|
import { consentResolver } from './resolvers.js';
|
|
1894
2112
|
import { defaultViews } from './views.js';
|
|
1895
|
-
import { renderView } from './views.js';${parConformanceImports}${tokenExchangeConformanceImports}${idJagConformanceImports}
|
|
2113
|
+
import { renderView } from './views.js';${parConformanceImports}${tokenExchangeConformanceImports}${idJagConformanceImports}${cibaConformanceImports}${rpInitiatedLogoutConformanceImports}${googleLoginConformanceImports}${customScopeConformanceImport}
|
|
1896
2114
|
${nodeAdapterImport}
|
|
1897
2115
|
|
|
1898
2116
|
const REDIRECT_URI = 'http://localhost:3000/callback';
|
|
@@ -2068,7 +2286,7 @@ ${responseModesSupportedExpectation}
|
|
|
2068
2286
|
});
|
|
2069
2287
|
});
|
|
2070
2288
|
|
|
2071
|
-
${scopesSupportedConformanceTest(features)}
|
|
2289
|
+
${scopesSupportedConformanceTest(features, scopes)}
|
|
2072
2290
|
// OIDC Core 1.0 §2 / §3.1.3.6 + Discovery 1.0 §3: claims_supported advertises
|
|
2073
2291
|
// the claims the OP can supply, including the ID Token protocol claims
|
|
2074
2292
|
// (auth_time/nonce/acr/amr/azp/at_hash). The full list is pinned so dropping
|
|
@@ -2303,15 +2521,18 @@ ${nonRedirectErrorTest}
|
|
|
2303
2521
|
});
|
|
2304
2522
|
});
|
|
2305
2523
|
});
|
|
2306
|
-
${transactionBindingConformanceBlock(features)}${customViewConformanceTestBlock()}${internalRedirectOriginConformanceBlock()}${endpointBehaviorConformanceBlock(features)}${idTokenHintConformanceBlock()}${consentWithdrawalConformanceBlock(features)}${reuseFlowConformanceTestBlock(features)}${onlineRefreshTokenConformanceBlock(features)}${revocationDisabledConformanceBlock(features)}${tokenEndpointAuthMethodsConformanceBlock()}${pkceDisabledConformanceBlock(features)}${parConformanceBlock(features)}${tokenExchangeConformanceBlock(features)}${idJagConformanceBlock(features)}${deviceAuthorizationConformanceBlock(features)}${jarmConformanceBlock(features, jarmConsentResponseMode)}${consentDecisionConformanceBlock()}});
|
|
2524
|
+
${transactionBindingConformanceBlock(features)}${customViewConformanceTestBlock()}${internalRedirectOriginConformanceBlock()}${endpointBehaviorConformanceBlock(features)}${idTokenHintConformanceBlock()}${consentWithdrawalConformanceBlock(features)}${reuseFlowConformanceTestBlock(features, errorPageMode)}${onlineRefreshTokenConformanceBlock(features)}${revocationDisabledConformanceBlock(features)}${tokenEndpointAuthMethodsConformanceBlock()}${pkceDisabledConformanceBlock(features)}${parConformanceBlock(features)}${tokenExchangeConformanceBlock(features)}${idJagConformanceBlock(features)}${deviceAuthorizationConformanceBlock(features)}${cibaConformanceBlock(features)}${jarmConformanceBlock(features, jarmConsentResponseMode)}${jwtIntrospectionResponseConformanceBlock(features)}${rpInitiatedLogoutConformanceBlock(features)}${googleLoginConformanceBlock(features)}${consentDecisionConformanceBlock()}${customScopeConformanceBlock(scopes)}});
|
|
2307
2525
|
`;
|
|
2308
2526
|
}
|
|
2309
|
-
function webCoreGeneratedFiles(corePkg, errorPageMode = 'html', features = DEFAULT_FEATURES, includeNodeAdapterContract = false, jarmConsentResponseMode = 'jwt') {
|
|
2527
|
+
function webCoreGeneratedFiles(corePkg, errorPageMode = 'html', features = DEFAULT_FEATURES, includeNodeAdapterContract = false, jarmConsentResponseMode = 'jwt', scopes = []) {
|
|
2310
2528
|
const consentFeatures = jarmConsentResponseMode === 'plain' ? { ...features, jarm: false } : features;
|
|
2311
2529
|
return [
|
|
2312
2530
|
{ path: 'app.ts', content: webAppTemplate(corePkg, features) },
|
|
2313
2531
|
{ path: 'web-router.ts', content: webRouterTemplate() },
|
|
2314
2532
|
{ path: 'config.ts', content: configTemplate(corePkg, features) },
|
|
2533
|
+
...(scopes.length > 0
|
|
2534
|
+
? [{ path: 'scopes.ts', content: customScopesTemplate(scopes, features) }]
|
|
2535
|
+
: []),
|
|
2315
2536
|
{
|
|
2316
2537
|
path: 'store.ts',
|
|
2317
2538
|
content: storeTemplate(corePkg, features),
|
|
@@ -2321,11 +2542,23 @@ function webCoreGeneratedFiles(corePkg, errorPageMode = 'html', features = DEFAU
|
|
|
2321
2542
|
content: resolversTemplate(corePkg, features).replace('through Hono context', 'through the generated request context'),
|
|
2322
2543
|
},
|
|
2323
2544
|
{ path: 'views.ts', content: viewsTemplate(features) },
|
|
2324
|
-
{ path: '
|
|
2545
|
+
{ path: 'pages/respond.ts', content: respondTemplate() },
|
|
2546
|
+
{ path: 'pages/errors.ts', content: errorPageTemplate() },
|
|
2547
|
+
{ path: 'pages/authorize.ts', content: toWebRouteTemplate(authorizePageTemplate()) },
|
|
2548
|
+
{ path: 'pages/login.ts', content: toWebRouteTemplate(loginPageTemplate(features)) },
|
|
2549
|
+
{ path: 'pages/consent.ts', content: toWebRouteTemplate(consentPageTemplate()) },
|
|
2550
|
+
...(features.deviceAuthorizationGrant
|
|
2551
|
+
? [{ path: 'pages/device.ts', content: toWebRouteTemplate(devicePageTemplate()) }]
|
|
2552
|
+
: []),
|
|
2553
|
+
...(features.ciba ? [{ path: 'pages/ciba.ts', content: toWebRouteTemplate(cibaPageTemplate()) }] : []),
|
|
2554
|
+
...(features.rpInitiatedLogout
|
|
2555
|
+
? [{ path: 'pages/logout.ts', content: toWebRouteTemplate(logoutPageTemplate()) }]
|
|
2556
|
+
: []),
|
|
2557
|
+
{ path: 'routes/authorize.ts', content: toWebRouteTemplate(authorizeRouteTemplate(corePkg, features, scopes)) },
|
|
2325
2558
|
{ path: 'routes/token.ts', content: toWebRouteTemplate(tokenRouteTemplate(corePkg, features)) },
|
|
2326
2559
|
{ path: 'routes/userinfo.ts', content: toWebRouteTemplate(userinfoRouteTemplate(corePkg)) },
|
|
2327
2560
|
...(features.introspection
|
|
2328
|
-
? [{ path: 'routes/introspection.ts', content: toWebRouteTemplate(introspectionRouteTemplate(corePkg)) }]
|
|
2561
|
+
? [{ path: 'routes/introspection.ts', content: toWebRouteTemplate(introspectionRouteTemplate(corePkg, features)) }]
|
|
2329
2562
|
: []),
|
|
2330
2563
|
...(features.revocation
|
|
2331
2564
|
? [{ path: 'routes/revocation.ts', content: toWebRouteTemplate(revocationRouteTemplate(corePkg)) }]
|
|
@@ -2337,39 +2570,54 @@ function webCoreGeneratedFiles(corePkg, errorPageMode = 'html', features = DEFAU
|
|
|
2337
2570
|
? [
|
|
2338
2571
|
{
|
|
2339
2572
|
path: 'routes/device-authorization.ts',
|
|
2340
|
-
content: toWebRouteTemplate(deviceAuthorizationRouteTemplate(corePkg, features)),
|
|
2573
|
+
content: toWebRouteTemplate(deviceAuthorizationRouteTemplate(corePkg, features, scopes)),
|
|
2341
2574
|
},
|
|
2342
2575
|
{
|
|
2343
2576
|
path: 'routes/device.ts',
|
|
2344
|
-
content: toWebRouteTemplate(deviceVerificationRouteTemplate(corePkg)),
|
|
2577
|
+
content: toWebRouteTemplate(deviceVerificationRouteTemplate(corePkg, scopes)),
|
|
2578
|
+
},
|
|
2579
|
+
]
|
|
2580
|
+
: []),
|
|
2581
|
+
...(features.ciba
|
|
2582
|
+
? [
|
|
2583
|
+
{
|
|
2584
|
+
path: 'routes/backchannel-authentication.ts',
|
|
2585
|
+
content: toWebRouteTemplate(backchannelAuthenticationRouteTemplate(corePkg, features, scopes)),
|
|
2586
|
+
},
|
|
2587
|
+
{
|
|
2588
|
+
path: 'routes/ciba-verification.ts',
|
|
2589
|
+
content: toWebRouteTemplate(cibaVerificationRouteTemplate(corePkg, scopes)),
|
|
2345
2590
|
},
|
|
2346
2591
|
]
|
|
2347
2592
|
: []),
|
|
2593
|
+
...(features.rpInitiatedLogout
|
|
2594
|
+
? [{ path: 'routes/logout.ts', content: toWebRouteTemplate(endSessionRouteTemplate(corePkg)) }]
|
|
2595
|
+
: []),
|
|
2348
2596
|
...(features.jarm
|
|
2349
2597
|
? [{ path: 'routes/jarm.ts', content: jarmConfigTemplate() }]
|
|
2350
2598
|
: []),
|
|
2351
2599
|
{ path: 'routes/jwks.ts', content: toWebRouteTemplate(jwksRouteTemplate(corePkg)) },
|
|
2352
|
-
{ path: 'routes/discovery.ts', content: toWebRouteTemplate(discoveryRouteTemplate(corePkg, features)) },
|
|
2600
|
+
{ path: 'routes/discovery.ts', content: toWebRouteTemplate(discoveryRouteTemplate(corePkg, features, scopes)) },
|
|
2353
2601
|
{ path: 'routes/login.ts', content: toWebRouteTemplate(loginRouteTemplate(corePkg, features)) },
|
|
2354
|
-
{ path: 'routes/consent.ts', content: toWebRouteTemplate(consentRouteTemplate(corePkg, consentFeatures)) },
|
|
2602
|
+
{ path: 'routes/consent.ts', content: toWebRouteTemplate(consentRouteTemplate(corePkg, consentFeatures, scopes)) },
|
|
2355
2603
|
{
|
|
2356
2604
|
path: 'conformance.test.ts',
|
|
2357
|
-
content: webConformanceTestTemplate(corePkg, errorPageMode, features, includeNodeAdapterContract, jarmConsentResponseMode),
|
|
2605
|
+
content: webConformanceTestTemplate(corePkg, errorPageMode, features, includeNodeAdapterContract, jarmConsentResponseMode, scopes),
|
|
2358
2606
|
},
|
|
2359
2607
|
];
|
|
2360
2608
|
}
|
|
2361
2609
|
function toNextJsModuleImports(content) {
|
|
2362
2610
|
return content.replaceAll(/(from\s+['"](?:\.{1,2}\/[^'"]+))\.js(['"])/g, '$1$2');
|
|
2363
2611
|
}
|
|
2364
|
-
export function webGeneratedFiles(corePkg, applyTemplate, features = DEFAULT_FEATURES) {
|
|
2612
|
+
export function webGeneratedFiles(corePkg, applyTemplate, features = DEFAULT_FEATURES, scopes = []) {
|
|
2365
2613
|
return [
|
|
2366
|
-
...webCoreGeneratedFiles(corePkg, 'html', features, true),
|
|
2614
|
+
...webCoreGeneratedFiles(corePkg, 'html', features, true, 'jwt', scopes),
|
|
2367
2615
|
{ path: 'apply.ts', content: applyTemplate },
|
|
2368
2616
|
{ path: 'node-adapter.ts', content: nodeAdapterTemplate() },
|
|
2369
2617
|
];
|
|
2370
2618
|
}
|
|
2371
|
-
export function nextJsGeneratedFiles(corePkg, features = DEFAULT_FEATURES) {
|
|
2372
|
-
const internalFiles = webCoreGeneratedFiles(corePkg, 'redirect', features, false, 'plain').map((file) => ({
|
|
2619
|
+
export function nextJsGeneratedFiles(corePkg, features = DEFAULT_FEATURES, scopes = []) {
|
|
2620
|
+
const internalFiles = webCoreGeneratedFiles(corePkg, 'redirect', features, false, 'plain', scopes).map((file) => ({
|
|
2373
2621
|
path: `_oidc-provider/${file.path}`,
|
|
2374
2622
|
content: toNextJsModuleImports(file.content),
|
|
2375
2623
|
}));
|
|
@@ -2377,7 +2625,7 @@ export function nextJsGeneratedFiles(corePkg, features = DEFAULT_FEATURES) {
|
|
|
2377
2625
|
...internalFiles,
|
|
2378
2626
|
{ path: '_oidc-provider/next.ts', content: nextJsRouteHandlerTemplate() },
|
|
2379
2627
|
{ path: '_oidc-provider/storage-backend.ts', content: nextJsStorageBackendTemplate() },
|
|
2380
|
-
{ path: '_oidc-provider/runtime.ts', content: nextJsRuntimeTemplate(corePkg) },
|
|
2628
|
+
{ path: '_oidc-provider/runtime.ts', content: nextJsRuntimeTemplate(corePkg, features) },
|
|
2381
2629
|
{
|
|
2382
2630
|
path: 'authorize/route.ts',
|
|
2383
2631
|
content: nextJsEndpointRouteTemplate('../_oidc-provider/runtime', [
|
|
@@ -2442,6 +2690,26 @@ export function nextJsGeneratedFiles(corePkg, features = DEFAULT_FEATURES) {
|
|
|
2442
2690
|
},
|
|
2443
2691
|
]
|
|
2444
2692
|
: []),
|
|
2693
|
+
...(features.ciba
|
|
2694
|
+
? [
|
|
2695
|
+
{
|
|
2696
|
+
path: 'backchannel_authentication/route.ts',
|
|
2697
|
+
content: nextJsEndpointRouteTemplate('../_oidc-provider/runtime', ['POST', 'OPTIONS']),
|
|
2698
|
+
},
|
|
2699
|
+
{
|
|
2700
|
+
path: 'ciba/route.ts',
|
|
2701
|
+
content: nextJsEndpointRouteTemplate('../_oidc-provider/runtime', ['GET']),
|
|
2702
|
+
},
|
|
2703
|
+
{
|
|
2704
|
+
path: 'ciba/login/route.ts',
|
|
2705
|
+
content: nextJsEndpointRouteTemplate('../../_oidc-provider/runtime', ['POST']),
|
|
2706
|
+
},
|
|
2707
|
+
{
|
|
2708
|
+
path: 'ciba/approve/route.ts',
|
|
2709
|
+
content: nextJsEndpointRouteTemplate('../../_oidc-provider/runtime', ['POST']),
|
|
2710
|
+
},
|
|
2711
|
+
]
|
|
2712
|
+
: []),
|
|
2445
2713
|
{
|
|
2446
2714
|
path: '.well-known/jwks.json/route.ts',
|
|
2447
2715
|
content: nextJsEndpointRouteTemplate('../../_oidc-provider/runtime', [
|
|
@@ -2458,8 +2726,16 @@ export function nextJsGeneratedFiles(corePkg, features = DEFAULT_FEATURES) {
|
|
|
2458
2726
|
},
|
|
2459
2727
|
{ path: 'login/page.tsx', content: nextJsLoginPageTemplate(corePkg, features) },
|
|
2460
2728
|
{ path: 'login/actions.ts', content: nextJsLoginActionTemplate(corePkg, features) },
|
|
2461
|
-
|
|
2462
|
-
|
|
2729
|
+
...(features.googleLogin
|
|
2730
|
+
? [
|
|
2731
|
+
{
|
|
2732
|
+
path: 'login/google/route.ts',
|
|
2733
|
+
content: nextJsEndpointRouteTemplate('../../_oidc-provider/runtime', ['POST']),
|
|
2734
|
+
},
|
|
2735
|
+
]
|
|
2736
|
+
: []),
|
|
2737
|
+
{ path: 'consent/page.tsx', content: nextJsConsentPageTemplate(corePkg, features, scopes) },
|
|
2738
|
+
{ path: 'consent/actions.ts', content: nextJsConsentActionTemplate(corePkg, features, scopes) },
|
|
2463
2739
|
{ path: 'oidc-error/page.tsx', content: nextJsAuthorizationErrorPageTemplate() },
|
|
2464
2740
|
{ path: 'oidc-error/error.tsx', content: nextJsAuthorizationErrorBoundaryTemplate() },
|
|
2465
2741
|
];
|