feat: Authentik OIDC SSO, allow/deny RBAC, admin console & Gitea CI
All checks were successful
build-and-push / build (push) Successful in 15m15s
All checks were successful
build-and-push / build (push) Successful in 15m15s
Authentication & RBAC
- Add confidential OIDC client (Authentik) with /auth/oidc/login +
/auth/oidc/callback: discovery, code exchange, id_token verify (go-oidc),
groups claim → role (Archivum-admin → admin, else user). Sessions carry groups.
- Rework ACL into an allow/deny model (new `effect` column + migration).
db.EffectiveAccess resolves user + all groups over the path and its ancestors:
default deny, explicit deny always beats allow.
- Enforce ACL for ALL non-admin users (not just guest) across list/read/save/
delete/move/create/history/diff/images/upload. Admins bypass.
- Seed built-in Archivum-admin / Archivum-reader groups; login allow-list on
users & groups; public (guest) user access is ACL-configurable.
Admin API & UI
- New GraphQL ops: oidcConfig/updateOidcConfig, group CRUD, membership,
setUserRole/setUserLogin/setGroupLogin, userGroups, loginOptions.
- Rebuilt AdminView: SSO config, user/group management + membership, login
toggles, and an allow/deny access-control matrix per path.
- LoginView: "Sign in with Authentik" + public-user option; OIDC callback route.
Rendering/editor
- Fix bug where inline marks (bold/italic/code/strike/link) were dropped on
TipTap→AsciiDoc save. Add RENDERING_IMPROVEMENTS.md with proposals.
CI / build
- .gitea/workflows/build.yaml: build on the Pi5 runner, push
localhost:5000/archivum:{latest,<sha>}. Add .dockerignore; bump Go image to 1.25.
- Docs: ARCHITECTURE.md, README.md, docs/AUTHENTIK_SETUP.md.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Config holds all backend runtime configuration.
|
||||
@@ -11,8 +12,13 @@ type Config struct {
|
||||
StoragePath string `json:"storage_path"`
|
||||
DBPath string `json:"db_path"`
|
||||
LDAP LDAPConfig `json:"ldap"`
|
||||
OIDC OIDCConfig `json:"oidc"`
|
||||
JWTSecret string `json:"jwt_secret"`
|
||||
ListenAddr string `json:"listen_addr"`
|
||||
// PublicURL is the externally reachable base URL of Archivum
|
||||
// (e.g. https://archivum.brasse-pc.eu). Used to build the OIDC
|
||||
// redirect URL when one is not set explicitly.
|
||||
PublicURL string `json:"public_url"`
|
||||
}
|
||||
|
||||
type LDAPConfig struct {
|
||||
@@ -22,6 +28,50 @@ type LDAPConfig struct {
|
||||
AdminPass string `json:"admin_pass"`
|
||||
}
|
||||
|
||||
// OIDCConfig configures Single-Sign-On via an OpenID Connect provider
|
||||
// (Authentik in this deployment). The provider authenticates the user and
|
||||
// returns their group membership in the groups claim.
|
||||
type OIDCConfig struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
Issuer string `json:"issuer"` // e.g. https://authentik.brasse-pc.eu/application/o/archivum/
|
||||
ClientID string `json:"client_id"`
|
||||
ClientSecret string `json:"client_secret"`
|
||||
RedirectURL string `json:"redirect_url"` // optional; derived from PublicURL when empty
|
||||
// Claim/group mapping. Sensible defaults are applied in Normalize().
|
||||
GroupsClaim string `json:"groups_claim"` // default "groups"
|
||||
UsernameClaim string `json:"username_claim"` // default "preferred_username"
|
||||
AdminGroup string `json:"admin_group"` // default "Archivum-admin"
|
||||
ReaderGroup string `json:"reader_group"` // default "Archivum-reader"
|
||||
}
|
||||
|
||||
// Normalize fills in defaults for optional OIDC fields.
|
||||
func (o *OIDCConfig) Normalize() {
|
||||
if o.GroupsClaim == "" {
|
||||
o.GroupsClaim = "groups"
|
||||
}
|
||||
if o.UsernameClaim == "" {
|
||||
o.UsernameClaim = "preferred_username"
|
||||
}
|
||||
if o.AdminGroup == "" {
|
||||
o.AdminGroup = "Archivum-admin"
|
||||
}
|
||||
if o.ReaderGroup == "" {
|
||||
o.ReaderGroup = "Archivum-reader"
|
||||
}
|
||||
}
|
||||
|
||||
// ResolvedRedirectURL returns the OIDC callback URL, deriving it from
|
||||
// PublicURL when RedirectURL is not set.
|
||||
func (c *Config) ResolvedRedirectURL() string {
|
||||
if c.OIDC.RedirectURL != "" {
|
||||
return c.OIDC.RedirectURL
|
||||
}
|
||||
if c.PublicURL != "" {
|
||||
return strings.TrimRight(c.PublicURL, "/") + "/auth/oidc/callback"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// ErrRequireSetup is returned when config is missing or empty,
|
||||
// signalling that the frontend should start the Setup Wizard.
|
||||
var ErrRequireSetup = errors.New("REQUIRE_SETUP")
|
||||
@@ -54,6 +104,8 @@ func Load(path string) (*Config, error) {
|
||||
cfg.DBPath = "/data/db/archivum.db"
|
||||
}
|
||||
|
||||
cfg.OIDC.Normalize()
|
||||
|
||||
return &cfg, nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user