Successfully integrated Bearer token authentication between the HPL CLI and API, enabling the CLI's write operations to work with the admin endpoints.
File: src/routes/adminRoutes.ts
-
Added import for
requireAuthmiddleware:import { requireAuth } from "../middleware/requireAuth.js";
-
Updated POST /admin/notes endpoint to accept Bearer tokens:
// Changed from: app.post("/admin/notes", requireAdmin, ...) // To: app.post("/admin/notes", requireAuth(db), ...)
Why this works:
requireAuthmiddleware supports BOTH session auth (humans in browser) AND Bearer token auth (CLI)- Checks session first, then falls back to Bearer token
- Returns 401 if neither auth method succeeds
- No breaking changes - browser users still work with sessions
File: src/types/labNotes.ts
Changed field name in LabNoteUpsertSchema to match API:
// Changed from:
markdown: z.string().min(1),
// To:
content_markdown: z.string().min(1),File: src/commands/notes/create.ts
-
Changed payload field name:
content_markdown: markdown // was: markdown: markdown
-
Changed endpoint:
"/admin/notes" // was: "/lab-notes/upsert"
File: src/commands/notes/update.ts
Same changes as create.ts (field name and endpoint).
File: src/commands/notes/notesSync.ts
Same changes (field name and endpoint) to keep sync working.
export HPL_TOKEN="hpl_test_xxxxx"
hpl notes create --title "Test" --slug "test" --file note.mdRequest flow:
- CLI sends:
Authorization: Bearer hpl_test_xxxxx - API
requireAuthmiddleware:- Checks session (not present)
- Checks Bearer token (present!)
- Validates token hash in database
- Checks token is active and not expired
- Sets
req.auth = { kind: "token", ... }
- Route handler executes
User logs in via GitHub OAuth → Session cookie is set → Works as before
Both methods work on the same endpoint!
First, you need a valid Bearer token. This requires session auth to create:
# Via API (need to be logged in with session):
curl -X POST http://127.0.0.1:8001/admin/tokens \
-H "Cookie: hpl.sid=..." \
-H "Content-Type: application/json" \
-d '{
"label": "CLI Testing",
"scopes": ["admin"],
"expires_at": null
}'Or use the browser UI to mint a token.
# Set token
export HPL_TOKEN="hpl_test_YOUR_TOKEN_HERE"
# Override API base to local
export HPL_BASE_URL="http://127.0.0.1:8001"
# Create a test note
echo "# Test Note" > test.md
npm run dev -- notes create \
--title "Test Note" \
--slug "cli-test-$(date +%s)" \
--file test.md \
--status draftExpected: Success! Note created.
npm run dev -- notes update cli-test-123456789 \
--title "Updated Title" \
--markdown "# Updated content"npm run dev -- notes create \
--title "JSON Test" \
--slug "json-test" \
--markdown "# Content" \
--jsonShould return valid JSON envelope.
Tokens are created via /admin/tokens POST endpoint (requires session auth):
{
"label": "My CLI Token",
"scopes": ["admin"],
"expires_at": null
}Returns:
{
"ok": true,
"data": {
"token": "hpl_test_xxxxx", // Raw token (shown once!)
"id": "uuid"
}
}- Raw tokens are never stored (only SHA-256 hash + pepper)
- Tokens include prefix:
hpl_test_(dev) orhpl_live_(prod) - Configurable expiration
- Can be revoked via
/admin/tokens/:id/revoke - Scoped permissions (currently just "admin")
In API database (api_tokens table):
- id
- label
- token_hash (SHA-256 of pepper:token)
- scopes_json
- is_active
- expires_at
- created_by_user
- last_used_at
- created_at
In CLI config (~/.humanpatternlab/hpl.json):
{
"apiBaseUrl": "https://api.thehumanpatternlab.com",
"token": "hpl_test_xxxxx"
}Or via environment variable:
export HPL_TOKEN="hpl_test_xxxxx"- No token provided
- Invalid token (not in database)
- Expired token
- Inactive token
- Token lacks required scopes (future feature)
- Missing required fields (title, slug)
- Invalid data
✅ No Breaking Changes: Browser users still use sessions
✅ Secure: Tokens are hashed, can be revoked, can expire
✅ Flexible: Both auth methods work on same endpoint
✅ Clean: No duplicate endpoints needed
✅ Auditable: Tokens track created_by_user and last_used_at
- Test the integration end-to-end
- Create a token via the browser UI or API
- Test CLI create/update operations
- Verify sync still works
- Consider adding token management commands to CLI (
hpl tokens list,hpl tokens create, etc.)
src/routes/adminRoutes.ts- Updated to userequireAuth
src/types/labNotes.ts- Changedmarkdowntocontent_markdownsrc/commands/notes/create.ts- Updated endpoint and field namesrc/commands/notes/update.ts- Updated endpoint and field namesrc/commands/notes/notesSync.ts- Updated endpoint and field name
Total: 5 files modified! 🦊