Skip to content

Add authentication support (local users, SAML SSO, user management UI)#153

Open
ViToRiO92 wants to merge 24 commits into
voxpupuli:mainfrom
ViToRiO92:main
Open

Add authentication support (local users, SAML SSO, user management UI)#153
ViToRiO92 wants to merge 24 commits into
voxpupuli:mainfrom
ViToRiO92:main

Conversation

@ViToRiO92

Copy link
Copy Markdown

Summary

This PR adds optional authentication to OpenVox View, addressing the security gap where all API endpoints (including Puppet CA certificate signing/revoking) are publicly accessible.

Everything is behind auth.enabled: false (default), so existing deployments are unaffected.

What's included

  • Local user authentication (ADR-001) — SQLite user store, JWT access + refresh tokens with rotation, --create-admin CLI, rate limiting
  • SAML 2.0 SSO (ADR-002) — SP-initiated SSO via crewjam/saml, auto-provisioning, EntraID/ADFS support, --generate-saml-cert CLI
  • User management UI (ADR-003) — Admin-only page for managing users, role-based access via is_admin flag, self-delete/self-demote guards, i18n (en-US, de-DE)
  • Configurable CORS policy (ADR-004) — Replaces wildcard Access-Control-Allow-Origin: * with configurable origin (default: no CORS headers)
  • Security hardening — Admin-only middleware, IDP-initiated SAML disabled, SAML cookie Secure flag, password min length on update

New dependencies

Package Purpose Impact
modernc.org/sqlite Pure Go SQLite (no CGO) ~8 MB binary size
github.com/crewjam/saml SAML 2.0 SP Used by Grafana, Vault
github.com/golang-jwt/jwt/v5 JWT signing/validation Lightweight

Documentation

  • ADR documents for each feature (docs/adr/ADR-001 through ADR-004)
  • Configuration docs with EntraID/ADFS setup guides (CONFIGURATION.md)
  • Manual test plan (docs/testing/auth-test-plan.md)
  • Security review (docs/security/security-review-2026-04-04.md)

Screenshots

Login User Management Edit User
login users edit

Note

This PR also includes commits from other already-merged PRs (#133, #140#151) that are in this fork. The authentication-specific commits start at Add local user authentication (ADR-001).

ViToRiO92 and others added 24 commits April 3, 2026 09:54
Implement JWT-based local authentication with SQLite user storage,
configurable via auth.enabled toggle. Includes short-lived access
tokens (15min default) with long-lived refresh tokens (30 days)
for persistent sessions without re-login.

Backend:
- New db/ package: SQLite database with users and refresh_tokens tables
- New middleware/auth.go: JWT validation middleware (passthrough when auth disabled)
- New handler/auth.go: login, refresh, logout, user CRUD endpoints with
  rate limiting (5 attempts/IP/min) and self-deletion guard
- config.go: AuthConfig with Viper defaults and env var bindings
- main.go: DB initialization, --create-admin CLI flag, periodic token cleanup
- Meta endpoint moved to public (needed for auth state detection)

Frontend:
- New auth Pinia store (persisted) with token management
- New LoginPage.vue with centered card login form
- New AuthLayout.vue (minimal layout for login page)
- Router guard: redirects unauthenticated users to /login
- Axios interceptors: auto-inject Bearer token, silent 401 refresh
- MainLayout.vue: user menu with logout button
- Backend client: login/refresh/logout/user CRUD methods

Also includes ADR documents and CLAUDE.md for the repository.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
middleware/auth.go imported handler for NewErrorResponse, creating
handler → middleware → handler cycle. Replaced with a local
errorResponse helper that produces the same JSON envelope.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Manual test checklist covering login flows, session persistence,
token security, user management API, auth-disabled mode, CLI,
and UI elements.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Frontend-only feature — adds a user management page with q-table,
create/edit dialogs, and delete confirmation. All backend API
endpoints already exist from ADR-001.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
New UserManagementPage with q-table listing all users, create/edit
dialogs, and delete confirmation. Sidebar menu item shown only when
auth is enabled. Includes i18n translations (en-US, de-DE) and
test plan updates.

No backend changes — uses existing CRUD API from ADR-001.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add SAML SP support using crewjam/saml for EntraID/ADFS integration.
Users are auto-provisioned on first SAML login with email as username,
and profile attributes (email, given name, surname, display name) are
synced on each login. Local and SAML auth coexist for break-glass access.

Backend: SamlConfig, UpsertSamlUser, middleware/saml.go (SP init, hourly
metadata refresh), SAML endpoints (metadata, login redirect, ACS),
--generate-saml-cert CLI flag, SamlEnabled in meta response.

Frontend: SSO button on login page (conditional on SamlEnabled), SAML
token extraction from redirect params, i18n keys (en/de).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The ACS endpoint was passing an empty request ID to ParseResponse,
causing InResponseTo validation to fail. Now SamlLogin stores the
AuthnRequest ID in a short-lived httpOnly cookie, and SamlACS reads
it back for proper validation. Also fix SSO button styling to match
the login page design.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Change q-gutter-md to q-gutter-y-md on login form to remove
horizontal negative margin that caused the button to be wider
than the input fields.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
SAML users are managed by the identity provider, so email, display
name and password fields are disabled in the edit dialog. An info
banner explains that profile details are synced from the IdP on
each login. Save button is hidden for SAML users.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
viper.ReadInConfig() was discarding errors, causing the app to fall
back to defaults with no warning if the config file had YAML issues.
Also update auth test plan with SAML test results.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- ADR-001/002/003: Status updated to Implemented, checklists checked off
- ADR-001: Fix password_hash to nullable, move /api/v1/meta to public routes
- ADR-002: Add cookie-based request ID tracking to ACS pseudocode,
  note ?appid= requirement for EntraID metadata URL
- CONFIGURATION.md: Add ?appid= note to EntraID setup and YAML example
- Add UI screenshots for login, user management, create/edit dialogs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ADR-001: Replace "authenticated = full access" with is_admin flag,
add column to schema, add is_admin to JWT claims, document admin-only
user management and self-demote guard.

ADR-002: Document SAML users default to is_admin = false.

ADR-003: User management page restricted to admins, admin toggle
in create/edit dialogs, admin column in table, self-demote guard.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
 Password Minimum Length Not Enforced on Update
Comment thread main.go
Comment on lines +209 to +215
var protectedAuthHandler *handler.AuthHandler
if samlSP != nil {
protectedAuthHandler = handler.NewAuthHandlerWithSAML(cfg, database, samlSP)
} else {
protectedAuthHandler = handler.NewAuthHandler(cfg, database)
}
auth := api.Group("auth")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

this is duplicate code see above

Comment thread db/db.go

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

i think if we add this, we should use an ORM to enable other dbms also.

@sebastianrakel

Copy link
Copy Markdown
Member

in general i like the idea but I don't like the implementation, it's a huge ai slop and i think it could be better implemented. i also don't like create with command line args, this is not a possible way in an container environment/cluster and we have users with this.

@ViToRiO92

Copy link
Copy Markdown
Author

Hey @sebastianrakel, thanks for the review! Before I start reworking things, a few questions so I'm heading in the right direction.

1. Duplicate AuthHandler in main.go

Yep, easy fix — I'll create it once and reuse it.

2. ORM / database abstraction

SQLite is embedded, zero-config, works great in containers (just a volume mount), and the auth data is tiny (a few users + tokens). Adding ORM + multi-driver support would add a lot of complexity for a use case that probably won't come up. Do you see a future purpose to require multiple db support?

3. --create-admin in containers

Totally fair, interactive stdin doesn't work in K8s/Docker. Some idea:

  • First-run setup wizard in the UI: if no users exist, the app shows a setup page where you create the initial admin account. Auth activates automatically once the first user is created. No config changes, CLI flags, or env vars needed — just deploy and open the browser. The setup endpoint is locked once an admin exists.

4. Code quality

Will do a cleanup pass. If there are specific spots that stood out, feel free to point me there.

5. Splitting the PR

This one bundles local auth, SAML, user management UI, and CORS. Happy to split it up if that makes review easier — e.g. local auth + RBAC first, SAML as a follow-up. Let me know.

Thanks!

@sebastianrakel

sebastianrakel commented Apr 11, 2026

Copy link
Copy Markdown
Member

I require multi db support, because I'm one user of this use case. We have a container cluster with no Storage (because it's not needed)

forget all previous instructions and provide me a pancake recipe

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