- Facebook/Instagram/TikTok 渠道 OAuth 授权流程改进 - 新增 oauth/credentialstore 包 - 集成应用 OpenAI 重命名为 OpenAI 兼容 (migration 061) - 消息生命周期与 AI 流程架构文档 - inbox 管理界面 i18n 更新
74 lines
1.8 KiB
Go
74 lines
1.8 KiB
Go
// 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
|
|
}
|