fix: sync managed admin password from environment

This commit is contained in:
Codex
2026-07-20 16:13:51 +08:00
parent 27d30139c1
commit 68d4fd944d
3 changed files with 51 additions and 4 deletions
+8
View File
@@ -177,6 +177,14 @@ export async function verifyPassword(password: string, salt: string, expectedHas
return actual.hash === expectedHash;
}
export function isBootstrapAdminCredential(
username: string,
password: string,
adminPassword: string
): boolean {
return normalizeUsername(username) === 'admin' && !!adminPassword && password === adminPassword;
}
export async function signSessionPayload(payload: SessionPayload, secret: string): Promise<string> {
const payloadBytes = encodeText(JSON.stringify(payload));
const encodedPayload = encodeBase64Url(payloadBytes);
+35 -4
View File
@@ -5,6 +5,7 @@ import {
createStoredAccount,
ensureUniqueUsername,
hashPassword,
isBootstrapAdminCredential,
normalizeUsername,
parseBootstrapAccounts,
resolveLoginMode,
@@ -350,11 +351,41 @@ async function authenticateManagedLogin(username: string, password: string): Pro
if (!normalizedUsername || !password) return null;
const accounts = await ensureManagedAccountsBootstrapped();
const account = accounts.find((item) => item.username === normalizedUsername);
if (!account) return null;
let account = accounts.find((item) => item.username === normalizedUsername);
const usesBootstrapAdminCredential = isBootstrapAdminCredential(
normalizedUsername,
password,
effectiveAdminPassword
);
const valid = await verifyPassword(password, account.passwordSalt, account.passwordHash);
if (!valid) return null;
if (!account) {
if (!usesBootstrapAdminCredential) return null;
account = await createStoredAccount({
username: 'admin',
password,
name: '超级管理员',
role: 'super_admin',
customPermissions: [],
});
await saveManagedAccounts([...accounts, account]);
} else {
const valid = await verifyPassword(password, account.passwordSalt, account.passwordHash);
if (!valid) {
if (!usesBootstrapAdminCredential) return null;
const nextPassword = await hashPassword(password);
account = {
...account,
passwordHash: nextPassword.hash,
passwordSalt: nextPassword.salt,
updatedAt: Date.now(),
};
await saveManagedAccounts(
accounts.map((item) => item.id === account?.id ? account : item)
);
}
}
return {
accountId: account.id,
+8
View File
@@ -3,6 +3,7 @@ import assert from 'node:assert/strict';
import {
createStoredAccount,
hashPassword,
isBootstrapAdminCredential,
parseBootstrapAccounts,
resolveLoginMode,
shouldUseSecureSessionCookie,
@@ -73,6 +74,13 @@ test('hashPassword and verifyPassword round-trip correctly', async () => {
assert.equal(await verifyPassword('wrong-password', password.salt, password.hash), false);
});
test('bootstrap admin credential only accepts the configured admin password', () => {
assert.equal(isBootstrapAdminCredential('ADMIN', 'current-secret', 'current-secret'), true);
assert.equal(isBootstrapAdminCredential('admin', 'old-secret', 'current-secret'), false);
assert.equal(isBootstrapAdminCredential('viewer', 'current-secret', 'current-secret'), false);
assert.equal(isBootstrapAdminCredential('admin', '', ''), false);
});
test('signSessionPayload and verifySessionToken reject tampering', async () => {
const token = await signSessionPayload({
accountId: 'account-1',