fosrl.pangolin/server/setup/ensureSetupToken.ts
Adrian Astles 69baa6785f feat: Add setup token security for initial server setup
- Add setupTokens database table with proper schema
- Implement setup token generation on first server startup
- Add token validation endpoint and modify admin creation
- Update initial setup page to require setup token
- Add migration scripts for both SQLite and PostgreSQL
- Add internationalization support for setup token fields
- Implement proper error handling and logging
- Add CLI command for resetting user security keys

This prevents unauthorized access during initial server setup by requiring
a token that is generated and displayed in the server console.
2025-08-03 21:17:18 +08:00

73 lines
No EOL
2.3 KiB
TypeScript

import { db, setupTokens, users } from "@server/db";
import { eq } from "drizzle-orm";
import { generateRandomString, RandomReader } from "@oslojs/crypto/random";
import moment from "moment";
import logger from "@server/logger";
const random: RandomReader = {
read(bytes: Uint8Array): void {
crypto.getRandomValues(bytes);
}
};
function generateToken(): string {
// Generate a 32-character alphanumeric token
const alphabet = "abcdefghijklmnopqrstuvwxyz0123456789";
return generateRandomString(random, alphabet, 32);
}
function generateId(length: number): string {
const alphabet = "abcdefghijklmnopqrstuvwxyz0123456789";
return generateRandomString(random, alphabet, length);
}
export async function ensureSetupToken() {
try {
// Check if a server admin already exists
const [existingAdmin] = await db
.select()
.from(users)
.where(eq(users.serverAdmin, true));
// If admin exists, no need for setup token
if (existingAdmin) {
logger.warn("Server admin exists. Setup token generation skipped.");
return;
}
// Check if a setup token already exists
const existingTokens = await db
.select()
.from(setupTokens)
.where(eq(setupTokens.used, false));
// If unused token exists, display it instead of creating a new one
if (existingTokens.length > 0) {
console.log("=== SETUP TOKEN EXISTS ===");
console.log("Token:", existingTokens[0].token);
console.log("Use this token on the initial setup page");
console.log("================================");
return;
}
// Generate a new setup token
const token = generateToken();
const tokenId = generateId(15);
await db.insert(setupTokens).values({
tokenId: tokenId,
token: token,
used: false,
dateCreated: moment().toISOString(),
dateUsed: null
});
console.log("=== SETUP TOKEN GENERATED ===");
console.log("Token:", token);
console.log("Use this token on the initial setup page");
console.log("================================");
} catch (error) {
console.error("Failed to ensure setup token:", error);
throw error;
}
}