Skip to content

Authentication

bAInquet has three authentication principals: a User JWT for dashboard and admin traffic, a Connector token plus HMAC for ingestion, and a planned API key for programmatic access. Each is used on a different surface.

PrincipalHow it authenticatesSurface
UserAuthorization: Bearer <access JWT>, refresh via the bnq_refresh HttpOnly cookiedashboard and admin REST
Connectorscoped bearer token plus HMAC signatureingestion only
API keyBearer bnq_live_... or X-Api-Keyprogrammatic access; created under /v1/organizations/{id}/api-keys

The global auth middleware (apps/api/src/app.ts) is only wired when a JWT signer is configured. It is permissive: an absent credential resolves to anonymous, and a present-but-invalid JWT is rejected 401. Per-route enforcement happens inside handlers via requireUser(ctx) and requireRole(ctx, role); the service layer derives the effective per-organization role and enforces tenant scope.

1. User JWT

Dashboard and admin users authenticate with a short-lived EdDSA/RS256 access JWT carried in the Authorization header. A longer-lived refresh token is held in a rotating HttpOnly cookie.

The refresh-rotation model

  • Login returns the access JWT in the body and sets a bnq_refresh HttpOnly cookie.
  • The access JWT is short-lived (about 15 minutes). When it expires, call refresh to get a new one.
  • Refresh rotates. Each refresh issues a new access JWT and a new refresh cookie, invalidating the old refresh token. This limits the window of a stolen refresh token.
  • The cookie is HttpOnly, so client JavaScript never reads it; the browser sends it automatically to the refresh endpoint.

Auth endpoints

MethodPathPrincipalStatus
POST/v1/auth/registeranonymous201
POST/v1/auth/verify-emailanonymous200
POST/v1/auth/loginanonymous200 (sets bnq_refresh cookie)
POST/v1/auth/refreshrefresh cookie or body200 (rotated)
POST/v1/auth/logoutuser204
POST/v1/auth/logout-alluser204
GET/v1/auth/meuser200
POST/v1/auth/password/changeuser204 (bumps token version, revokes sessions)
POST/v1/auth/password/forgotanonymous202 (always, no account enumeration)
POST/v1/auth/password/resetreset token204
bash
# Log in: capture the rotating refresh cookie into cookies.txt
curl -sS -X POST https://api.bainquet.online/v1/auth/login \
  -H 'Content-Type: application/json' \
  -c cookies.txt \
  -d '{ "email": "owner@acme.com", "password": "a-strong-password" }'

# Refresh: send the cookie back, receive a new access JWT and a rotated cookie
curl -sS -X POST https://api.bainquet.online/v1/auth/refresh \
  -b cookies.txt -c cookies.txt

logout revokes the current session; logout-all and a password change bump the token version, revoking every session. Errors emitted: validation.failed, auth.unauthenticated, auth.token_revoked, service.unavailable.

Built caveats

Email-verification, password-reset, and forgot tokens are stored in memory in the current build and do not survive a restart. There is no /v1/auth/sessions or /v1/auth/org/switch route yet.

RBAC role ladder

User access is governed by an ordered role ladder. Roles are per organization (derived from org_membership), except admin and super_admin, which are platform-wide.

RoleCan do
viewerread organization, websites, knowledge, entitlements
editorthe above, plus edit websites, request verification, trigger publish
organization_adminthe above, plus manage members, create websites, issue connector tokens
organization_ownerthe above, plus delete the organization and run GDPR erasure
adminplatform admin: read any organization, audit log, impersonate
super_adminthe above, highest platform role

The ladder is viewer < editor < organization_admin < organization_owner < admin < super_admin. A route's minimum role is enforced in the service layer.

2. Connector token plus HMAC

Connectors authenticate against the ingestion surface only. A connector token is bound to one verified website and is scope-limited to ingestion; it is not part of the RBAC ladder and cannot publish or read the dashboard.

The token has the form connectorId.secret, shown once at creation. Every ingestion request carries:

  • Authorization: Bearer <connectorId>.<secret>
  • an HMAC signature (X-Signature) over a six-line canonical string, using a key derived from the secret with HKDF-SHA256 (info = "bq.connector.hmac.v1", salt = websiteId),
  • X-Timestamp (unix seconds, plus or minus 300s window), X-Nonce (per-request, replay-guarded in Redis), X-Body-Sha256, and a required Idempotency-Key.

The full signing scheme, the canonical-string field order, the server verification pipeline, and a no-SDK signing example are in Ingestion and signing. To issue and manage tokens, see Connector tokens.

3. API key

A programmatic API key (bnq_live_...) gives non-interactive access for an organization. Create one with POST /v1/organizations/{id}/api-keys (organization_admin); the full key is shown once and stored only as a sha256 hash. Send it as Authorization: Bearer bnq_live_... or in the X-Api-Key header; the auth middleware resolves an api_key principal scoped to the organization. List with GET and revoke with DELETE /v1/organizations/{id}/api-keys/{keyId}.

Two-factor authentication

Accounts can enable TOTP two-factor auth: POST /v1/auth/2fa/setup returns a secret and an otpauth:// URI; POST /v1/auth/2fa/activate verifies a code and returns one-time recovery codes (shown once, hashed at rest); GET /v1/auth/2fa reports status; /disable and /recovery-codes require a current second factor. When 2FA is enabled, login requires the second factor: a request without it gets auth.mfa_required, and a wrong code gets auth.mfa_invalid. TOTP is RFC 6238 with a one-step skew; recovery codes are single-use.

Owner-controlled structured data for AI.