This commit introduces a new authentication mechanism for the web UI. Instead of requiring a pre-configured API key, a temporary API key is generated based on the system's UUID. This key is passed to the UI as a URL parameter and used for API requests. Changes: - Added a new utility to get the system UUID and generate a temporary API key. - Modified the `ccr ui` command to generate and pass the temporary API key. - Updated the authentication middleware to validate the temporary API key. - Adjusted the frontend to use the temporary API key from the URL. - Added a dedicated endpoint to test API access without modifying data. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
111 lines
3.1 KiB
TypeScript
111 lines
3.1 KiB
TypeScript
import { FastifyRequest, FastifyReply } from "fastify";
|
|
import { getTempAPIKey } from "../utils/systemUUID";
|
|
|
|
export const apiKeyAuth =
|
|
(config: any) =>
|
|
async (req: FastifyRequest, reply: FastifyReply, done: () => void) => {
|
|
// Check for temp API key in query parameters or headers
|
|
let tempApiKey = null;
|
|
if (req.query && (req.query as any).tempApiKey) {
|
|
tempApiKey = (req.query as any).tempApiKey;
|
|
} else if (req.headers['x-temp-api-key']) {
|
|
tempApiKey = req.headers['x-temp-api-key'] as string;
|
|
}
|
|
|
|
// If temp API key is provided, validate it
|
|
if (tempApiKey) {
|
|
try {
|
|
const expectedTempKey = await getTempAPIKey();
|
|
|
|
// If temp key matches, grant temporary full access
|
|
if (tempApiKey === expectedTempKey) {
|
|
(req as any).accessLevel = "full";
|
|
(req as any).isTempAccess = true;
|
|
return done();
|
|
}
|
|
} catch (error) {
|
|
// If there's an error generating temp key, continue with normal auth
|
|
console.warn("Failed to verify temporary API key:", error);
|
|
}
|
|
}
|
|
|
|
// Public endpoints that don't require authentication
|
|
if (["/", "/health"].includes(req.url) || req.url.startsWith("/ui")) {
|
|
return done();
|
|
}
|
|
|
|
const apiKey = config.APIKEY;
|
|
const isConfigEndpoint = req.url.startsWith("/api/config");
|
|
|
|
// For config endpoints, we implement granular access control
|
|
if (isConfigEndpoint) {
|
|
// Attach access level to request for later use
|
|
(req as any).accessLevel = "restricted";
|
|
|
|
// If no API key is set in config, allow restricted access
|
|
if (!apiKey) {
|
|
(req as any).accessLevel = "restricted";
|
|
return done();
|
|
}
|
|
|
|
// Check for temporary access via query parameter (for UI)
|
|
if ((req as any).isTempAccess) {
|
|
return done();
|
|
}
|
|
|
|
// If API key is set, check authentication
|
|
const authKey: string =
|
|
req.headers.authorization || req.headers["x-api-key"];
|
|
|
|
if (!authKey) {
|
|
(req as any).accessLevel = "restricted";
|
|
return done();
|
|
}
|
|
|
|
let token = "";
|
|
if (authKey.startsWith("Bearer")) {
|
|
token = authKey.split(" ")[1];
|
|
} else {
|
|
token = authKey;
|
|
}
|
|
|
|
if (token !== apiKey) {
|
|
(req as any).accessLevel = "restricted";
|
|
return done();
|
|
}
|
|
|
|
// Full access for authenticated users
|
|
(req as any).accessLevel = "full";
|
|
return done();
|
|
}
|
|
|
|
// For non-config endpoints, use existing logic
|
|
if (!apiKey) {
|
|
return done();
|
|
}
|
|
|
|
// Check for temporary access via query parameter (for UI)
|
|
if ((req as any).isTempAccess) {
|
|
return done();
|
|
}
|
|
|
|
const authKey: string =
|
|
req.headers.authorization || req.headers["x-api-key"];
|
|
if (!authKey) {
|
|
reply.status(401).send("APIKEY is missing");
|
|
return;
|
|
}
|
|
let token = "";
|
|
if (authKey.startsWith("Bearer")) {
|
|
token = authKey.split(" ")[1];
|
|
} else {
|
|
token = authKey;
|
|
}
|
|
if (token !== apiKey) {
|
|
reply.status(401).send("Invalid API key");
|
|
return;
|
|
}
|
|
|
|
done();
|
|
};
|