Add ability to predefine a root api key

This commit is contained in:
Owen
2026-04-20 12:19:21 -07:00
parent 0ac97ecd5e
commit 2dad97cb6b
2 changed files with 108 additions and 0 deletions

View File

@@ -0,0 +1,106 @@
import { db, apiKeys } from "@server/db";
import { eq } from "drizzle-orm";
import { generateRandomString, RandomReader } from "@oslojs/crypto/random";
import moment from "moment";
import logger from "@server/logger";
import { hashPassword } from "@server/auth/password";
const random: RandomReader = {
read(bytes: Uint8Array): void {
crypto.getRandomValues(bytes);
}
};
function validateApiKeyId(id: string): boolean {
return /^[a-z0-9]{15}$/.test(id);
}
function validateApiKeySecret(secret: string): boolean {
return secret.length > 0;
}
function showRootApiKey(apiKeyId: string, source: string): void {
console.log(`=== ROOT API KEY ${source} ===`);
console.log("API Key ID:", apiKeyId);
console.log(
"The root API key from PANGOLIN_ROOT_API_KEY has been applied."
);
console.log("Use the full key value (apiKeyId.apiKeySecret) in requests.");
console.log("================================");
}
export async function ensureRootApiKey() {
try {
const envApiKey = process.env.PANGOLIN_ROOT_API_KEY;
if (!envApiKey) {
logger.debug(
"PANGOLIN_ROOT_API_KEY not set. Root API key from environment skipped."
);
return;
}
const parts = envApiKey.split(".");
if (parts.length !== 2) {
throw new Error(
"Invalid format for PANGOLIN_ROOT_API_KEY. Expected format: {apiKeyId}.{apiKeySecret}"
);
}
const [apiKeyId, apiKeySecret] = parts;
if (!validateApiKeyId(apiKeyId)) {
throw new Error(
"Invalid apiKeyId in PANGOLIN_ROOT_API_KEY. Must be 15 lowercase alphanumeric characters."
);
}
if (!validateApiKeySecret(apiKeySecret)) {
throw new Error(
"Invalid apiKeySecret in PANGOLIN_ROOT_API_KEY. Secret must not be empty."
);
}
const apiKeyHash = await hashPassword(apiKeySecret);
const lastChars = apiKeySecret.slice(-4);
const createdAt = moment().toISOString();
const [existingKey] = await db
.select()
.from(apiKeys)
.where(eq(apiKeys.apiKeyId, apiKeyId));
if (existingKey) {
if (!existingKey.isRoot) {
console.warn(
`API key with ID ${apiKeyId} exists but is not a root key. Promoting to root and updating hash.`
);
} else {
console.warn(
`Overwriting existing root API key hash since PANGOLIN_ROOT_API_KEY is set (apiKeyId: ${apiKeyId})`
);
}
await db
.update(apiKeys)
.set({ apiKeyHash, lastChars, isRoot: true })
.where(eq(apiKeys.apiKeyId, apiKeyId));
showRootApiKey(apiKeyId, "UPDATED FROM ENVIRONMENT");
} else {
await db.insert(apiKeys).values({
apiKeyId,
name: "Root API Key (Environment)",
apiKeyHash,
lastChars,
createdAt,
isRoot: true
});
showRootApiKey(apiKeyId, "CREATED FROM ENVIRONMENT");
}
} catch (error) {
console.error("Failed to ensure root API key:", error);
throw error;
}
}