📦 EqualifyEverything / equalify-iris

📄 middleware.ts · 52 lines
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52import type { Request, Response, NextFunction } from "express";
import type { IrisConfig } from "../config.ts";
import type { Store, UserRecord } from "../store/db.ts";
import { fetchUser } from "./github.ts";
import { sendError } from "../routes/errors.ts";

// Request augmented with the resolved user + their GitHub token.
export interface AuthedRequest extends Request {
  user?: UserRecord;
  token?: string;
}

// Cache token -> user id so we don't hit GitHub's /user on every request.
const tokenCache = new Map<string, { id: number; expires: number }>();
const TTL_MS = 5 * 60 * 1000;

export function makeAuthMiddleware(store: Store, cfg: IrisConfig) {
  const apiBase = cfg.github.api_base_url;
  const defaultMaxIter = cfg.defaults.max_review_iterations;
  return async function auth(req: AuthedRequest, res: Response, next: NextFunction): Promise<void> {
    const header = req.header("authorization") ?? "";
    const match = header.match(/^Bearer\s+(.+)$/i);
    if (!match) {
      sendError(res, 401, "unauthorized", "Missing or malformed Authorization header");
      return;
    }
    const token = match[1].trim();

    try {
      const cached = tokenCache.get(token);
      let userId: number;
      if (cached && cached.expires > Date.now()) {
        userId = cached.id;
        // Keep the stored token fresh for PR operations.
        const u = store.getUser(userId);
        if (u && u.github_token !== token) store.upsertUser({ github_user_id: u.github_user_id, github_login: u.github_login, github_token: token });
      } else {
        // GitHub identifies the caller; login provisions an account (§9.1).
        const ghUser = await fetchUser(token, apiBase);
        store.upsertUser({ github_user_id: ghUser.id, github_login: ghUser.login, github_token: token }, defaultMaxIter);
        userId = ghUser.id;
        tokenCache.set(token, { id: userId, expires: Date.now() + TTL_MS });
      }
      req.user = store.getUser(userId)!;
      req.token = token;
      next();
    } catch (e) {
      sendError(res, 401, "unauthorized", `Token validation failed: ${(e as Error).message}`);
    }
  };
}