fix: support AUTH_PASS_B64 for seeded admin password
This commit is contained in:
parent
bcae068da9
commit
43a47ad886
|
|
@ -2,6 +2,9 @@
|
|||
# Admin user seeded on first run (only if no users exist in DB)
|
||||
AUTH_USER=admin
|
||||
AUTH_PASS=change-me-on-first-login
|
||||
# If your password includes "#" and you do not want to quote AUTH_PASS, use base64:
|
||||
# AUTH_PASS_B64=Y2hhbmdlLW1lLW9uLWZpcnN0LWxvZ2lu
|
||||
# Example: echo -n 'my#password' | base64
|
||||
|
||||
# API key for headless/external access (x-api-key header)
|
||||
API_KEY=generate-a-random-key
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ pnpm dev # http://localhost:3000
|
|||
```
|
||||
|
||||
Initial login is seeded from `AUTH_USER` / `AUTH_PASS` on first run.
|
||||
If `AUTH_PASS` contains `#`, quote it (e.g. `AUTH_PASS="my#password"`) or use `AUTH_PASS_B64`.
|
||||
|
||||
## Project Status
|
||||
|
||||
|
|
@ -339,6 +340,7 @@ See [`.env.example`](.env.example) for the complete list. Key variables:
|
|||
|----------|----------|-------------|
|
||||
| `AUTH_USER` | No | Initial admin username (default: `admin`) |
|
||||
| `AUTH_PASS` | No | Initial admin password |
|
||||
| `AUTH_PASS_B64` | No | Base64-encoded admin password (overrides `AUTH_PASS` if set) |
|
||||
| `API_KEY` | No | API key for headless access |
|
||||
| `OPENCLAW_HOME` | Yes* | Path to `.openclaw` directory |
|
||||
| `OPENCLAW_GATEWAY_HOST` | No | Gateway host (default: `127.0.0.1`) |
|
||||
|
|
|
|||
|
|
@ -83,6 +83,7 @@ See `.env.example` for the full list. Key variables:
|
|||
|----------|----------|---------|-------------|
|
||||
| `AUTH_USER` | Yes | `admin` | Admin username (seeded on first run) |
|
||||
| `AUTH_PASS` | Yes | - | Admin password |
|
||||
| `AUTH_PASS_B64` | No | - | Base64-encoded admin password (overrides `AUTH_PASS` if set) |
|
||||
| `API_KEY` | Yes | - | API key for headless access |
|
||||
| `PORT` | No | `3005` (direct) / `3000` (Docker) | Server port |
|
||||
| `OPENCLAW_HOME` | No | - | Path to OpenClaw installation |
|
||||
|
|
@ -99,6 +100,14 @@ rm -rf node_modules
|
|||
pnpm install
|
||||
```
|
||||
|
||||
### AUTH_PASS with "#" is not working
|
||||
|
||||
In dotenv files, `#` starts a comment unless the value is quoted.
|
||||
|
||||
Use one of these:
|
||||
- `AUTH_PASS="my#password"`
|
||||
- `AUTH_PASS_B64=$(echo -n 'my#password' | base64)`
|
||||
|
||||
### "pnpm-lock.yaml not found" during Docker build
|
||||
|
||||
If your deployment context omits `pnpm-lock.yaml`, Docker build now falls back to
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
import { describe, expect, it } from 'vitest'
|
||||
import { resolveSeedAuthPassword } from '../db'
|
||||
|
||||
describe('resolveSeedAuthPassword', () => {
|
||||
it('returns AUTH_PASS when AUTH_PASS_B64 is not set', () => {
|
||||
const password = resolveSeedAuthPassword({ AUTH_PASS: 'plain-secret-123' } as unknown as NodeJS.ProcessEnv)
|
||||
expect(password).toBe('plain-secret-123')
|
||||
})
|
||||
|
||||
it('prefers AUTH_PASS_B64 when present and valid', () => {
|
||||
const encoded = Buffer.from('secret#with#hash', 'utf8').toString('base64')
|
||||
const password = resolveSeedAuthPassword({
|
||||
AUTH_PASS: 'fallback-value',
|
||||
AUTH_PASS_B64: encoded,
|
||||
} as unknown as NodeJS.ProcessEnv)
|
||||
expect(password).toBe('secret#with#hash')
|
||||
})
|
||||
|
||||
it('falls back to AUTH_PASS when AUTH_PASS_B64 is invalid', () => {
|
||||
const password = resolveSeedAuthPassword({
|
||||
AUTH_PASS: 'fallback-value',
|
||||
AUTH_PASS_B64: '%%%not-base64%%%',
|
||||
} as unknown as NodeJS.ProcessEnv)
|
||||
expect(password).toBe('fallback-value')
|
||||
})
|
||||
|
||||
it('returns null when no password env var is set', () => {
|
||||
const password = resolveSeedAuthPassword({} as unknown as NodeJS.ProcessEnv)
|
||||
expect(password).toBeNull()
|
||||
})
|
||||
})
|
||||
|
|
@ -83,6 +83,33 @@ const INSECURE_PASSWORDS = new Set([
|
|||
'testpass123',
|
||||
])
|
||||
|
||||
export function resolveSeedAuthPassword(env: NodeJS.ProcessEnv = process.env): string | null {
|
||||
const b64 = env.AUTH_PASS_B64
|
||||
if (b64 && b64.trim().length > 0) {
|
||||
const normalized = b64.trim()
|
||||
const base64Pattern = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/
|
||||
if (!base64Pattern.test(normalized)) {
|
||||
logger.warn('AUTH_PASS_B64 is not valid base64; falling back to AUTH_PASS')
|
||||
return env.AUTH_PASS || null
|
||||
}
|
||||
|
||||
try {
|
||||
const decoded = Buffer.from(normalized, 'base64').toString('utf8')
|
||||
const canonical = Buffer.from(decoded, 'utf8').toString('base64')
|
||||
if (canonical !== normalized) {
|
||||
logger.warn('AUTH_PASS_B64 failed base64 verification; falling back to AUTH_PASS')
|
||||
return env.AUTH_PASS || null
|
||||
}
|
||||
if (decoded.length > 0) return decoded
|
||||
logger.warn('AUTH_PASS_B64 is set but decoded to an empty value; falling back to AUTH_PASS')
|
||||
} catch {
|
||||
logger.warn('AUTH_PASS_B64 is not valid base64; falling back to AUTH_PASS')
|
||||
}
|
||||
}
|
||||
|
||||
return env.AUTH_PASS || null
|
||||
}
|
||||
|
||||
function seedAdminUserFromEnv(dbConn: Database.Database): void {
|
||||
// Skip seeding during `next build` — env vars may not be available yet
|
||||
if (process.env.NEXT_PHASE === 'phase-production-build') return
|
||||
|
|
@ -91,12 +118,12 @@ function seedAdminUserFromEnv(dbConn: Database.Database): void {
|
|||
if (count > 0) return
|
||||
|
||||
const username = process.env.AUTH_USER || 'admin'
|
||||
const password = process.env.AUTH_PASS
|
||||
const password = resolveSeedAuthPassword()
|
||||
|
||||
if (!password) {
|
||||
logger.warn(
|
||||
'AUTH_PASS is not set — skipping admin user seeding. ' +
|
||||
'Set AUTH_PASS in your .env file to create the initial admin account.'
|
||||
'Set AUTH_PASS (quote values containing #) or AUTH_PASS_B64 in your environment.'
|
||||
)
|
||||
return
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue