Nyk commited on
Commit
2b182b0
·
1 Parent(s): cbd7797

fix: support AUTH_PASS_B64 for seeded admin password

Browse files
.env.example CHANGED
@@ -2,6 +2,9 @@
2
  # Admin user seeded on first run (only if no users exist in DB)
3
  AUTH_USER=admin
4
  AUTH_PASS=change-me-on-first-login
 
 
 
5
 
6
  # API key for headless/external access (x-api-key header)
7
  API_KEY=generate-a-random-key
 
2
  # Admin user seeded on first run (only if no users exist in DB)
3
  AUTH_USER=admin
4
  AUTH_PASS=change-me-on-first-login
5
+ # If your password includes "#" and you do not want to quote AUTH_PASS, use base64:
6
+ # AUTH_PASS_B64=Y2hhbmdlLW1lLW9uLWZpcnN0LWxvZ2lu
7
+ # Example: echo -n 'my#password' | base64
8
 
9
  # API key for headless/external access (x-api-key header)
10
  API_KEY=generate-a-random-key
README.md CHANGED
@@ -44,6 +44,7 @@ pnpm dev # http://localhost:3000
44
  ```
45
 
46
  Initial login is seeded from `AUTH_USER` / `AUTH_PASS` on first run.
 
47
 
48
  ## Project Status
49
 
@@ -339,6 +340,7 @@ See [`.env.example`](.env.example) for the complete list. Key variables:
339
  |----------|----------|-------------|
340
  | `AUTH_USER` | No | Initial admin username (default: `admin`) |
341
  | `AUTH_PASS` | No | Initial admin password |
 
342
  | `API_KEY` | No | API key for headless access |
343
  | `OPENCLAW_HOME` | Yes* | Path to `.openclaw` directory |
344
  | `OPENCLAW_GATEWAY_HOST` | No | Gateway host (default: `127.0.0.1`) |
 
44
  ```
45
 
46
  Initial login is seeded from `AUTH_USER` / `AUTH_PASS` on first run.
47
+ If `AUTH_PASS` contains `#`, quote it (e.g. `AUTH_PASS="my#password"`) or use `AUTH_PASS_B64`.
48
 
49
  ## Project Status
50
 
 
340
  |----------|----------|-------------|
341
  | `AUTH_USER` | No | Initial admin username (default: `admin`) |
342
  | `AUTH_PASS` | No | Initial admin password |
343
+ | `AUTH_PASS_B64` | No | Base64-encoded admin password (overrides `AUTH_PASS` if set) |
344
  | `API_KEY` | No | API key for headless access |
345
  | `OPENCLAW_HOME` | Yes* | Path to `.openclaw` directory |
346
  | `OPENCLAW_GATEWAY_HOST` | No | Gateway host (default: `127.0.0.1`) |
docs/deployment.md CHANGED
@@ -83,6 +83,7 @@ See `.env.example` for the full list. Key variables:
83
  |----------|----------|---------|-------------|
84
  | `AUTH_USER` | Yes | `admin` | Admin username (seeded on first run) |
85
  | `AUTH_PASS` | Yes | - | Admin password |
 
86
  | `API_KEY` | Yes | - | API key for headless access |
87
  | `PORT` | No | `3005` (direct) / `3000` (Docker) | Server port |
88
  | `OPENCLAW_HOME` | No | - | Path to OpenClaw installation |
@@ -99,6 +100,14 @@ rm -rf node_modules
99
  pnpm install
100
  ```
101
 
 
 
 
 
 
 
 
 
102
  ### "pnpm-lock.yaml not found" during Docker build
103
 
104
  If your deployment context omits `pnpm-lock.yaml`, Docker build now falls back to
 
83
  |----------|----------|---------|-------------|
84
  | `AUTH_USER` | Yes | `admin` | Admin username (seeded on first run) |
85
  | `AUTH_PASS` | Yes | - | Admin password |
86
+ | `AUTH_PASS_B64` | No | - | Base64-encoded admin password (overrides `AUTH_PASS` if set) |
87
  | `API_KEY` | Yes | - | API key for headless access |
88
  | `PORT` | No | `3005` (direct) / `3000` (Docker) | Server port |
89
  | `OPENCLAW_HOME` | No | - | Path to OpenClaw installation |
 
100
  pnpm install
101
  ```
102
 
103
+ ### AUTH_PASS with "#" is not working
104
+
105
+ In dotenv files, `#` starts a comment unless the value is quoted.
106
+
107
+ Use one of these:
108
+ - `AUTH_PASS="my#password"`
109
+ - `AUTH_PASS_B64=$(echo -n 'my#password' | base64)`
110
+
111
  ### "pnpm-lock.yaml not found" during Docker build
112
 
113
  If your deployment context omits `pnpm-lock.yaml`, Docker build now falls back to
src/lib/__tests__/db-seed-auth-pass.test.ts ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { describe, expect, it } from 'vitest'
2
+ import { resolveSeedAuthPassword } from '../db'
3
+
4
+ describe('resolveSeedAuthPassword', () => {
5
+ it('returns AUTH_PASS when AUTH_PASS_B64 is not set', () => {
6
+ const password = resolveSeedAuthPassword({ AUTH_PASS: 'plain-secret-123' } as unknown as NodeJS.ProcessEnv)
7
+ expect(password).toBe('plain-secret-123')
8
+ })
9
+
10
+ it('prefers AUTH_PASS_B64 when present and valid', () => {
11
+ const encoded = Buffer.from('secret#with#hash', 'utf8').toString('base64')
12
+ const password = resolveSeedAuthPassword({
13
+ AUTH_PASS: 'fallback-value',
14
+ AUTH_PASS_B64: encoded,
15
+ } as unknown as NodeJS.ProcessEnv)
16
+ expect(password).toBe('secret#with#hash')
17
+ })
18
+
19
+ it('falls back to AUTH_PASS when AUTH_PASS_B64 is invalid', () => {
20
+ const password = resolveSeedAuthPassword({
21
+ AUTH_PASS: 'fallback-value',
22
+ AUTH_PASS_B64: '%%%not-base64%%%',
23
+ } as unknown as NodeJS.ProcessEnv)
24
+ expect(password).toBe('fallback-value')
25
+ })
26
+
27
+ it('returns null when no password env var is set', () => {
28
+ const password = resolveSeedAuthPassword({} as unknown as NodeJS.ProcessEnv)
29
+ expect(password).toBeNull()
30
+ })
31
+ })
src/lib/db.ts CHANGED
@@ -83,6 +83,33 @@ const INSECURE_PASSWORDS = new Set([
83
  'testpass123',
84
  ])
85
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
  function seedAdminUserFromEnv(dbConn: Database.Database): void {
87
  // Skip seeding during `next build` — env vars may not be available yet
88
  if (process.env.NEXT_PHASE === 'phase-production-build') return
@@ -91,12 +118,12 @@ function seedAdminUserFromEnv(dbConn: Database.Database): void {
91
  if (count > 0) return
92
 
93
  const username = process.env.AUTH_USER || 'admin'
94
- const password = process.env.AUTH_PASS
95
 
96
  if (!password) {
97
  logger.warn(
98
  'AUTH_PASS is not set — skipping admin user seeding. ' +
99
- 'Set AUTH_PASS in your .env file to create the initial admin account.'
100
  )
101
  return
102
  }
 
83
  'testpass123',
84
  ])
85
 
86
+ export function resolveSeedAuthPassword(env: NodeJS.ProcessEnv = process.env): string | null {
87
+ const b64 = env.AUTH_PASS_B64
88
+ if (b64 && b64.trim().length > 0) {
89
+ const normalized = b64.trim()
90
+ const base64Pattern = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/
91
+ if (!base64Pattern.test(normalized)) {
92
+ logger.warn('AUTH_PASS_B64 is not valid base64; falling back to AUTH_PASS')
93
+ return env.AUTH_PASS || null
94
+ }
95
+
96
+ try {
97
+ const decoded = Buffer.from(normalized, 'base64').toString('utf8')
98
+ const canonical = Buffer.from(decoded, 'utf8').toString('base64')
99
+ if (canonical !== normalized) {
100
+ logger.warn('AUTH_PASS_B64 failed base64 verification; falling back to AUTH_PASS')
101
+ return env.AUTH_PASS || null
102
+ }
103
+ if (decoded.length > 0) return decoded
104
+ logger.warn('AUTH_PASS_B64 is set but decoded to an empty value; falling back to AUTH_PASS')
105
+ } catch {
106
+ logger.warn('AUTH_PASS_B64 is not valid base64; falling back to AUTH_PASS')
107
+ }
108
+ }
109
+
110
+ return env.AUTH_PASS || null
111
+ }
112
+
113
  function seedAdminUserFromEnv(dbConn: Database.Database): void {
114
  // Skip seeding during `next build` — env vars may not be available yet
115
  if (process.env.NEXT_PHASE === 'phase-production-build') return
 
118
  if (count > 0) return
119
 
120
  const username = process.env.AUTH_USER || 'admin'
121
+ const password = resolveSeedAuthPassword()
122
 
123
  if (!password) {
124
  logger.warn(
125
  'AUTH_PASS is not set — skipping admin user seeding. ' +
126
+ 'Set AUTH_PASS (quote values containing #) or AUTH_PASS_B64 in your environment.'
127
  )
128
  return
129
  }