Add self-service API tokens (PATs) — the UI had no way to create one

jump-host had zero API-token support: no model, no route, no UI, and
Auth.checkApiToken was explicitly absent from the createOidcClient() call
(per the comment it left behind). proxy and sso-manager-node both have
this; jump-host didn't.

Ports proxy's models/api_token.js + routes/api_token.js pattern (jmp_
prefix instead of prx_), wires checkApiToken into createOidcClient(), adds
Bearer-token support to middleware/auth.js, and adds a token management
card to dashboard.ejs (create/list/rotate/revoke) using app.modal/
app.messages.

Scope note: a jump-host token carries no group claims (unlike proxy's,
which snapshots the creator's groups), so it authenticates as its creator
for non-admin routes (e.g. GET /api/user/hosts) but can never pass
requireAdmin — a deliberate, conservative default rather than recomputing
live admin status per-request.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-27 19:44:04 -04:00
parent 4a70b5b27e
commit 4326d5588e
7 changed files with 348 additions and 5 deletions
+7 -4
View File
@@ -32,14 +32,17 @@ async function getRedis() {
module.exports.getRedis = getRedis;
// Register models (order matters: User before AuthToken's relation resolves).
// Register models (order matters: User before AuthToken's relation resolves,
// and before ApiToken so `require('.')`'s Table is already exporting User).
require('./user_redis'); // User (redis-backed local + OIDC JIT)
const { ApiToken } = require('./api_token');
module.exports.ApiToken = ApiToken;
// Shared OIDC client (authorization-code + PKCE): session models (Token,
// AuthToken, OidcState), the Auth service, and the /login /logout /oidc/start
// /oidc/callback router — all created on this app's Table/redis. jump-host has
// no Bearer PATs, so checkApiToken is omitted (Auth.checkApiToken is absent).
const oidcClient = createOidcClient({ Table });
// /oidc/callback router — all created on this app's Table/redis. checkApiToken
// wraps ApiToken.authenticate, same wiring as proxy's models/index.js.
const oidcClient = createOidcClient({ Table, checkApiToken: (raw) => ApiToken.authenticate(raw) });
module.exports.Token = oidcClient.Token;
module.exports.AuthToken = oidcClient.AuthToken;
module.exports.OidcState = oidcClient.OidcState;