Files
alexei.dolgolyov 32de5b26a8 feat(docker-watcher): phase 12 - hardening
Blue-green zero-downtime deploys, promote flow validation.
Dual auth: local (bcrypt + JWT) and OAuth2/OIDC (any provider).
Auth middleware, login page, auth settings UI.
Structured logging (slog JSON), config export to YAML.
Graceful shutdown with deploy draining.
Multi-stage Dockerfile and production docker-compose.yml.
Swap phase order: Volumes & Env before UI Polish.
2026-03-27 23:20:56 +03:00

88 lines
2.3 KiB
Go

package auth
import (
"context"
"fmt"
"github.com/coreos/go-oidc/v3/oidc"
"golang.org/x/oauth2"
)
// OIDCProvider wraps an OIDC provider and OAuth2 configuration.
type OIDCProvider struct {
provider *oidc.Provider
oauth2Config oauth2.Config
verifier *oidc.IDTokenVerifier
}
// OIDCConfig holds the configuration needed to set up an OIDC provider.
type OIDCConfig struct {
IssuerURL string
ClientID string
ClientSecret string
RedirectURL string
}
// OIDCUserInfo represents the user information extracted from an OIDC ID token.
type OIDCUserInfo struct {
Subject string `json:"sub"`
Email string `json:"email"`
Username string `json:"preferred_username"`
Name string `json:"name"`
}
// NewOIDCProvider initializes an OIDC provider using the discovery URL.
func NewOIDCProvider(ctx context.Context, cfg OIDCConfig) (*OIDCProvider, error) {
provider, err := oidc.NewProvider(ctx, cfg.IssuerURL)
if err != nil {
return nil, fmt.Errorf("create oidc provider: %w", err)
}
oauth2Config := oauth2.Config{
ClientID: cfg.ClientID,
ClientSecret: cfg.ClientSecret,
RedirectURL: cfg.RedirectURL,
Endpoint: provider.Endpoint(),
Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
}
verifier := provider.Verifier(&oidc.Config{ClientID: cfg.ClientID})
return &OIDCProvider{
provider: provider,
oauth2Config: oauth2Config,
verifier: verifier,
}, nil
}
// AuthCodeURL returns the URL to redirect the user to for OIDC authentication.
func (op *OIDCProvider) AuthCodeURL(state string) string {
return op.oauth2Config.AuthCodeURL(state)
}
// Exchange trades an authorization code for tokens and returns the user info
// extracted from the ID token.
func (op *OIDCProvider) Exchange(ctx context.Context, code string) (OIDCUserInfo, error) {
token, err := op.oauth2Config.Exchange(ctx, code)
if err != nil {
return OIDCUserInfo{}, fmt.Errorf("exchange code: %w", err)
}
rawIDToken, ok := token.Extra("id_token").(string)
if !ok {
return OIDCUserInfo{}, fmt.Errorf("no id_token in response")
}
idToken, err := op.verifier.Verify(ctx, rawIDToken)
if err != nil {
return OIDCUserInfo{}, fmt.Errorf("verify id_token: %w", err)
}
var userInfo OIDCUserInfo
if err := idToken.Claims(&userInfo); err != nil {
return OIDCUserInfo{}, fmt.Errorf("parse id_token claims: %w", err)
}
return userInfo, nil
}