skills/auth.go
2026-05-12 19:08:00 +02:00

91 lines
1.8 KiB
Go

package main
import (
"context"
"database/sql"
"net/http"
"strings"
)
type contextKey string
const userContextKey contextKey = "auth_user"
type User struct {
Sub string
Email string
Name string
}
type Auth struct {
devMode bool
db *sql.DB
}
func NewAuth(cfg Config) (*Auth, error) {
return &Auth{devMode: cfg.DevMode}, nil
}
func (a *Auth) WithDB(db *sql.DB) {
a.db = db
}
func (a *Auth) RequireAuth(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
user, ok := a.userFromRequest(r)
if !ok {
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
if a.db != nil {
_, _ = a.db.ExecContext(r.Context(), `
INSERT INTO users (sub, email, name)
VALUES ($1, $2, $3)
ON CONFLICT (sub) DO UPDATE
SET email = EXCLUDED.email, name = EXCLUDED.name
`, user.Sub, user.Email, user.Name)
}
ctx := context.WithValue(r.Context(), userContextKey, user)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
func (a *Auth) userFromRequest(r *http.Request) (User, bool) {
if a.devMode {
return User{Sub: "demo-user-001", Email: "demo@example.com", Name: "Demo User"}, true
}
sub := strings.TrimSpace(r.Header.Get("X-Authentik-Uid"))
if sub == "" {
sub = strings.TrimSpace(r.Header.Get("X-Forwarded-User"))
}
email := strings.TrimSpace(r.Header.Get("X-Authentik-Email"))
name := strings.TrimSpace(r.Header.Get("X-Authentik-Name"))
if name == "" {
name = strings.TrimSpace(r.Header.Get("X-Forwarded-Preferred-Username"))
}
if sub == "" {
return User{}, false
}
if email == "" {
email = sub
}
if name == "" {
name = email
}
return User{Sub: sub, Email: email, Name: name}, true
}
func MustUserFromContext(ctx context.Context) User {
v := ctx.Value(userContextKey)
if v == nil {
return User{}
}
if u, ok := v.(User); ok {
return u
}
return User{}
}