Raw
  1package signin
  2
  3import (
  4	"context"
  5	"errors"
  6	"net/http"
  7	"time"
  8
  9	"tug/internal/platform/config"
 10	"tug/internal/service/identity"
 11	"tug/internal/service/session"
 12	"tug/internal/storage"
 13	"tug/internal/transport/forwardauth"
 14	"tug/internal/transport/google"
 15	"tug/internal/transport/idp"
 16	"tug/internal/transport/oidcrp"
 17	"tug/internal/transport/web"
 18)
 19
 20const ForwardTxnLimit = 4
 21
 22type Wiring struct {
 23	Store         *storage.Store
 24	Kit           *web.Kit
 25	IDP           *idp.IDP
 26	IDPHandler    http.Handler
 27	Server        config.Server
 28	PrimaryGroup  string
 29	Apps          []config.App
 30	MasterKey     config.Secret
 31	SessionTTL    time.Duration
 32	Now           func() time.Time
 33	Issuer        string
 34	AccountOrigin string
 35	AdminOrigin   string
 36	AppOrigins    map[string]string
 37	Refuse        func(http.ResponseWriter, *http.Request, int, error)
 38	Google        *google.RP
 39	Portdistrict  bool
 40	ObserveAuthz  func(outcome string)
 41}
 42
 43type Composition struct {
 44	Sessions    *session.Service
 45	PortalRP    *oidcrp.RP
 46	Signin      *Signin
 47	ForwardRP   *oidcrp.RP
 48	ForwardAuth *forwardauth.ForwardAuth
 49}
 50
 51func Compose(ctx context.Context, w Wiring) (*Composition, error) {
 52	if w.Store == nil || w.Kit == nil || w.IDP == nil {
 53		return nil, errors.New("signin: the store, the kit and the identity provider are required")
 54	}
 55	handler := w.IDPHandler
 56	if handler == nil {
 57		handler = w.IDP.Handler()
 58	}
 59	refuse := w.Refuse
 60	if refuse == nil {
 61		refuse = Refuse(w.Kit)
 62	}
 63	ids := identity.NewWithLockout(w.Store, w.PrimaryGroup, w.IDP.Lockout())
 64	sessions, err := session.New(session.Config{Store: w.Store, Identity: ids.Observed(identity.PathPassword, w.IDP.ObserveChecks()), AdminGroup: w.Server.AdminGroup, TTL: w.SessionTTL, Now: w.Now, HostTTL: config.HostTTLs(w.Apps)})
 65	if err != nil {
 66		return nil, err
 67	}
 68	var clients []oidcrp.Client
 69	for _, c := range []struct{ id, domain, origin string }{
 70		{idp.PortalClientID, w.Server.DomainAccount, w.AccountOrigin},
 71		{idp.AdminClientID, w.Server.DomainAdmin, w.AdminOrigin},
 72	} {
 73		secret, err := idp.FirstPartySecret(w.MasterKey.Bytes(), c.id)
 74		if err != nil {
 75			return nil, err
 76		}
 77		clients = append(clients, oidcrp.Client{ID: c.id, Secret: config.NewSecret([]byte(secret)), Origins: []string{originOr(c.origin, c.domain)}, CallbackPath: CallbackPath})
 78	}
 79	portal, err := oidcrp.New(ctx, oidcrp.Config{
 80		Issuer:      originOr(w.Issuer, w.Server.DomainIDP),
 81		IDP:         handler,
 82		Hosts:       []string{w.Server.DomainAccount, w.Server.DomainAdmin},
 83		MasterKey:   w.MasterKey,
 84		Clients:     clients,
 85		ConnectorID: idp.ConnectorID,
 86		Sessions:    Sessions(sessions),
 87		CodeEpoch:   w.IDP.CodeEpoch,
 88		Now:         w.Now,
 89		Refuse:      refuse,
 90		Rotate:      w.Kit.RotateCSRF,
 91	})
 92	if err != nil {
 93		return nil, err
 94	}
 95	si, err := New(Config{
 96		Kit: w.Kit, Sessions: sessions, Identity: ids.Observed(identity.PathSudo, w.IDP.ObserveChecks()), Verifier: w.IDP.Verifier(), RP: portal, AccountDomain: w.Server.DomainAccount,
 97		AdminOrigin: originOr(w.AdminOrigin, w.Server.DomainAdmin), PrimaryGroup: w.PrimaryGroup, Google: w.Google, Portdistrict: w.Portdistrict,
 98	})
 99	if err != nil {
100		return nil, err
101	}
102	comp := &Composition{Sessions: sessions, PortalRP: portal, Signin: si}
103	if len(w.Apps) == 0 {
104		return comp, nil
105	}
106	secret, err := idp.FirstPartySecret(w.MasterKey.Bytes(), idp.ForwardClientID)
107	if err != nil {
108		return nil, err
109	}
110	hosts := make([]string, 0, len(w.Apps))
111	origins := make([]string, 0, len(w.Apps))
112	for _, a := range w.Apps {
113		hosts = append(hosts, a.Domain)
114		origins = append(origins, originOr(w.AppOrigins[a.Domain], a.Domain))
115	}
116	comp.ForwardRP, err = oidcrp.New(ctx, oidcrp.Config{
117		Issuer:      originOr(w.Issuer, w.Server.DomainIDP),
118		IDP:         handler,
119		Hosts:       hosts,
120		MasterKey:   w.MasterKey,
121		Clients:     []oidcrp.Client{{ID: idp.ForwardClientID, Secret: config.NewSecret([]byte(secret)), Origins: origins, CallbackPath: forwardauth.CallbackPath}},
122		ConnectorID: idp.ConnectorID,
123		Sessions:    Sessions(sessions),
124		CodeEpoch:   w.IDP.CodeEpoch,
125		TxnLimit:    ForwardTxnLimit,
126		Now:         w.Now,
127		Refuse:      refuse,
128		Rotate:      w.Kit.RotateCSRF,
129	})
130	if err != nil {
131		return nil, err
132	}
133	guard, ender := AppSession(sessions, comp.ForwardRP)
134	comp.ForwardAuth, err = forwardauth.New(forwardauth.Config{RP: comp.ForwardRP, Sessions: sessions, Kit: w.Kit, Guard: guard, Ender: ender, Observe: w.ObserveAuthz})
135	if err != nil {
136		return nil, err
137	}
138	return comp, nil
139}
140
141func originOr(origin, domain string) string {
142	if origin != "" {
143		return origin
144	}
145	return "https://" + domain
146}