Skip to content

Commit 841cb30

Browse files
committed
feat: auto-generate and persist AUTH_SECRET in objectstack start
1 parent 7d0802b commit 841cb30

1 file changed

Lines changed: 38 additions & 1 deletion

File tree

packages/cli/src/commands/start.ts

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import { Command, Flags } from '@oclif/core';
44
import { spawn } from 'child_process';
5+
import crypto from 'crypto';
56
import fs from 'fs';
67
import os from 'os';
78
import 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

Comments
 (0)