Spaces:
Runtime error
Runtime error
Nyk commited on
Commit ·
2b182b0
1
Parent(s): cbd7797
fix: support AUTH_PASS_B64 for seeded admin password
Browse files- .env.example +3 -0
- README.md +2 -0
- docs/deployment.md +9 -0
- src/lib/__tests__/db-seed-auth-pass.test.ts +31 -0
- src/lib/db.ts +29 -2
.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 =
|
| 95 |
|
| 96 |
if (!password) {
|
| 97 |
logger.warn(
|
| 98 |
'AUTH_PASS is not set — skipping admin user seeding. ' +
|
| 99 |
-
'Set AUTH_PASS
|
| 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 |
}
|