2026-03-13 01:56:57 +01:00
|
|
|
package oidc
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"crypto/rand"
|
|
|
|
|
"encoding/base64"
|
|
|
|
|
"sync"
|
|
|
|
|
"time"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// PKCESession stores the in-flight authorization state server-side.
|
|
|
|
|
type PKCESession struct {
|
|
|
|
|
Code string
|
|
|
|
|
ClientID string
|
|
|
|
|
RedirectURI string
|
|
|
|
|
PKCEChallenge string // S256 challenge
|
|
|
|
|
PKCEChallengeMethod string // always "S256"
|
|
|
|
|
State string
|
2026-06-01 21:20:54 +02:00
|
|
|
Nonce string
|
2026-03-13 01:56:57 +01:00
|
|
|
Username string // set after auth
|
|
|
|
|
Scopes []string
|
|
|
|
|
ExpiresAt time.Time
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SessionStore is an in-memory PKCE session store.
|
|
|
|
|
type SessionStore struct {
|
|
|
|
|
mu sync.Mutex
|
|
|
|
|
sessions map[string]*PKCESession // keyed by code
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NewSessionStore returns an initialised, empty SessionStore.
|
|
|
|
|
func NewSessionStore() *SessionStore {
|
|
|
|
|
return &SessionStore{
|
|
|
|
|
sessions: make(map[string]*PKCESession),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create stores the session and returns the generated authorization code.
|
|
|
|
|
func (s *SessionStore) Create(sess *PKCESession) string {
|
|
|
|
|
code := generateCode()
|
|
|
|
|
sess.Code = code
|
|
|
|
|
|
|
|
|
|
s.mu.Lock()
|
|
|
|
|
s.sessions[code] = sess
|
|
|
|
|
s.mu.Unlock()
|
|
|
|
|
|
|
|
|
|
return code
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get retrieves a session by code. Returns false if not found or expired.
|
|
|
|
|
func (s *SessionStore) Get(code string) (*PKCESession, bool) {
|
|
|
|
|
s.mu.Lock()
|
|
|
|
|
sess, ok := s.sessions[code]
|
|
|
|
|
s.mu.Unlock()
|
|
|
|
|
|
|
|
|
|
if !ok {
|
|
|
|
|
return nil, false
|
|
|
|
|
}
|
|
|
|
|
if time.Now().After(sess.ExpiresAt) {
|
|
|
|
|
s.Delete(code)
|
|
|
|
|
return nil, false
|
|
|
|
|
}
|
|
|
|
|
return sess, true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Delete removes a session by code. No-op if the code is not present.
|
|
|
|
|
func (s *SessionStore) Delete(code string) {
|
|
|
|
|
s.mu.Lock()
|
|
|
|
|
delete(s.sessions, code)
|
|
|
|
|
s.mu.Unlock()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// generateCode returns a cryptographically random, URL-safe string suitable
|
|
|
|
|
// for use as an authorization code.
|
|
|
|
|
func generateCode() string {
|
|
|
|
|
b := make([]byte, 32)
|
|
|
|
|
if _, err := rand.Read(b); err != nil {
|
|
|
|
|
panic("oidc: failed to generate random code: " + err.Error())
|
|
|
|
|
}
|
|
|
|
|
return base64.RawURLEncoding.EncodeToString(b)
|
|
|
|
|
}
|