55
66import NextAuth from 'next-auth' ;
77import type { OIDCConfig } from 'next-auth/providers' ;
8+ import Credentials from 'next-auth/providers/credentials' ;
9+ import { z } from 'zod' ;
810
911import { runtimeConfig } from '#contexts/App/runtime-config.ts' ;
1012import { routes } from '#utils/router.ts' ;
1113
12- import type { ProviderConfig , ProviderWithId } from './types' ;
13- import { providerConfigSchema } from './types' ;
14+ import type { LocalDevUser , ProviderConfig , ProviderWithId } from './types' ;
15+ import { localDevUserSchema , providerConfigSchema } from './types' ;
1416import { getTokenRefreshSchedule , jwtWithRefresh , RefreshTokenError } from './utils' ;
1517
1618export const AUTH_COOKIE_NAME = 'adk-auth-token' ;
1719
18- const { isAuthEnabled } = runtimeConfig ;
20+ const { isAuthEnabled, isLocalDevAutoLogin } = runtimeConfig ;
21+
22+ const oidcConfigSchema = z . object ( {
23+ issuer : z . string ( ) ,
24+ clientId : z . string ( ) ,
25+ clientSecret : z . string ( ) ,
26+ } ) ;
27+
28+ function createLocalDevCredentialsProvider ( config : z . infer < typeof oidcConfigSchema > ) {
29+ return Credentials ( {
30+ id : 'local-dev' ,
31+ name : 'Local Dev' ,
32+ credentials : {
33+ username : { label : 'Username' , type : 'text' } ,
34+ password : { label : 'Password' , type : 'password' } ,
35+ } ,
36+ authorize : async ( credentials ) => {
37+ const { username, password } = z
38+ . object ( {
39+ username : z . string ( ) ,
40+ password : z . string ( ) ,
41+ } )
42+ . parse ( credentials ) ;
43+
44+ const tokenEndpoint = `${ config . issuer . replace ( / \/ $ / , '' ) } /protocol/openid-connect/token` ;
45+
46+ console . info ( `[local-dev] fetching token from ${ tokenEndpoint } for user "${ username } "` ) ;
47+ const response = await fetch ( tokenEndpoint , {
48+ method : 'POST' ,
49+ headers : { 'Content-Type' : 'application/x-www-form-urlencoded' } ,
50+ body : new URLSearchParams ( {
51+ grant_type : 'password' ,
52+ client_id : config . clientId ,
53+ client_secret : config . clientSecret ,
54+ username,
55+ password,
56+ scope : 'openid email profile' ,
57+ } ) ,
58+ } ) ;
59+
60+ if ( ! response . ok ) {
61+ const body = await response . text ( ) ;
62+ console . error ( `[local-dev] token request failed: ${ response . status } ${ response . statusText } — ${ body } ` ) ;
63+ return null ;
64+ }
65+
66+ const tokens = await response . json ( ) ;
67+ console . info ( `[local-dev] token request succeeded, expires_in=${ tokens . expires_in } ` ) ;
68+ return {
69+ id : username ,
70+ name : username ,
71+ access_token : tokens . access_token ,
72+ refresh_token : tokens . refresh_token ,
73+ expires_in : tokens . expires_in ,
74+ expires_at : Math . floor ( Date . now ( ) / 1000 + tokens . expires_in ) ,
75+ } satisfies LocalDevUser ;
76+ } ,
77+ } ) ;
78+ }
1979
2080function createOIDCProvider ( config : ProviderConfig ) : OIDCConfig < unknown > {
2181 const useInternalBackChannel = config . external_issuer && config . external_issuer !== config . issuer ;
@@ -86,13 +146,33 @@ export function getProvider(): ProviderWithId | null {
86146 }
87147}
88148
149+ function assembleProviders ( oidcProvider : ProviderWithId | null ) {
150+ if ( isLocalDevAutoLogin ) {
151+ return [
152+ createLocalDevCredentialsProvider (
153+ oidcConfigSchema . parse ( {
154+ issuer : process . env . OIDC_PROVIDER_ISSUER ,
155+ clientId : process . env . OIDC_PROVIDER_CLIENT_ID ,
156+ clientSecret : process . env . OIDC_PROVIDER_CLIENT_SECRET ,
157+ } ) ,
158+ ) ,
159+ ] ;
160+ }
161+
162+ if ( oidcProvider ) {
163+ return [ oidcProvider ] ;
164+ }
165+
166+ return [ ] ;
167+ }
168+
89169const provider = getProvider ( ) ;
90170
91171// Prevents nextauth errors when authentication is disabled and NEXTAUTH_SECRET is not provided
92172export const AUTH_SECRET = isAuthEnabled ? process . env . NEXTAUTH_SECRET : 'dummy_secret' ;
93173
94174export const { handlers, signIn, signOut, auth } = NextAuth ( {
95- providers : provider ? [ provider ] : [ ] ,
175+ providers : assembleProviders ( provider ) ,
96176 pages : {
97177 signIn : routes . signIn ( ) ,
98178 } ,
@@ -113,18 +193,18 @@ export const { handlers, signIn, signOut, auth } = NextAuth({
113193 authorized : ( { auth } ) => {
114194 return isAuthEnabled ? Boolean ( auth ) : true ;
115195 } ,
116- jwt : async ( { token, account, trigger, session } ) => {
196+ jwt : async ( { token, account, user , trigger, session } ) => {
117197 if ( trigger === 'update' ) {
118198 token . name = session . user . name ;
119199 }
120200
121- // pull the id token out of the account on signIn
122201 if ( account ) {
123- token . accessToken = account . access_token ;
202+ const src = account . type === 'credentials' ? localDevUserSchema . parse ( user ) : account ;
203+ token . accessToken = src . access_token ;
124204 token . provider = account . provider ;
125- token . refreshToken = account . refresh_token ;
126- token . expiresIn = account . expires_in ;
127- token . expiresAt = account . expires_at ;
205+ token . refreshToken = src . refresh_token ;
206+ token . expiresIn = src . expires_in ;
207+ token . expiresAt = src . expires_at ;
128208 token . refreshSchedule = getTokenRefreshSchedule ( token . expiresAt ) ;
129209 }
130210
0 commit comments