import { eq } from "drizzle-orm";
import { db } from "../db";
import { users, apiTokens, oauthAccessTokens } from "../db/schema";
import { sha256Hex } from "./oauth";
export type ResolvedPusher = {
userId: string;
username: string;
source: "pat" | "oauth";
};
export function decodeBasicAuth(
header: string | null | undefined
): { user: string; secret: string } | null {
if (!header) return null;
const m = /^\s*Basic\s+(.+)$/i.exec(header);
if (!m) return null;
let decoded: string;
try {
decoded = Buffer.from(m[1].trim(), "base64").toString("utf8");
} catch {
return null;
}
const colon = decoded.indexOf(":");
if (colon < 0) return null;
return {
user: decoded.slice(0, colon),
secret: decoded.slice(colon + 1),
};
}
export function decodeBearerAuth(
header: string | null | undefined
): string | null {
if (!header) return null;
const m = /^\s*Bearer\s+(.+)$/i.exec(header);
if (!m) return null;
const tok = m[1].trim();
return tok || null;
}
async function resolveByPat(token: string): Promise<ResolvedPusher | null> {
if (!token.startsWith("glc_")) return null;
try {
const hash = await sha256Hex(token);
const [row] = await db
.select()
.from(apiTokens)
.where(eq(apiTokens.tokenHash, hash))
.limit(1);
if (!row) return null;
if (row.expiresAt && new Date(row.expiresAt) < new Date()) return null;
const [u] = await db
.select({ id: users.id, username: users.username })
.from(users)
.where(eq(users.id, row.userId))
.limit(1);
if (!u) return null;
return { userId: u.id, username: u.username, source: "pat" };
} catch {
return null;
}
}
async function resolveByOauth(token: string): Promise<ResolvedPusher | null> {
if (!token.startsWith("glct_")) return null;
try {
const hash = await sha256Hex(token);
const [row] = await db
.select()
.from(oauthAccessTokens)
.where(eq(oauthAccessTokens.accessTokenHash, hash))
.limit(1);
if (!row) return null;
if (row.revokedAt) return null;
if (new Date(row.expiresAt) < new Date()) return null;
const [u] = await db
.select({ id: users.id, username: users.username })
.from(users)
.where(eq(users.id, row.userId))
.limit(1);
if (!u) return null;
return { userId: u.id, username: u.username, source: "oauth" };
} catch {
return null;
}
}
export async function resolvePusher(
authHeader: string | null | undefined
): Promise<ResolvedPusher | null> {
if (!authHeader) return null;
const bearer = decodeBearerAuth(authHeader);
if (bearer) {
return (
(await resolveByPat(bearer)) ||
(await resolveByOauth(bearer))
);
}
const basic = decodeBasicAuth(authHeader);
if (basic) {
const secret = basic.secret;
return (
(await resolveByPat(secret)) ||
(await resolveByOauth(secret))
);
}
return null;
}
|