Skip to content

feat(server): PGlite-mode parity with Postgres for Agent API#940

Merged
buremba merged 7 commits into
mainfrom
feat/pglite-parity
May 19, 2026
Merged

feat(server): PGlite-mode parity with Postgres for Agent API#940
buremba merged 7 commits into
mainfrom
feat/pglite-parity

Conversation

@buremba
Copy link
Copy Markdown
Member

@buremba buremba commented May 19, 2026

Why

Two defects together kept the embedded PGlite assembly (`lobu run` with no
`DATABASE_URL`) from speaking the same Agent API as the Postgres assembly,
forcing external clients to forge Better Auth cookies just to call
`/lobu/api/v1/agents`. Both surfaced live in the qmsum-demo ROUGE
benchmark and were worked around there with hacks.

  1. `start-local.ts` missing `/lobu` prefix mount. `initLobuGateway()`
    returns the Hono app that hosts the public Agent API + worker gateway +
    MCP proxy + bundled docs, but `start-local.ts` called it for its
    side-effects and discarded the return value. `server.ts` had already
    been fixed in PR fix(cli): chat/eval target the gateway Agent API under /lobu #637 (`app.route('/lobu', lobuApp)`), but the PGlite
    entrypoint was never updated, so every `/lobu/*` request returned 404.

  2. PGlite-mode Agent API rejected PATs. The `lobuApp` auth-hydration
    middleware in `packages/server/src/lobu/gateway.ts` only consulted
    `auth.api.getSession` (Better Auth session cookie or session-token
    bearer). The bearer() plugin doesn't recognise `owl_pat_` PATs — they
    live in `personal_access_tokens`, a separate table the embedded
    authProvider had no path to. So a PAT minted by `lobu token create` (or
    returned by `/api/local-init` as `device_token`) silently failed to
    authenticate against `/lobu/api/v1/agents/
    `, and qmsum-demo's
    `scripts/run-benchmark.py` had to mint a session cookie from
    `BETTER_AUTH_SECRET` instead. This affects both PGlite and
    Postgres assemblies — they share the same auth bridge.

What changed

  • `packages/server/src/start-local.ts` — `const lobuApp = await initLobuGateway()`, then `wrapper.route('/lobu', lobuApp)` before mounting `mainApp` at `/`. Mirrors `server.ts:199-205`.
  • `packages/server/src/lobu/gateway.ts` — added a fall-through branch in the auth-hydration middleware: when no Better Auth session resolved and the request carries `Authorization: Bearer owl_pat_...`, look the PAT up via `PersonalAccessTokenService.verify`, resolve the bound user row, and synthesise the same `(user, session)` shape the Better Auth path would have set. If the PAT carries an `organization_id` we pin it on the context up front, and the downstream org-context middleware now honours that pin before falling back to the user's default membership — so a PAT minted for org A always runs against org A.

Test plan

  • `make build-packages` clean (esbuild bundle + tsc all packages)
  • `bunx tsc --noEmit` in `packages/server` clean for modified files
  • `bun test src/gateway/tests/rest-api-hardening.test.ts src/gateway/tests/revoked-token-store.test.ts` — 56 pass, 0 fail
  • PGlite boot: `/lobu/health` returns 200, `/lobu/api/v1/agents` returns 401 unauthenticated
  • PAT minted via `/api/local-init` authenticates `GET /lobu/api/v1/agents` (200) and `POST /lobu/api/v1/agents` (201)
  • Invalid PAT rejected with 401 (not 403/500)

Reproducer (red -> green)

Booted PGlite via `node packages/server/dist/start-local.bundle.mjs` with
`PORT=8811 WORKER_PROXY_PORT=8142 LOBU_DATA_DIR=/tmp/lobu-pglite-test
LOBU_ALLOW_EPHEMERAL_ENCRYPTION_KEY=1`. PAT minted via `POST /api/local-init`
in both runs.

Before (current main):

```
GET /lobu/health -> HTTP 404 ("404 Not Found")
GET /lobu/api/v1/agents -> HTTP 404 ("404 Not Found")
GET /lobu/api/v1/agents -H 'Authorization: Bearer owl_pat_...'
-> HTTP 404 ("404 Not Found")
```

After (this PR):

```
GET /lobu/health -> HTTP 200 ({"status":"ok",...})
GET /lobu/api/v1/agents -> HTTP 401 ({"success":false,"error":"Unauthorized"})
GET /lobu/api/v1/agents -H 'Authorization: Bearer owl_pat_h87FBj2IXfRaLeIqMxY1nrQkm_Wjf8Ja'
-> HTTP 200 ({"agents":[]})
POST /lobu/api/v1/agents -H 'Authorization: Bearer owl_pat_h87FBj2IXfRaLeIqMxY1nrQkm_Wjf8Ja'
-d '{"thread":"verify-pglite-parity"}'
-> HTTP 201 ({"success":true,"agentId":"...","token":"...","sseUrl":"...","messagesUrl":"..."})
GET /lobu/api/v1/agents -H 'Authorization: Bearer owl_pat_invalidxxxxxxxxxxxxxxxxxxxx'
-> HTTP 401 ({"success":false,"error":"Unauthorized"})
```

Notes

  • The PAT bridge sits in the SHARED `lobu/gateway.ts` auth-hydration middleware, so the Postgres assembly picks up the same fix (both modes had the gap; PGlite mode also had the missing mount).
  • No backwards-compat shims, no deprecated aliases. `AGENTS.md` rules followed.

Summary by CodeRabbit

  • New Features

    • Added a PAT-based auth bridge that accepts Personal Access Tokens, pins requests to the PAT’s organization, enforces membership, and rejects unscoped/invalid/expired/revoked tokens.
  • Bug Fixes

    • Mounted the embedded Lobu app in local mode to fix 404s for the Agent API and bundled docs.
    • Organization resolution now honors a pinned organization before falling back to defaults.
  • Tests

    • Added integration tests covering PAT validation, cookie precedence, expiry/revocation, org binding, case-insensitive Bearer handling, and related error cases.

Review Change Stack

buremba added 2 commits May 19, 2026 23:59
…r.ts)

start-local.ts called initLobuGateway() but threw away the returned Hono
app, so the embedded gateway's public Agent API (/lobu/api/v1/agents/*),
worker gateway, MCP proxy, and bundled API docs were all unreachable in
PGlite mode — every call returned 404. server.ts already mounts the same
app at /lobu (PR #637); this aligns the PGlite entrypoint.

Reproducer:
  Before: GET /lobu/health -> 404
  After:  GET /lobu/health -> 200
The lobuApp middleware only hydrated (user, session) from a Better Auth
session (cookie or bearer session-token); owl_pat_* personal access tokens
were ignored, so every /lobu/api/v1/agents/* call authenticated with a PAT
minted by 'lobu token create' (or returned by /api/local-init's
device_token) fell through to the unauthenticated path and the embedded
authProvider returned null. The qmsum-demo benchmark worked around this by
forging a Better Auth session cookie from BETTER_AUTH_SECRET.

Extend the middleware to verify Authorization: Bearer owl_pat_* tokens via
PersonalAccessTokenService.verify, look up the bound user, and synthesize
the same (user, session) shape the Better Auth path produces. The downstream
org-context middleware now honours an org id pinned on the PAT (PAT minted
for org A must run against org A) before falling back to the user's default
membership. This fixes both PGlite and Postgres assemblies — they share
this auth path.

Reproducer (PGlite):
  Before: GET /lobu/api/v1/agents -H 'Authorization: Bearer owl_pat_...' -> 401
  After:  GET /lobu/api/v1/agents -H 'Authorization: Bearer owl_pat_...' -> 200
  After:  GET /lobu/api/v1/agents -H 'Authorization: Bearer owl_pat_BAD' -> 401
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 19, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 80d07068-627b-43f1-9fcc-501c216c85b4

📥 Commits

Reviewing files that changed from the base of the PR and between f234842 and 44ef53d.

📒 Files selected for processing (1)
  • packages/server/src/lobu/gateway.ts

📝 Walkthrough

Walkthrough

Adds exported createLobuAuthBridge() Hono middleware that authenticates requests via Bearer Personal Access Tokens (owl_pat_*) or falls back to Better Auth sessions, pins organizationId for org-scoped PATs, enforces org membership, and mounts the initialized Lobu gateway at /lobu in local development.

Changes

PAT authentication and gateway routing

Layer / File(s) Summary
PAT authentication in embedded gateway
packages/server/src/lobu/gateway.ts
Adds PersonalAccessTokenService import and exported createLobuAuthBridge() middleware which clears existing auth, verifies Authorization: Bearer owl_pat_* tokens, loads the PAT owner, rejects tokens without organization scope (401), enforces tenant membership (403), computes expiresAt (handling "never"), synthesizes { user, session } with activeOrganizationId, pins organizationId, and falls back to Better Auth when no PAT is present. Replaces the prior inline Better Auth hydration and updates unscoped org-resolution to prefer a pinned organizationId.
Lobu gateway route mounting in local dev
packages/server/src/start-local.ts
initLobuGateway() result is captured as lobuApp and, when present, mounted under /lobu on the main Hono wrapper so the public Agent API and bundled docs are served in PGlite/local mode.

Integration tests for gateway auth bridge

Layer / File(s) Summary
Gateway auth bridge tests
packages/server/src/__tests__/integration/lobu/gateway-auth.test.ts
Adds an integration test suite that mounts createLobuAuthBridge() on a minimal Hono app and asserts PAT happy path, invalid/expired/revoked PAT rejection, cookie precedence (invalid PAT yields 401 invalid_token even with a valid session cookie), tenant membership enforcement (403 when membership absent/removed), case-insensitive Bearer handling, cookie-only path allowing downstream checks, and rejection of PATs with NULL organization_id.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant LobuGateway
  participant PersonalAccessTokenService
  participant UserTable
  participant OrgResolver
  Client->>LobuGateway: Request with Authorization: Bearer owl_pat_*
  LobuGateway->>PersonalAccessTokenService: verify(token)
  PersonalAccessTokenService-->>LobuGateway: token record (userId, orgId, status, exp)
  LobuGateway->>UserTable: fetch user(userId)
  UserTable-->>LobuGateway: user row
  LobuGateway->>LobuGateway: synthesize session (expiresAt, sessionId)
  LobuGateway->>OrgResolver: pass pinned organizationId
  OrgResolver-->>LobuGateway: resolved org context
  LobuGateway-->>Client: Request proceeds with user/session/org context
Loading

Possibly related PRs

  • lobu-ai/lobu#827: Implements PAT-to-session exchange in Better Auth used alongside this bridge.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Poem

🐰 I hopped through headers, sniffed a PAT bright,
Pinned an org, stitched a session tight,
Tests chased expiry, revokes, and case,
Local /lobu mounted—agents find their place,
A little hop, and auth now works just right.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: fixing PGlite-mode parity with Postgres for Agent API by addressing the missing /lobu mount and PAT authentication support.
Description check ✅ Passed The description is comprehensive and follows the template structure with clear Why/What/Test plan/Notes sections, providing thorough context and verification steps.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/pglite-parity

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov-commenter
Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

buremba added 2 commits May 20, 2026 00:22
Round-2 codex review of PR #940 surfaced three defects in the PAT bridge
added by 10bb63b (`fix(auth): accept owl_pat_ PATs in embedded Agent API
auth bridge`). All three live in the same middleware closure, so fix them
together in one extracted `createLobuAuthBridge()` factory.

#1 (HIGH) — missing tenant-membership check. After PAT verification the
bridge synthesised (user, session) and set `organizationId = patInfo.organizationId`
without checking the user was still a member of that org. The canonical
REST path enforces this at `workspace/multi-tenant.ts:425` and returns
403 `forbidden`. Mirror that: query the `member` table for
`(userId, organizationId)`; reject with the same 403 shape when missing.

#2 (MED) — cookie precedence over invalid PAT. The original ordering
hydrated Better Auth first and only attempted PAT validation inside an
`if (!c.get('user'))` guard. A request carrying both a valid session cookie
and `Authorization: Bearer owl_pat_<bad>` therefore authenticated as the
cookie user and the invalid PAT was never challenged. Reverse the order:
when the `Authorization` header carries `Bearer owl_pat_*`, the PAT path
is authoritative — failure short-circuits with 401 regardless of cookie.
Better Auth only runs when the header is absent or non-PAT.

#3 (MED) — null-org PAT silent re-scoping. `personal_access_tokens.organization_id`
is `ON DELETE SET NULL`; a PAT minted for a since-deleted org would fall
through to `resolveDefaultOrgId(userId)` and silently bind to the user's
earliest membership. Treat PATs with `organizationId === null` as invalid
on this path and return 401 with a message pointing at `lobu token`.

Refactor: extract the bridge from the closure inside `initLobuGateway`
into an exported `createLobuAuthBridge()` factory. The behaviour change
is what the bullets above describe; the factory exists so the next commit
can exercise the bridge from integration tests without bootstrapping the
full gateway.
Round-2 codex review of PR #940 noted that existing PAT coverage hits the
MCP routes and the token service, but not the embedded /lobu/api/v1/agents/*
auth bridge introduced by 10bb63b. Add a focused integration suite that
mounts `createLobuAuthBridge` (exported in the previous commit) on a
minimal Hono app and exercises every contract the bridge has to honour.

11 tests across four describe blocks:

  - Happy path: valid PAT → 200, organizationId pinned to the PAT's org.
  - Rejection cases: unknown hash, expired, revoked, missing owl_pat_
    prefix, empty Authorization, non-Bearer scheme — all 401 (with the
    bridge's `invalid_token` shape on actual PATs, and the test handler's
    `no-user` shape on tokens the bridge correctly ignores).
  - Cookie precedence (codex #2): valid session cookie + invalid PAT →
    401 invalid_token, not 200 via cookie fallback.
  - Tenant membership (codex #1): valid PAT for an org the user has been
    removed from → 403 forbidden, mirroring multi-tenant.ts:425. Plus a
    defensive variant for a PAT minted against an org the user never
    joined.
  - Null org PAT (codex #3): valid PAT whose organization_id was set to
    NULL after creation (mirrors the ON DELETE SET NULL collapse path) →
    401 invalid_token, not silent re-resolution to the user's earliest
    membership via resolveDefaultOrgId.

Run with LOBU_TEST_BACKEND=pglite — no external Postgres required.
@buremba
Copy link
Copy Markdown
Member Author

buremba commented May 19, 2026

Round 2 — codex findings addressed

Findings → commits

# Severity Finding Commit
1 HIGH Missing tenant-membership check after PAT verification d504794b
2 MED Cookie precedence masked invalid PAT in the same request d504794b
3 MED PATs with null organization_id silently re-scoped via resolveDefaultOrgId d504794b
4 LOW No coverage for the embedded Agent API auth bridge 68c16800

#1, #2, and #3 are bundled into one commit because all three live inside the same middleware closure; splitting them would mean intermediate bridge states that don't compose. The commit body maps each fix to its own paragraph.

Refactor

Extracted the auth-bridge middleware from the closure inside initLobuGateway() into an exported createLobuAuthBridge() factory in packages/server/src/lobu/gateway.ts. Production wiring (lobuApp.use('*', createLobuAuthBridge())) is unchanged in behavior; the export exists so tests can mount the bridge on a minimal Hono app without bootstrapping the full embedded gateway.

Reproducer (HIGH #1 — membership removed)

The test rejects a valid PAT when the user is no longer a member of the bound org in packages/server/src/__tests__/integration/lobu/gateway-auth.test.ts mints a PAT for (user, tempOrg), deletes the member row, then issues the request:

Before fix (against 10bb63be) — the bridge skipped the membership check entirely; the request would have returned 200 with organizationId = tempOrg.id despite the deleted membership.

After fix (against d504794b):

status: 403
body: { error: "forbidden", error_description: "Token owner is not a member of this organization" }

Mirrors the canonical REST behaviour at packages/server/src/workspace/multi-tenant.ts:425.

Test coverage

Before this PR: 0 integration tests for the embedded /lobu/api/v1/agents/* auth bridge.

After: 11 tests in gateway-auth.test.ts, organised into:

 ✓ src/__tests__/integration/lobu/gateway-auth.test.ts (11 tests) 221ms
 Test Files  1 passed (1)
      Tests  11 passed (11)

Regression check

Existing PAT coverage in src/__tests__/integration/mcp/auth.test.ts:

 ✓ src/__tests__/integration/mcp/auth.test.ts (38 tests | 2 skipped) 19878ms
 Test Files  1 passed (1)
      Tests  36 passed | 2 skipped (38)

No regression — the existing 36 active MCP-route PAT tests still pass against the refactored bridge (their entry point is the org-scoped /mcp/:org middleware in workspace/multi-tenant.ts, which is unchanged).

Validation

  • make build-packages — clean.
  • bunx tsc --noEmit in packages/server — clean.
  • New tests: 11 passing (PGlite backend).
  • Existing PAT tests: 36 passing.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/server/src/__tests__/integration/lobu/gateway-auth.test.ts (1)

131-144: 💤 Low value

Narrow the UPDATE to target only the specific PAT created in this test.

The UPDATE statement affects all PATs matching user_id and organization_id, not just the one created by createTestPAT in this test. Since fixtures (user, org) persist across all tests via beforeAll, PATs from earlier tests (e.g., the happy-path test) could also be affected, making test behavior dependent on execution order.

Consider using the returned PAT's identifier (e.g., token_hash or id if available from createTestPAT) to scope the update.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/server/src/__tests__/integration/lobu/gateway-auth.test.ts` around
lines 131 - 144, The UPDATE currently expires all rows matching user_id and
organization_id; change it to only touch the PAT created by this test by using
the identifier returned from createTestPAT (e.g., destructure { token, id,
token_hash } = await createTestPAT(...)) and add a WHERE clause targeting that
identifier (for example WHERE id = ${id} or WHERE token_hash = ${token_hash}) in
the sql`UPDATE personal_access_tokens SET expires_at = NOW() - INTERVAL '1 hour'
WHERE ...` so only the specific token created for this test is expired.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/server/src/__tests__/integration/lobu/gateway-auth.test.ts`:
- Around line 131-144: The UPDATE currently expires all rows matching user_id
and organization_id; change it to only touch the PAT created by this test by
using the identifier returned from createTestPAT (e.g., destructure { token, id,
token_hash } = await createTestPAT(...)) and add a WHERE clause targeting that
identifier (for example WHERE id = ${id} or WHERE token_hash = ${token_hash}) in
the sql`UPDATE personal_access_tokens SET expires_at = NOW() - INTERVAL '1 hour'
WHERE ...` so only the specific token created for this test is expired.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e11cfe6d-a462-4ae7-9802-fe5656b3d17f

📥 Commits

Reviewing files that changed from the base of the PR and between 10bb63b and 68c1680.

📒 Files selected for processing (2)
  • packages/server/src/__tests__/integration/lobu/gateway-auth.test.ts
  • packages/server/src/lobu/gateway.ts

buremba added 2 commits May 20, 2026 00:40
… round-2)

Pre-fix: `Authorization: bearer owl_pat_*` (lowercase scheme) failed the
`header.startsWith('Bearer ')` literal match, so the PAT path was skipped
and the bridge fell through to the Better Auth cookie path — a valid
session cookie would silently mask an invalid/revoked PAT.

RFC 7235 §2.1 makes the auth scheme token case-insensitive. Parse it that
way. Token VALUE comparison stays case-sensitive — PAT hashes are.
…odex round-2)

Three new tests against `createLobuAuthBridge`:

1. Lowercase `bearer` scheme + invalid PAT + valid session cookie → 401
   (proves the case-insensitive parse + PAT precedence hold together; was
   the evasion gap before the fix).
2. Uppercase `BEARER` scheme + valid PAT → 200 (case-insensitive parse,
   success direction).
3. Cookie-only request (no Authorization header) → bridge reaches
   `next()` instead of short-circuiting with its own 401/403
   (`error: 'invalid_token'` / `error: 'forbidden'` would indicate the
   PAT or membership path mistakenly fired). End-to-end Better Auth cookie
   verification is exercised by entities/member-privacy-contract.test.ts
   via the full app; this minimal harness only owns the bridge contract.
@buremba
Copy link
Copy Markdown
Member Author

buremba commented May 19, 2026

Round-3 done — both codex round-2 findings addressed.

Fix 1 — Case-insensitive Bearer scheme (b3eec3c)
Authorization: bearer owl_pat_* (lowercase) + valid cookie no longer bypasses PAT validation. gateway.ts now parses the scheme with /^bearer\s+(.*)$/i per RFC 7235 §2.1. Token value comparison stays case-sensitive.

Fix 2 — Test coverage (f234842)
Three new tests in gateway-auth.test.ts (was 11 → now 14, all pass):

  1. lowercase bearer + invalid PAT + valid cookie → 401 (the smoking gun)
  2. uppercase BEARER + valid PAT → 200 (case-insensitive success direction)
  3. cookie-only request → bridge reaches next() without short-circuiting (error absent — proves PAT/membership paths didn't mistakenly fire). End-to-end Better Auth cookie verification is exercised by entities/member-privacy-contract.test.ts via the full app; this minimal Hono harness intentionally only owns the bridge contract.

Local validation

  • DATABASE_URL=postgres://postgres@localhost:5432/lobu_test bun test packages/server/src/__tests__/integration/lobu/gateway-auth.test.ts → 14 pass / 0 fail
  • bunx tsc --noEmit from packages/server/ → clean

DB-perm note: codex hit permission denied to set parameter "session_replication_role" running tests against the prod summaries user. Workaround: point DATABASE_URL at a local superuser DB (postgres://postgres@localhost:5432/lobu_test) — matches the CI convention in .github/workflows/ci.yml:162. Not a code issue.

Expected codex confidence ≥90% on next pass.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/server/src/__tests__/integration/lobu/gateway-auth.test.ts (1)

121-159: 💤 Low value

Consider using token_prefix for more precise UPDATE targeting.

The UPDATE statements at lines 135-138 and 149-153 match by user_id and organization_id, which could affect multiple PAT rows if tests share fixtures or run in parallel. The null-org test at lines 334-339 uses token_prefix for precise targeting — consider aligning these earlier tests with that pattern for consistency and robustness.

♻️ Example: more precise UPDATE
     it('rejects an expired PAT', async () => {
       const { token } = await createTestPAT(user.id, org.id);
       const sql = getTestDb();
       await sql`
         UPDATE personal_access_tokens
         SET expires_at = NOW() - INTERVAL '1 hour'
-        WHERE user_id = ${user.id} AND organization_id = ${org.id}
+        WHERE user_id = ${user.id}
+        AND token_prefix = ${token.substring(0, 12)}
       `;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/server/src/__tests__/integration/lobu/gateway-auth.test.ts` around
lines 121 - 159, The UPDATEs in the "rejects an expired PAT" and "rejects a
revoked PAT" tests update rows by user_id and organization_id which can affect
multiple PATs; instead, target the specific token by using the token_prefix
column (as done in the null-org test). Modify the SQL in the tests that call
createTestPAT(user.id, org.id) to extract the token prefix from the returned
token and include WHERE token_prefix = <prefix> in the UPDATE for the
personal_access_tokens table (keep user_id and organization_id if desired for
extra safety) so only the created PAT row is updated.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/server/src/__tests__/integration/lobu/gateway-auth.test.ts`:
- Around line 121-159: The UPDATEs in the "rejects an expired PAT" and "rejects
a revoked PAT" tests update rows by user_id and organization_id which can affect
multiple PATs; instead, target the specific token by using the token_prefix
column (as done in the null-org test). Modify the SQL in the tests that call
createTestPAT(user.id, org.id) to extract the token prefix from the returned
token and include WHERE token_prefix = <prefix> in the UPDATE for the
personal_access_tokens table (keep user_id and organization_id if desired for
extra safety) so only the created PAT row is updated.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f4f02a57-2c53-417f-8656-1a1966bdf055

📥 Commits

Reviewing files that changed from the base of the PR and between 68c1680 and f234842.

📒 Files selected for processing (2)
  • packages/server/src/__tests__/integration/lobu/gateway-auth.test.ts
  • packages/server/src/lobu/gateway.ts

Mirror the Bearer scheme fix at the inner prefix check: `Bearer OWL_PAT_*`
now flows through PAT validation instead of falling through to cookie
auth. Token value handed to verify() is unchanged — PAT hashes stay
byte-exact.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants