Add browser-based board CLI auth flow

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
dotta 2026-03-23 07:48:03 -05:00
parent 1376fc8f44
commit 37c2c4acc4
31 changed files with 13299 additions and 19 deletions

View file

@ -7,6 +7,7 @@ import { verifyLocalAgentJwt } from "../agent-auth-jwt.js";
import type { DeploymentMode } from "@paperclipai/shared";
import type { BetterAuthSessionResult } from "../auth/better-auth.js";
import { logger } from "./logger.js";
import { boardAuthService } from "../services/board-auth.js";
function hashToken(token: string) {
return createHash("sha256").update(token).digest("hex");
@ -18,6 +19,7 @@ interface ActorMiddlewareOptions {
}
export function actorMiddleware(db: Db, opts: ActorMiddlewareOptions): RequestHandler {
const boardAuth = boardAuthService(db);
return async (req, _res, next) => {
req.actor =
opts.deploymentMode === "local_trusted"
@ -80,6 +82,25 @@ export function actorMiddleware(db: Db, opts: ActorMiddlewareOptions): RequestHa
return;
}
const boardKey = await boardAuth.findBoardApiKeyByToken(token);
if (boardKey) {
const access = await boardAuth.resolveBoardAccess(boardKey.userId);
if (access.user) {
await boardAuth.touchBoardApiKey(boardKey.id);
req.actor = {
type: "board",
userId: boardKey.userId,
companyIds: access.companyIds,
isInstanceAdmin: access.isInstanceAdmin,
keyId: boardKey.id,
runId: runIdHeader || undefined,
source: "board_key",
};
next();
return;
}
}
const tokenHash = hashToken(token);
const key = await db
.select()

View file

@ -49,10 +49,9 @@ export function boardMutationGuard(): RequestHandler {
return;
}
// Local-trusted mode uses an implicit board actor for localhost-only development.
// In this mode, origin/referer headers can be omitted by some clients for multipart
// uploads; do not block those mutations.
if (req.actor.source === "local_implicit") {
// Local-trusted mode and board bearer keys are not browser-session requests.
// In these modes, origin/referer headers can be absent; do not block those mutations.
if (req.actor.source === "local_implicit" || req.actor.source === "board_key") {
next();
return;
}