zengate 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zengate",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Keyless OpenAI-compatible API (Chat Completions + Responses) for OpenCode's free Zen models, served through the real OpenCode CLI.",
5
5
  "homepage": "https://github.com/developingchet/zengate#readme",
6
6
  "repository": {
@@ -3,17 +3,25 @@ import { ApiError, sendError } from './errors.js';
3
3
 
4
4
  const PUBLIC_PATHS = new Set(['/health', '/ready']);
5
5
 
6
- /** Constant-time comparison; hashing first hides the key length. */
7
- function safeEqual(a, b) {
8
- const ha = crypto.createHash('sha256').update(a).digest();
9
- const hb = crypto.createHash('sha256').update(b).digest();
10
- return crypto.timingSafeEqual(ha, hb);
6
+ const BEARER = 'bearer';
7
+
8
+ /**
9
+ * Constant-time comparison of the key bytes. Only the length can differ in
10
+ * timing, and key lengths are not secret (generated keys have a fixed format).
11
+ */
12
+ function safeEqual(presented, expected) {
13
+ const a = Buffer.from(presented);
14
+ const b = Buffer.from(expected);
15
+ return a.length === b.length && crypto.timingSafeEqual(a, b);
11
16
  }
12
17
 
18
+ /** The key from `Authorization: Bearer <key>`, parsed without a backtracking regex. */
13
19
  function presentedKey(req) {
14
- const header = req.headers.authorization || '';
15
- const match = /^Bearer\s+(.+)$/i.exec(header);
16
- return match ? match[1].trim() : '';
20
+ const header = typeof req.headers.authorization === 'string' ? req.headers.authorization.trim() : '';
21
+ const scheme = header.slice(0, BEARER.length);
22
+ const separator = header.charAt(BEARER.length);
23
+ if (scheme.toLowerCase() !== BEARER || !/\s/.test(separator)) return '';
24
+ return header.slice(BEARER.length + 1).trim();
17
25
  }
18
26
 
19
27
  /**
@@ -25,9 +33,11 @@ export function authMiddleware({ apiKeys, allowNoAuth, onFailure }) {
25
33
  req.clientId = 'anonymous';
26
34
  if (allowNoAuth || PUBLIC_PATHS.has(req.path) || req.method === 'OPTIONS') return next();
27
35
  const key = presentedKey(req);
28
- if (key && apiKeys.some((candidate) => safeEqual(key, candidate))) {
29
- // Opaque per-key id: scopes stored responses to the key that made them.
30
- req.clientId = crypto.createHash('sha256').update(key).digest('hex').slice(0, 32);
36
+ const index = key ? apiKeys.findIndex((candidate) => safeEqual(key, candidate)) : -1;
37
+ if (index !== -1) {
38
+ // Names the matched key without deriving anything from it; scopes
39
+ // stored responses to the key that made them.
40
+ req.clientId = `key_${index}`;
31
41
  return next();
32
42
  }
33
43
  onFailure?.();