feat(proxy): add environment config and request handling
- Introduce environment-based configuration for proxy settings - Add middleware for API key authentication - Implement request forwarding to LanguageTool backend - Set up server startup and routing logic
This commit is contained in:
63
src/env.ts
Normal file
63
src/env.ts
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
/**
|
||||||
|
* Environment configuration for lt-auth-proxy.
|
||||||
|
* All properties are lazily evaluated and cached on first access.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class Env {
|
||||||
|
private static _proxyHost?: string;
|
||||||
|
private static _proxyPort?: number;
|
||||||
|
private static _apiKeys?: string[];
|
||||||
|
private static _ltServerHost?: string;
|
||||||
|
private static _ltServerPort?: number;
|
||||||
|
|
||||||
|
private static getEnv(key: string, required = false): string | undefined {
|
||||||
|
const value = Deno.env.get(key);
|
||||||
|
if (required && (!value || value.trim() === '')) {
|
||||||
|
throw new Error(`Missing required environment variable: ${key}`);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Hostname for the proxy (default: 0.0.0.0) */
|
||||||
|
static get proxyHost(): string {
|
||||||
|
if (this._proxyHost === undefined) {
|
||||||
|
this._proxyHost = this.getEnv('PROXY_HOST') || '0.0.0.0';
|
||||||
|
}
|
||||||
|
return this._proxyHost;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Port for the proxy (default: 8011) */
|
||||||
|
static get proxyPort(): number {
|
||||||
|
if (this._proxyPort === undefined) {
|
||||||
|
this._proxyPort = Number(this.getEnv('PROXY_PORT') || 8011);
|
||||||
|
}
|
||||||
|
return this._proxyPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** List of allowed API keys (required) */
|
||||||
|
static get apiKeys(): string[] {
|
||||||
|
if (this._apiKeys === undefined) {
|
||||||
|
const raw = this.getEnv('API_KEYS', true)!;
|
||||||
|
this._apiKeys = raw.split(',').map((k) => k.trim()).filter((k) =>
|
||||||
|
k.length > 0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return this._apiKeys;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Hostname of the LanguageTool backend (default: localhost) */
|
||||||
|
static get ltServerHost(): string {
|
||||||
|
if (this._ltServerHost === undefined) {
|
||||||
|
this._ltServerHost = this.getEnv('LT_SERVER_HOST') || 'localhost';
|
||||||
|
}
|
||||||
|
return this._ltServerHost;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Port of the LanguageTool backend (default: 8010) */
|
||||||
|
static get ltServerPort(): number {
|
||||||
|
if (this._ltServerPort === undefined) {
|
||||||
|
this._ltServerPort = Number(this.getEnv('LT_SERVER_PORT') || 8010);
|
||||||
|
}
|
||||||
|
return this._ltServerPort;
|
||||||
|
}
|
||||||
|
}
|
21
src/ltProxyAuth.ts
Normal file
21
src/ltProxyAuth.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { Middleware } from 'http-kernel/Types/mod.ts';
|
||||||
|
import { Env } from './env.ts';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Middleware that checks for a valid API key via ?apiKey=... query/form param.
|
||||||
|
* Rejects request with 403 if the key is missing or invalid.
|
||||||
|
*/
|
||||||
|
export const authMiddleware: Middleware = async (ctx, next) => {
|
||||||
|
const key = ctx.query.apiKey;
|
||||||
|
|
||||||
|
// Support both ?apiKey=... and form body with apiKey=...
|
||||||
|
const extractedKey = Array.isArray(key) ? key[0] : key;
|
||||||
|
|
||||||
|
if (!extractedKey || !Env.apiKeys.includes(extractedKey)) {
|
||||||
|
return new Response('Forbidden – Invalid API key', { status: 403 });
|
||||||
|
}
|
||||||
|
|
||||||
|
return await next();
|
||||||
|
};
|
||||||
|
|
||||||
|
export { authMiddleware as ltProxyAuth };
|
28
src/ltProxyHandler.ts
Normal file
28
src/ltProxyHandler.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { Handler } from 'http-kernel/Types/mod.ts';
|
||||||
|
import { Env } from './env.ts';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forwards the incoming request to the actual LanguageTool server.
|
||||||
|
* Dynamically passes through path and query string.
|
||||||
|
*/
|
||||||
|
export const handler: Handler = async (ctx) => {
|
||||||
|
const originalUrl = new URL(ctx.req.url);
|
||||||
|
const proxyUrl = new URL(
|
||||||
|
`${originalUrl.pathname}${originalUrl.search}`,
|
||||||
|
`http://${Env.ltServerHost}:${Env.ltServerPort}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
const forwarded = await fetch(proxyUrl.toString(), {
|
||||||
|
method: ctx.req.method,
|
||||||
|
headers: ctx.req.headers,
|
||||||
|
body: ctx.req.body,
|
||||||
|
});
|
||||||
|
|
||||||
|
const headers = new Headers(forwarded.headers);
|
||||||
|
return new Response(forwarded.body, {
|
||||||
|
status: forwarded.status,
|
||||||
|
headers,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export { handler as ltProxyHandler };
|
19
src/main.ts
Normal file
19
src/main.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { HttpKernel } from 'http-kernel/mod.ts';
|
||||||
|
import { Env } from './env.ts';
|
||||||
|
import { ltProxyAuth } from './ltProxyAuth.ts';
|
||||||
|
import { ltProxyHandler } from './ltProxyHandler.ts';
|
||||||
|
|
||||||
|
const httpKernel = new HttpKernel();
|
||||||
|
|
||||||
|
httpKernel.route({
|
||||||
|
method: 'POST',
|
||||||
|
path: '/*',
|
||||||
|
}).middleware(ltProxyAuth).handle(ltProxyHandler);
|
||||||
|
|
||||||
|
Deno.serve({
|
||||||
|
port: Env.proxyPort,
|
||||||
|
hostname: Env.proxyHost,
|
||||||
|
onListen: ({ hostname, port }) => {
|
||||||
|
console.info(`lt-auth-proxy listening on ${hostname}:${port}`);
|
||||||
|
},
|
||||||
|
}, async (req) => await httpKernel.handle(req));
|
Reference in New Issue
Block a user