123 lines
3.2 KiB
Go
123 lines
3.2 KiB
Go
|
|
package golooseauth
|
||
|
|
|
||
|
|
import (
|
||
|
|
"crypto/tls"
|
||
|
|
"crypto/x509"
|
||
|
|
"fmt"
|
||
|
|
"net"
|
||
|
|
"net/http"
|
||
|
|
"os"
|
||
|
|
"strings"
|
||
|
|
"time"
|
||
|
|
|
||
|
|
goloose "github.com/danyel/go-loose/client"
|
||
|
|
|
||
|
|
"github.com/danyel/go-guess/backend/internal/config"
|
||
|
|
)
|
||
|
|
|
||
|
|
const SessionCookie = "go_loose_user_session"
|
||
|
|
|
||
|
|
type Registry struct {
|
||
|
|
appDomain string
|
||
|
|
bySlug map[string]*goloose.BrowserAuth
|
||
|
|
}
|
||
|
|
|
||
|
|
func New(cfg config.GoLooseConfig) (*Registry, error) {
|
||
|
|
registry := &Registry{
|
||
|
|
appDomain: strings.TrimPrefix(strings.ToLower(cfg.AppDomain), "."),
|
||
|
|
bySlug: map[string]*goloose.BrowserAuth{},
|
||
|
|
}
|
||
|
|
if registry.appDomain == "" {
|
||
|
|
registry.appDomain = "guess.dev"
|
||
|
|
}
|
||
|
|
if len(cfg.Tenants) == 0 {
|
||
|
|
return registry, nil
|
||
|
|
}
|
||
|
|
authDomain := strings.TrimPrefix(strings.ToLower(cfg.AuthDomain), ".")
|
||
|
|
if authDomain == "" {
|
||
|
|
return nil, fmt.Errorf("GO_LOOSE_AUTH_DOMAIN is required when client credentials are configured")
|
||
|
|
}
|
||
|
|
httpClient, err := client(cfg.CAFile)
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
for slug, tenant := range cfg.Tenants {
|
||
|
|
slug = strings.ToLower(strings.TrimSpace(slug))
|
||
|
|
if slug == "" || strings.Contains(slug, ".") {
|
||
|
|
return nil, fmt.Errorf("invalid Go Loose tenant slug %q", slug)
|
||
|
|
}
|
||
|
|
auth, err := goloose.NewBrowserAuth(goloose.BrowserAuthConfig{
|
||
|
|
BaseURL: "https://" + slug + "." + authDomain,
|
||
|
|
ClientID: tenant.ClientID,
|
||
|
|
ClientSecret: tenant.ClientSecret,
|
||
|
|
RedirectURL: "https://" + slug + "." + registry.appDomain + "/api/auth/callback",
|
||
|
|
AfterLoginURL: "/",
|
||
|
|
AfterLogoutURL: "/",
|
||
|
|
LoginPath: "/api/auth/login",
|
||
|
|
CookieName: SessionCookie,
|
||
|
|
CookieSecure: true,
|
||
|
|
HTTPClient: httpClient,
|
||
|
|
})
|
||
|
|
if err != nil {
|
||
|
|
return nil, fmt.Errorf("configure %s Go Loose login: %w", slug, err)
|
||
|
|
}
|
||
|
|
registry.bySlug[slug] = auth
|
||
|
|
}
|
||
|
|
return registry, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (r *Registry) AppDomain() string {
|
||
|
|
if r == nil || r.appDomain == "" {
|
||
|
|
return "guess.dev"
|
||
|
|
}
|
||
|
|
return r.appDomain
|
||
|
|
}
|
||
|
|
|
||
|
|
func (r *Registry) ForHost(host string) (*goloose.BrowserAuth, string, bool) {
|
||
|
|
if r == nil {
|
||
|
|
return nil, "", false
|
||
|
|
}
|
||
|
|
slug := TenantFromHost(host, r.appDomain)
|
||
|
|
auth, ok := r.bySlug[slug]
|
||
|
|
return auth, slug, ok
|
||
|
|
}
|
||
|
|
|
||
|
|
func TenantFromHost(host, appDomain string) string {
|
||
|
|
hostname := strings.ToLower(host)
|
||
|
|
if parsed, _, err := net.SplitHostPort(host); err == nil {
|
||
|
|
hostname = strings.ToLower(parsed)
|
||
|
|
}
|
||
|
|
appDomain = strings.TrimPrefix(strings.ToLower(appDomain), ".")
|
||
|
|
if appDomain == "" {
|
||
|
|
appDomain = "guess.dev"
|
||
|
|
}
|
||
|
|
suffix := "." + appDomain
|
||
|
|
if !strings.HasSuffix(hostname, suffix) {
|
||
|
|
return ""
|
||
|
|
}
|
||
|
|
slug := strings.TrimSuffix(hostname, suffix)
|
||
|
|
if slug == "" || strings.Contains(slug, ".") {
|
||
|
|
return ""
|
||
|
|
}
|
||
|
|
return slug
|
||
|
|
}
|
||
|
|
|
||
|
|
func client(caFile string) (*http.Client, error) {
|
||
|
|
transport := http.DefaultTransport.(*http.Transport).Clone()
|
||
|
|
if caFile != "" {
|
||
|
|
pem, err := os.ReadFile(caFile)
|
||
|
|
if err != nil {
|
||
|
|
return nil, fmt.Errorf("read Go Loose CA: %w", err)
|
||
|
|
}
|
||
|
|
pool, err := x509.SystemCertPool()
|
||
|
|
if err != nil || pool == nil {
|
||
|
|
pool = x509.NewCertPool()
|
||
|
|
}
|
||
|
|
if !pool.AppendCertsFromPEM(pem) {
|
||
|
|
return nil, fmt.Errorf("parse Go Loose CA %s", caFile)
|
||
|
|
}
|
||
|
|
transport.TLSClientConfig = &tls.Config{RootCAs: pool, MinVersion: tls.VersionTLS12}
|
||
|
|
}
|
||
|
|
return &http.Client{Timeout: 5 * time.Second, Transport: transport}, nil
|
||
|
|
}
|