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.
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user