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) ##### Admin Setup (Automated)
- **Example:** - **Example (new instance):**
```json ```json
{ {
"admin": { "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:** - **Fields:**
- `username`: Admin username to create on first startup - `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 - `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.** **On first startup, Zerobyte will automatically create the admin user from the config file.**
> **⚠️ About the Recovery Key** > **⚠️ About the Recovery Key**

View file

@ -176,17 +176,30 @@ export const startup = async () => {
try { try {
const { authService } = await import("../auth/auth.service"); 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(); const hasUsers = await authService.hasUsers();
if (!hasUsers) { if (!hasUsers) {
const { user } = await authService.register(configFileAdmin.username, configFileAdmin.password); let userId: number;
logger.info(`Admin user '${configFileAdmin.username}' created from config.`); 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) { 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 { } 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) { } catch (err) {
const e = err instanceof Error ? err : new Error(String(err)); const e = err instanceof Error ? err : new Error(String(err));