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{} }