baa-conductor

git clone 

baa-conductor / packages / auth / src
im_wower  ·  2026-03-21

model.ts

  1import type { AuthAction } from "./actions.js";
  2
  3export const AUTH_ROLES = [
  4  "controller",
  5  "worker",
  6  "browser_admin",
  7  "ops_admin",
  8  "readonly"
  9] as const;
 10
 11export type AuthRole = (typeof AUTH_ROLES)[number];
 12
 13export const AUTH_TOKEN_KINDS = [
 14  "service_hmac",
 15  "service_signed",
 16  "browser_session",
 17  "ops_session"
 18] as const;
 19
 20export type AuthTokenKind = (typeof AUTH_TOKEN_KINDS)[number];
 21
 22export const DEFAULT_AUTH_AUDIENCE = "control_api";
 23
 24export type AuthAudience = typeof DEFAULT_AUTH_AUDIENCE;
 25export type AuthIdentityBinding = "machine" | "session" | "ops";
 26
 27export interface AuthPrincipal {
 28  subject: string;
 29  role: AuthRole;
 30  tokenKind: AuthTokenKind;
 31  audience: AuthAudience;
 32  tokenId?: string;
 33  issuer?: string;
 34  issuedAt?: string;
 35  expiresAt?: string;
 36  controllerId?: string;
 37  workerId?: string;
 38  nodeId?: string;
 39  sessionId?: string;
 40  scope?: AuthAction[];
 41}
 42
 43export interface AuthResourceOwnership {
 44  controllerId?: string;
 45  workerId?: string;
 46}
 47
 48export interface AuthTokenModel {
 49  kind: AuthTokenKind;
 50  summary: string;
 51  intendedRoles: readonly AuthRole[];
 52  status: "current" | "future";
 53  credentialShape: "shared_secret" | "signed_claims" | "bearer_secret";
 54  identityBinding: AuthIdentityBinding;
 55}
 56
 57export const AUTH_TOKEN_MODELS = {
 58  service_hmac: {
 59    kind: "service_hmac",
 60    summary: "当前最低要求。controller 与 worker 使用共享密钥或 HMAC 签名材料。",
 61    intendedRoles: ["controller", "worker"],
 62    status: "current",
 63    credentialShape: "shared_secret",
 64    identityBinding: "machine"
 65  },
 66  service_signed: {
 67    kind: "service_signed",
 68    summary: "后续演进。把 role、scope、节点身份放进可验证的签名 service token。",
 69    intendedRoles: ["controller", "worker"],
 70    status: "future",
 71    credentialShape: "signed_claims",
 72    identityBinding: "machine"
 73  },
 74  browser_session: {
 75    kind: "browser_session",
 76    summary: "可见 control 浏览器会话使用的 bearer token,用于 browser_admin 与 readonly。",
 77    intendedRoles: ["browser_admin", "readonly"],
 78    status: "current",
 79    credentialShape: "bearer_secret",
 80    identityBinding: "session"
 81  },
 82  ops_session: {
 83    kind: "ops_session",
 84    summary: "运维人员专用 bearer token,权限与浏览器会话分离。",
 85    intendedRoles: ["ops_admin"],
 86    status: "current",
 87    credentialShape: "bearer_secret",
 88    identityBinding: "ops"
 89  }
 90} satisfies Record<AuthTokenKind, AuthTokenModel>;
 91
 92export type BearerTokenFailureReason =
 93  | "missing_authorization_header"
 94  | "invalid_authorization_scheme"
 95  | "empty_bearer_token";
 96
 97export type BearerTokenExtractionResult =
 98  | {
 99      ok: true;
100      token: string;
101    }
102  | {
103      ok: false;
104      reason: BearerTokenFailureReason;
105    };
106
107export type AuthVerificationFailureReason =
108  | "unknown_token"
109  | "invalid_signature"
110  | "token_expired"
111  | "audience_mismatch";
112
113export type AuthVerificationResult =
114  | {
115      ok: true;
116      principal: AuthPrincipal;
117    }
118  | {
119      ok: false;
120      reason: AuthVerificationFailureReason;
121      statusCode: 401 | 403;
122    };
123
124export interface AuthTokenVerifier {
125  verifyBearerToken(token: string): Promise<AuthVerificationResult>;
126}
127
128export function extractBearerToken(
129  authorizationHeader: string | undefined
130): BearerTokenExtractionResult {
131  if (!authorizationHeader) {
132    return {
133      ok: false,
134      reason: "missing_authorization_header"
135    };
136  }
137
138  const [scheme, ...rest] = authorizationHeader.trim().split(/\s+/u);
139
140  if (scheme !== "Bearer") {
141    return {
142      ok: false,
143      reason: "invalid_authorization_scheme"
144    };
145  }
146
147  const token = rest.join(" ").trim();
148
149  if (!token) {
150    return {
151      ok: false,
152      reason: "empty_bearer_token"
153    };
154  }
155
156  return {
157    ok: true,
158    token
159  };
160}