feat(auth): add api key authentication

Implement API key authentication by introducing a new auth module.
Update configuration and .env.example to support API key setup, and add
authorization checks in the server endpoints.
This commit is contained in:
2025-06-30 12:32:44 -05:00
parent 37f0c4b643
commit 190442a8cf
4 changed files with 51 additions and 1 deletions

View File

@@ -1,2 +1,3 @@
PORT=11434 PORT=11434
VERBOSE=false VERBOSE=false
API_KEY=MY0P3NA1K3Y

38
src/auth.ts Normal file
View File

@@ -0,0 +1,38 @@
/**
* @fileoverview This file contains the authentication logic for the server.
*/
import http from 'http';
import { config } from './config';
/**
* Checks for API key authentication.
* @param req - The HTTP incoming message object.
* @param res - The HTTP server response object.
* @returns True if the request is authorized, false otherwise.
*/
export function isAuthorized(
req: http.IncomingMessage,
res: http.ServerResponse,
): boolean {
if (!config.API_KEY) {
return true; // No key configured, public access.
}
const authHeader = req.headers.authorization;
if (!authHeader) {
res.writeHead(401, { 'Content-Type': 'application/json' });
res.end(
JSON.stringify({ error: { message: 'Missing Authorization header' } }),
);
return false;
}
const token = authHeader.split(' ')[1];
if (token !== config.API_KEY) {
res.writeHead(401, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: { message: 'Invalid API key' } }));
return false;
}
return true;
}

View File

@@ -23,4 +23,10 @@ export const config = {
* @type {boolean} * @type {boolean}
*/ */
VERBOSE: Boolean(process.env.VERBOSE ?? true), VERBOSE: Boolean(process.env.VERBOSE ?? true),
/**
* The API key for securing the server.
* If not set, the server will be public.
* @type {string | undefined}
*/
API_KEY: process.env.API_KEY,
}; };

View File

@@ -8,6 +8,7 @@ import { listModels, sendChat, sendChatStream } from './chatwrapper';
import { mapRequest, mapResponse, mapStreamChunk } from './mapper.js'; import { mapRequest, mapResponse, mapStreamChunk } from './mapper.js';
import { RequestBody, GeminiResponse, GeminiStreamChunk, Part } from './types'; import { RequestBody, GeminiResponse, GeminiStreamChunk, Part } from './types';
import { config } from './config'; import { config } from './config';
import { isAuthorized } from './auth';
// ================================================================== // ==================================================================
// Server Configuration // Server Configuration
@@ -97,6 +98,10 @@ http
return; return;
} }
if (!isAuthorized(req, res)) {
return;
}
// Route for listing available models. // Route for listing available models.
if (pathname === '/v1/models' || pathname === '/models') { if (pathname === '/v1/models' || pathname === '/models') {
res.writeHead(200, { 'Content-Type': 'application/json' }); res.writeHead(200, { 'Content-Type': 'application/json' });