22
33import { Command , Flags } from '@oclif/core' ;
44import { spawn } from 'child_process' ;
5+ import crypto from 'crypto' ;
56import fs from 'fs' ;
67import os from 'os' ;
78import path from 'path' ;
@@ -117,6 +118,19 @@ export default class Start extends Command {
117118 ?? process . env . OS_ENVIRONMENT_ID
118119 ?? 'env_local' ;
119120
121+ // ── Auth secret ─────────────────────────────────────────────────
122+ // Priority: --auth-secret > $AUTH_SECRET > $OS_AUTH_SECRET > persisted
123+ // <home>/auth-secret (auto-generated on first run).
124+ //
125+ // Without this, `serve` runs in production mode and silently skips
126+ // AuthPlugin when no secret is set — which makes /api/v1/auth/*
127+ // return 404 and breaks Studio's login flow. Quick-start should
128+ // "just work" without the user having to export AUTH_SECRET.
129+ const authSecret = flags [ 'auth-secret' ]
130+ ?? process . env . AUTH_SECRET
131+ ?? process . env . OS_AUTH_SECRET
132+ ?? readOrCreateAuthSecret ( homeDir ) ;
133+
120134 // ── Banner ──────────────────────────────────────────────────────
121135 printKV ( 'Home' , homeDir , '🏠' ) ;
122136 if ( artifactSource ) {
@@ -141,7 +155,7 @@ export default class Start extends Command {
141155 ...( flags . port ? { PORT : String ( flags . port ) } : { } ) ,
142156 ...( flags [ 'database-driver' ] ? { OS_DATABASE_DRIVER : flags [ 'database-driver' ] } : { } ) ,
143157 ...( flags [ 'database-auth-token' ] ? { OS_DATABASE_AUTH_TOKEN : flags [ 'database-auth-token' ] } : { } ) ,
144- ... ( flags [ 'auth-secret' ] ? { AUTH_SECRET : flags [ 'auth-secret' ] } : { } ) ,
158+ AUTH_SECRET : authSecret ,
145159 ...( artifactSource ? { OS_ARTIFACT_PATH : artifactSource . path } : { OS_BOOT_EMPTY : '1' } ) ,
146160 } ;
147161 // NODE_ENV is only forced to production when the user has not set it.
@@ -223,3 +237,26 @@ function redactDbUrl(url: string): string {
223237 return url ;
224238 }
225239}
240+
241+ /**
242+ * Read the persisted AUTH_SECRET from `<home>/auth-secret`, or generate
243+ * one on first run and persist it so subsequent restarts keep existing
244+ * sessions valid. Mode 0o600 to keep the secret reasonably private.
245+ */
246+ function readOrCreateAuthSecret ( homeDir : string ) : string {
247+ const secretPath = path . join ( homeDir , 'auth-secret' ) ;
248+ try {
249+ const existing = fs . readFileSync ( secretPath , 'utf8' ) . trim ( ) ;
250+ if ( existing . length >= 32 ) return existing ;
251+ } catch {
252+ // file missing or unreadable — fall through to generation
253+ }
254+ const secret = crypto . randomBytes ( 32 ) . toString ( 'hex' ) ;
255+ try {
256+ fs . mkdirSync ( homeDir , { recursive : true } ) ;
257+ fs . writeFileSync ( secretPath , secret + '\n' , { mode : 0o600 } ) ;
258+ } catch {
259+ // best-effort persist; secret is still returned for this process
260+ }
261+ return secret ;
262+ }
0 commit comments