Files
package-updater/internal/master/webauth/provider.go
T

86 lines
2.4 KiB
Go

package webauth
import (
"context"
"errors"
"fmt"
"github.com/coreos/go-oidc/v3/oidc"
"golang.org/x/oauth2"
)
// Claims is what the master needs from the identity provider.
type Claims struct {
Subject string
Name string
Email string
Groups []string
}
// Provider performs the OIDC authorization code flow with PKCE. Declared as
// an interface so handlers can be tested without a real identity provider.
type Provider interface {
AuthCodeURL(state, verifier string) string
Exchange(ctx context.Context, code, verifier string) (Claims, error)
}
// OIDCConfig configures the real provider.
type OIDCConfig struct {
Issuer string // e.g. https://auth.example.com/application/o/package-updater/
ClientID string
ClientSecret string
RedirectURL string // PUBLIC_URL + /auth/callback
}
type oidcProvider struct {
oauth *oauth2.Config
verifier *oidc.IDTokenVerifier
}
// NewOIDCProvider discovers the issuer's endpoints. It makes a network call,
// so the master fails at startup if Authentik is unreachable.
func NewOIDCProvider(ctx context.Context, cfg OIDCConfig) (Provider, error) {
p, err := oidc.NewProvider(ctx, cfg.Issuer)
if err != nil {
return nil, fmt.Errorf("oidc discovery: %w", err)
}
return &oidcProvider{
oauth: &oauth2.Config{
ClientID: cfg.ClientID,
ClientSecret: cfg.ClientSecret,
Endpoint: p.Endpoint(),
RedirectURL: cfg.RedirectURL,
Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
},
verifier: p.Verifier(&oidc.Config{ClientID: cfg.ClientID}),
}, nil
}
func (p *oidcProvider) AuthCodeURL(state, verifier string) string {
return p.oauth.AuthCodeURL(state, oauth2.S256ChallengeOption(verifier))
}
func (p *oidcProvider) Exchange(ctx context.Context, code, verifier string) (Claims, error) {
tok, err := p.oauth.Exchange(ctx, code, oauth2.VerifierOption(verifier))
if err != nil {
return Claims{}, fmt.Errorf("exchange code: %w", err)
}
raw, ok := tok.Extra("id_token").(string)
if !ok {
return Claims{}, errors.New("token response has no id_token")
}
idt, err := p.verifier.Verify(ctx, raw)
if err != nil {
return Claims{}, fmt.Errorf("verify id_token: %w", err)
}
var c struct {
Name string `json:"name"`
Email string `json:"email"`
Groups []string `json:"groups"`
}
if err := idt.Claims(&c); err != nil {
return Claims{}, fmt.Errorf("read claims: %w", err)
}
return Claims{Subject: idt.Subject, Name: c.Name, Email: c.Email, Groups: c.Groups}, nil
}