Files
mcias/web/templates/fragments/token_list.html
Kyle Isom d4e8ef90ee Add policy-based authz and token delegation
- Replace requireAdmin (role-based) guards on all REST endpoints
  with RequirePolicy middleware backed by the existing policy engine;
  built-in admin wildcard rule (-1) preserves existing admin behaviour
  while operator rules can now grant targeted access to non-admin
  accounts (e.g. a system account allowed to list accounts)
- Wire policy engine into Server: loaded from DB at startup,
  reloaded after every policy-rule create/update/delete so changes
  take effect immediately without a server restart
- Add service_account_delegates table (migration 000008) so a human
  account can be delegated permission to issue tokens for a specific
  system account without holding the admin role
- Add token-download nonce mechanism: a short-lived (5 min),
  single-use random nonce is stored server-side after token issuance;
  the browser downloads the token as a file via
  GET /token/download/{nonce} (Content-Disposition: attachment)
  instead of copying from a flash message
- Add /service-accounts UI page for non-admin delegates
- Add TestPolicyEnforcement and TestPolicyDenyRule integration tests

Security:
- Policy engine uses deny-wins, default-deny semantics; admin wildcard
  is a compiled-in built-in and cannot be deleted via the API
- Token download nonces are 128-bit crypto/rand values, single-use,
  and expire after 5 minutes; a background goroutine evicts stale entries
- alg header validation and Ed25519 signing unchanged

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-15 14:40:16 -07:00

56 lines
1.7 KiB
HTML

{{define "token_list"}}
<div id="token-list">
{{if .Flash}}
<div class="alert alert-success" role="alert" style="margin-bottom:1rem">
{{.Flash}}
{{if .DownloadNonce}}
<div style="margin-top:.5rem">
<a class="btn btn-sm btn-secondary"
href="/token/download/{{.DownloadNonce}}">Download token as file</a>
</div>
{{end}}
</div>
{{end}}
{{if .Tokens}}
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>JTI</th><th>Issued</th><th>Expires</th><th>Status</th><th>Action</th>
</tr>
</thead>
<tbody>
{{range .Tokens}}
<tr id="token-row-{{truncateJTI .JTI}}">
<td><code style="font-size:.75rem">{{truncateJTI .JTI}}</code></td>
<td class="text-small text-muted">{{formatTime .IssuedAt}}</td>
<td class="text-small text-muted">{{formatTime .ExpiresAt}}</td>
<td>
{{if .IsRevoked}}
<span class="badge badge-deleted">revoked</span>
{{else if .IsExpired}}
<span class="badge badge-inactive">expired</span>
{{else}}
<span class="badge badge-active">active</span>
{{end}}
</td>
<td>
{{if not .IsRevoked}}
<button class="btn btn-sm btn-danger"
hx-delete="/token/{{.JTI}}"
hx-target="#token-row-{{truncateJTI .JTI}}"
hx-swap="outerHTML"
hx-confirm="Revoke this token?">Revoke</button>
{{end}}
</td>
</tr>
{{end}}
</tbody>
</table>
</div>
{{else}}
<p class="text-muted text-small">No tokens issued.</p>
{{end}}
</div>
{{end}}