Merge pull request #136 from builderz-labs/fix-auth-pass-hash-env-134

fix: support # in seeded admin password env config
This commit is contained in:
nyk 2026-03-04 12:59:02 +07:00 committed by GitHub
commit 3681420376
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 74 additions and 2 deletions

View File

@ -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

View File

@ -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`) |

View File

@ -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

View File

@ -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()
})
})

View File

@ -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
}