// Package credentialstore provides a short-lived in-memory cache for OAuth // credentials that need to be passed from the authorization request to the // OAuth callback handler. // // Usage: // 1. Authorization handler stores {app_id, app_secret} keyed by a random nonce // 2. The nonce is embedded in the OAuth state JWT // 3. Callback handler extracts the nonce from state, retrieves credentials, and the entry auto-expires // // Entries expire after 10 minutes. This is a single-instance solution; // for multi-instance deployments, replace with Redis-backed implementation. package credentialstore import ( "crypto/rand" "encoding/hex" "sync" "time" ) const defaultTTL = 10 * time.Minute // Entry holds OAuth credentials temporarily for the callback flow. type Entry struct { AppID string AppSecret string CreatedAt time.Time } var ( mu sync.RWMutex entries = make(map[string]Entry) ) // Store saves credentials and returns a nonce key for retrieval. func Store(appID, appSecret string) (string, error) { b := make([]byte, 32) if _, err := rand.Read(b); err != nil { return "", err } nonce := hex.EncodeToString(b) mu.Lock() defer mu.Unlock() // Prune expired entries opportunistically now := time.Now() for k, v := range entries { if now.Sub(v.CreatedAt) > defaultTTL { delete(entries, k) } } entries[nonce] = Entry{ AppID: appID, AppSecret: appSecret, CreatedAt: now, } return nonce, nil } // Retrieve fetches and deletes credentials by nonce. Returns ok=false if not found or expired. func Retrieve(nonce string) (Entry, bool) { mu.Lock() defer mu.Unlock() entry, ok := entries[nonce] if !ok { return Entry{}, false } delete(entries, nonce) // one-time use if time.Since(entry.CreatedAt) > defaultTTL { return Entry{}, false } return entry, true }