admin passwordHash import support

This commit is contained in:
Jakub Trávník 2025-12-01 12:29:30 +01:00
parent 8f540164df
commit d767346345
2 changed files with 35 additions and 7 deletions

View file

@ -336,7 +336,7 @@ Secrets/credentials in the config file can reference environment variables using
##### Admin Setup (Automated)
- **Example:**
- **Example (new instance):**
```json
{
"admin": {
@ -346,11 +346,26 @@ Secrets/credentials in the config file can reference environment variables using
}
}
```
- **Example (migration from another instance):**
```json
{
"admin": {
"username": "admin",
"passwordHash": "$argon2id$v=19$m=19456,t=2,p=1$...",
"recoveryKey": "${RECOVERY_KEY}"
}
}
```
- **Fields:**
- `username`: Admin username to create on first startup
- `password`: Admin password (can use `${ENV_VAR}`)
- `password`: Admin password for new instances (can use `${ENV_VAR}`)
- `passwordHash`: Pre-hashed password for migration (exported from another instance)
- `recoveryKey`: Optional recovery key (can use `${ENV_VAR}`) - if provided, the UI prompt to download recovery key will be skipped
> **Note:** Use either `password` OR `passwordHash`, not both. The `passwordHash` option is useful when migrating from another Zerobyte instance using an exported config with `includePasswordHash=true`.
**On first startup, Zerobyte will automatically create the admin user from the config file.**
> **⚠️ About the Recovery Key**

View file

@ -176,17 +176,30 @@ export const startup = async () => {
try {
const { authService } = await import("../auth/auth.service");
if (configFileAdmin && configFileAdmin.username && configFileAdmin.password) {
if (configFileAdmin && configFileAdmin.username && (configFileAdmin.password || configFileAdmin.passwordHash)) {
const hasUsers = await authService.hasUsers();
if (!hasUsers) {
const { user } = await authService.register(configFileAdmin.username, configFileAdmin.password);
logger.info(`Admin user '${configFileAdmin.username}' created from config.`);
let userId: number;
if (configFileAdmin.passwordHash) {
// Import with existing password hash (migration from another instance)
const [user] = await db.insert(usersTable).values({
username: configFileAdmin.username,
passwordHash: configFileAdmin.passwordHash,
}).returning();
userId = user.id;
logger.info(`Admin user '${configFileAdmin.username}' imported with password hash from config.`);
} else {
// Create new user with plaintext password
const { user } = await authService.register(configFileAdmin.username, configFileAdmin.password);
userId = user.id;
logger.info(`Admin user '${configFileAdmin.username}' created from config.`);
}
if (configFileAdmin.recoveryKey) {
await db.update(usersTable).set({ hasDownloadedResticPassword: true }).where(eq(usersTable.id, user.id));
await db.update(usersTable).set({ hasDownloadedResticPassword: true }).where(eq(usersTable.id, userId));
}
}
} else {
logger.warn("Admin config missing required fields (username, password). Skipping automated admin setup.");
logger.warn("Admin config missing required fields (username, password or passwordHash). Skipping automated admin setup.");
}
} catch (err) {
const e = err instanceof Error ? err : new Error(String(err));