Finish KEY-WP-0008: registration handoff and client MFA isolation
All checks were successful
Build and Publish Container Image / build-and-push (push) Successful in 34s
All checks were successful
Build and Publish Container Image / build-and-push (push) Successful in 34s
Add signed registration/enrollment handoffs, per-request assurance policy with login-session isolation, and /logout. coulomb-social stays AAL1 unless acr_values or another client raises the bar.
This commit is contained in:
parent
fff9e39478
commit
b6af6c5268
22 changed files with 1636 additions and 42 deletions
130
src/internal/server/oidc/login_session.go
Normal file
130
src/internal/server/oidc/login_session.go
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
package oidc
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"keycape/internal/domain"
|
||||
)
|
||||
|
||||
const (
|
||||
loginCookieName = "kc_login"
|
||||
loginSessionTTL = 8 * time.Hour
|
||||
)
|
||||
|
||||
// LoginSession is a KeyCape browser session that records the assurance
|
||||
// already proven for a user. It is not client-specific: a later high-
|
||||
// assurance client must still step up if the stored level is too low.
|
||||
type LoginSession struct {
|
||||
ID string
|
||||
Username string
|
||||
Level domain.AssuranceLevel
|
||||
IssuedAt time.Time
|
||||
ExpiresAt time.Time
|
||||
}
|
||||
|
||||
// LoginSessionStore is an in-memory login-session map keyed by cookie value.
|
||||
type LoginSessionStore struct {
|
||||
mu sync.Mutex
|
||||
sessions map[string]*LoginSession
|
||||
}
|
||||
|
||||
// NewLoginSessionStore returns an empty login-session store.
|
||||
func NewLoginSessionStore() *LoginSessionStore {
|
||||
return &LoginSessionStore{sessions: make(map[string]*LoginSession)}
|
||||
}
|
||||
|
||||
// Create stores a session and returns it.
|
||||
func (s *LoginSessionStore) Create(username string, level domain.AssuranceLevel) *LoginSession {
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
id, err := randomID()
|
||||
if err != nil {
|
||||
panic("oidc: failed to generate login session id: " + err.Error())
|
||||
}
|
||||
now := time.Now()
|
||||
sess := &LoginSession{
|
||||
ID: id,
|
||||
Username: username,
|
||||
Level: level,
|
||||
IssuedAt: now,
|
||||
ExpiresAt: now.Add(loginSessionTTL),
|
||||
}
|
||||
s.mu.Lock()
|
||||
s.sessions[id] = sess
|
||||
s.mu.Unlock()
|
||||
return sess
|
||||
}
|
||||
|
||||
// Get returns a live session by id.
|
||||
func (s *LoginSessionStore) Get(id string) (*LoginSession, bool) {
|
||||
if s == nil || id == "" {
|
||||
return nil, false
|
||||
}
|
||||
s.mu.Lock()
|
||||
sess, ok := s.sessions[id]
|
||||
s.mu.Unlock()
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
if time.Now().After(sess.ExpiresAt) {
|
||||
s.Delete(id)
|
||||
return nil, false
|
||||
}
|
||||
return sess, true
|
||||
}
|
||||
|
||||
// Delete removes a session.
|
||||
func (s *LoginSessionStore) Delete(id string) {
|
||||
if s == nil {
|
||||
return
|
||||
}
|
||||
s.mu.Lock()
|
||||
delete(s.sessions, id)
|
||||
s.mu.Unlock()
|
||||
}
|
||||
|
||||
func (s *LoginSessionStore) fromRequest(r *http.Request) *LoginSession {
|
||||
if s == nil || r == nil {
|
||||
return nil
|
||||
}
|
||||
c, err := r.Cookie(loginCookieName)
|
||||
if err != nil || c.Value == "" {
|
||||
return nil
|
||||
}
|
||||
sess, ok := s.Get(c.Value)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return sess
|
||||
}
|
||||
|
||||
func writeLoginCookie(w http.ResponseWriter, sess *LoginSession, secure bool) {
|
||||
if sess == nil {
|
||||
return
|
||||
}
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: loginCookieName,
|
||||
Value: sess.ID,
|
||||
Path: "/",
|
||||
Expires: sess.ExpiresAt,
|
||||
MaxAge: int(time.Until(sess.ExpiresAt).Seconds()),
|
||||
HttpOnly: true,
|
||||
SameSite: http.SameSiteLaxMode,
|
||||
Secure: secure,
|
||||
})
|
||||
}
|
||||
|
||||
func clearLoginCookie(w http.ResponseWriter, secure bool) {
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: loginCookieName,
|
||||
Value: "",
|
||||
Path: "/",
|
||||
MaxAge: -1,
|
||||
HttpOnly: true,
|
||||
SameSite: http.SameSiteLaxMode,
|
||||
Secure: secure,
|
||||
})
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue