// Package connect adapts the Home handoff to an application's existing identity. // It grants no membership and never creates local accounts or sample content. package connect import ( "context" "database/sql" "net/http" "time" "amadan.net/oficina/home/connect/linkstore" ) const HomeMethod = "home-assertion" // Session times must come from the verified stored session, never request or last-seen time. // AuthTime is the method verification time; At is immutable session creation time. type Session struct { Subject, Method string AuthTime, At time.Time } // Person.Label is the canonical verified local address, never a display name. // Local IDs must never be reused, including for a new account with the same address. type Person struct { ID int64 Label string } type Config struct { DB *sql.DB Origin, Issuer, ConsumerID, Namespace, SessionCookie string Keys map[string][]byte DEK []byte // ReadSession must resolve the actual verified session, never a supplied subject. ReadSession func(*http.Request) (*Session, error) MintHomeSession func(http.ResponseWriter, *http.Request, Session) error // Both lookup hooks must read current existing, admitted local accounts. // They never create accounts or infer a Home link from an email address. LookupLocalSession func(context.Context, Session) (Person, error) LookupPerson linkstore.LookupPerson Render func(http.ResponseWriter, *http.Request, string, string, int) ReturnPath func(*http.Request) string Now func() time.Time } func (b *Bridge) Resolve(ctx context.Context, subject string) (Person, error) { id, label, err := b.links.Resolve(ctx, b.issuer, subject) return Person{ID: id, Label: label}, err } func (b *Bridge) ResolveSession(r *http.Request) (Person, error) { session, err := b.actualSession(r) if err != nil || session == nil { return Person{}, errIdentityRefused } return b.existingPerson(r, *session) } // FreshLocal accepts only independent local verification. A Keymail session // without auth_time cannot borrow its local session creation time. func FreshLocal(s Session, now time.Time) (time.Time, bool) { if s.Subject == "" || (s.Method != "magiclink" && s.Method != "keymail") { return time.Time{}, false } at := s.AuthTime if at.IsZero() && s.Method == "magiclink" { at = s.At } if at.IsZero() || at.After(now) || now.Sub(at) > 5*time.Minute { return time.Time{}, false } return at, true }