@@ -10,40 +10,50 @@ const DEFAULT_JWT_EXPIRY_DAYS = 7; // Default 7 days
1010// Cache for JWT secret to avoid multiple file reads
1111let jwtSecretCache : string | null = null ;
1212
13- /**
14- * Sync the in-memory process.env (and jwtSecretCache) with values just
15- * written to the .env file. Keys not in the file are left untouched.
16- *
17- * This is required because Next.js routes read from process.env at request
18- * time, and dotenv is only loaded once at server startup. Without this,
19- * writes via updateAuthCredentials/enabled/etc. are visible on disk but
20- * the running server keeps returning the pre-write values until restart.
21- */
22- export function syncProcessEnvFromFile ( ) : void {
13+ function parseEnvFile ( ) : Record < string , string > {
2314 const envPath = path . join ( process . cwd ( ) , '.env' ) ;
24- if ( ! fs . existsSync ( envPath ) ) return ;
15+ if ( ! fs . existsSync ( envPath ) ) return { } ;
2516
2617 const envContent = fs . readFileSync ( envPath , 'utf8' ) ;
27- const updates : Record < string , string > = { } ;
18+ const values : Record < string , string > = { } ;
2819
2920 for ( const line of envContent . split ( / \r ? \n / ) ) {
3021 const trimmed = line . trim ( ) ;
3122 if ( ! trimmed || trimmed . startsWith ( '#' ) ) continue ;
23+
3224 const eqIdx = trimmed . indexOf ( '=' ) ;
3325 if ( eqIdx === - 1 ) continue ;
26+
3427 const key = trimmed . slice ( 0 , eqIdx ) . trim ( ) ;
3528 let value = trimmed . slice ( eqIdx + 1 ) . trim ( ) ;
36- // Strip a single pair of surrounding double or single quotes
29+
3730 if (
3831 value . length >= 2 &&
3932 ( ( value . startsWith ( '"' ) && value . endsWith ( '"' ) ) ||
4033 ( value . startsWith ( "'" ) && value . endsWith ( "'" ) ) )
4134 ) {
4235 value = value . slice ( 1 , - 1 ) ;
4336 }
44- if ( key ) updates [ key ] = value ;
37+
38+ if ( key ) values [ key ] = value ;
4539 }
4640
41+ return values ;
42+ }
43+
44+ /**
45+ * Sync the in-memory process.env (and jwtSecretCache) with values just
46+ * written to the .env file. Keys not in the file are left untouched.
47+ *
48+ * This is required because Next.js routes read from process.env at request
49+ * time, and dotenv is only loaded once at server startup. Without this,
50+ * writes via updateAuthCredentials/enabled/etc. are visible on disk but
51+ * the running server keeps returning the pre-write values until restart.
52+ */
53+ export function syncProcessEnvFromFile ( ) : void {
54+ const updates = parseEnvFile ( ) ;
55+ if ( Object . keys ( updates ) . length === 0 ) return ;
56+
4757 for ( const [ key , value ] of Object . entries ( updates ) ) {
4858 process . env [ key ] = value ;
4959 }
@@ -71,6 +81,13 @@ export function getJwtSecret(): string {
7181 return jwtSecretCache ;
7282 }
7383
84+ const fileSecret = parseEnvFile ( ) . JWT_SECRET ?. trim ( ) ;
85+ if ( fileSecret ) {
86+ process . env . JWT_SECRET = fileSecret ;
87+ jwtSecretCache = fileSecret ;
88+ return jwtSecretCache ;
89+ }
90+
7491 // No secret in process.env — generate and persist a new one
7592 const newSecret = randomBytes ( 64 ) . toString ( 'hex' ) ;
7693
@@ -150,11 +167,21 @@ export function getAuthConfig(): {
150167 setupCompleted : boolean ;
151168 sessionDurationDays : number ;
152169} {
153- const username = process . env . AUTH_USERNAME ?. trim ( ) ?? null ;
154- const passwordHash = process . env . AUTH_PASSWORD_HASH ?. trim ( ) ?? null ;
155- const enabled = ( process . env . AUTH_ENABLED ?. toLowerCase ( ) ?? '' ) === 'true' ;
156- const setupCompleted = ( process . env . AUTH_SETUP_COMPLETED ?. toLowerCase ( ) ?? '' ) === 'true' ;
157- const parsed = parseInt ( process . env . AUTH_SESSION_DURATION_DAYS ?? '' , 10 ) ;
170+ const fileValues = parseEnvFile ( ) ;
171+
172+ const usernameSource = fileValues . AUTH_USERNAME ?? process . env . AUTH_USERNAME ?? '' ;
173+ const passwordHashSource = fileValues . AUTH_PASSWORD_HASH ?? process . env . AUTH_PASSWORD_HASH ?? '' ;
174+ const enabledSource = fileValues . AUTH_ENABLED ?? process . env . AUTH_ENABLED ?? '' ;
175+ const setupCompletedSource =
176+ fileValues . AUTH_SETUP_COMPLETED ?? process . env . AUTH_SETUP_COMPLETED ?? '' ;
177+ const sessionDurationSource =
178+ fileValues . AUTH_SESSION_DURATION_DAYS ?? process . env . AUTH_SESSION_DURATION_DAYS ?? '' ;
179+
180+ const username = usernameSource . trim ( ) || null ;
181+ const passwordHash = passwordHashSource . trim ( ) || null ;
182+ const enabled = enabledSource . toLowerCase ( ) === 'true' ;
183+ const setupCompleted = setupCompletedSource . toLowerCase ( ) === 'true' ;
184+ const parsed = parseInt ( sessionDurationSource , 10 ) ;
158185 const sessionDurationDays = Number . isNaN ( parsed ) ? DEFAULT_JWT_EXPIRY_DAYS : parsed ;
159186 const hasCredentials = ! ! ( username && passwordHash ) ;
160187
@@ -269,14 +296,14 @@ export function updateAuthEnabled(enabled: boolean): void {
269296 envContent = envContent . replace ( enabledRegex , `AUTH_ENABLED=${ enabled } ` ) ;
270297 } else {
271298 envContent += ( envContent . endsWith ( '\n' ) ? '' : '\n' ) + `AUTH_ENABLED=${ enabled } \n` ;
272-
273- // Refresh in-memory process.env so subsequent getAuthConfig() reads see
274- // the updated AUTH_ENABLED flag without a restart.
275- syncProcessEnvFromFile ( ) ;
276299 }
277300
278301 // Write back to .env file
279302 fs . writeFileSync ( envPath , envContent ) ;
303+
304+ // Refresh in-memory process.env so subsequent getAuthConfig() reads see
305+ // the updated AUTH_ENABLED flag without a restart.
306+ syncProcessEnvFromFile ( ) ;
280307}
281308
282309/**
@@ -300,12 +327,12 @@ export function updateSessionDuration(days: number): void {
300327 envContent = envContent . replace ( sessionDurationRegex , `AUTH_SESSION_DURATION_DAYS=${ validDays } ` ) ;
301328 } else {
302329 envContent += ( envContent . endsWith ( '\n' ) ? '' : '\n' ) + `AUTH_SESSION_DURATION_DAYS=${ validDays } \n` ;
303-
304- // Refresh in-memory process.env so subsequent getAuthConfig() reads see
305- // the updated session duration without a restart.
306- syncProcessEnvFromFile ( ) ;
307330 }
308331
309332 // Write back to .env file
310333 fs . writeFileSync ( envPath , envContent ) ;
334+
335+ // Refresh in-memory process.env so subsequent getAuthConfig() reads see
336+ // the updated session duration without a restart.
337+ syncProcessEnvFromFile ( ) ;
311338}
0 commit comments