store.go raw
1 package oauth2
2
3 import (
4 "crypto/rand"
5 "encoding/hex"
6 "sync"
7 "time"
8 )
9
10 // AuthCode represents an OAuth2 authorization code
11 type AuthCode struct {
12 Code string
13 ClientID string
14 RedirectURI string
15 Pubkey string // Nostr public key (hex)
16 State string
17 CreatedAt time.Time
18 ExpiresAt time.Time
19 }
20
21 // Challenge represents a Nostr authentication challenge
22 type Challenge struct {
23 Nonce string
24 ClientID string
25 State string
26 RedirectURI string
27 CreatedAt time.Time
28 ExpiresAt time.Time
29 }
30
31 // AccessToken represents an issued access token
32 type AccessToken struct {
33 Token string
34 Pubkey string
35 ClientID string
36 CreatedAt time.Time
37 ExpiresAt time.Time
38 }
39
40 // Store interface for OAuth2 data persistence
41 type Store interface {
42 // Challenge operations
43 CreateChallenge(clientID, state, redirectURI string, ttl time.Duration) (*Challenge, error)
44 GetChallenge(nonce string) (*Challenge, error)
45 DeleteChallenge(nonce string) error
46
47 // Auth code operations
48 CreateAuthCode(clientID, redirectURI, pubkey, state string) (*AuthCode, error)
49 GetAuthCode(code string) (*AuthCode, error)
50 DeleteAuthCode(code string) error
51
52 // Access token operations
53 CreateAccessToken(pubkey, clientID string) (*AccessToken, error)
54 GetAccessToken(token string) (*AccessToken, error)
55 }
56
57 // MemoryStore is an in-memory implementation of Store
58 type MemoryStore struct {
59 challenges map[string]*Challenge
60 authCodes map[string]*AuthCode
61 accessTokens map[string]*AccessToken
62 mu sync.RWMutex
63 }
64
65 func NewMemoryStore() *MemoryStore {
66 s := &MemoryStore{
67 challenges: make(map[string]*Challenge),
68 authCodes: make(map[string]*AuthCode),
69 accessTokens: make(map[string]*AccessToken),
70 }
71 go s.cleanup()
72 return s
73 }
74
75 func (s *MemoryStore) cleanup() {
76 ticker := time.NewTicker(time.Minute)
77 for range ticker.C {
78 s.mu.Lock()
79 now := time.Now()
80 for k, v := range s.challenges {
81 if now.After(v.ExpiresAt) {
82 delete(s.challenges, k)
83 }
84 }
85 for k, v := range s.authCodes {
86 if now.After(v.ExpiresAt) {
87 delete(s.authCodes, k)
88 }
89 }
90 for k, v := range s.accessTokens {
91 if now.After(v.ExpiresAt) {
92 delete(s.accessTokens, k)
93 }
94 }
95 s.mu.Unlock()
96 }
97 }
98
99 func generateToken(length int) (string, error) {
100 bytes := make([]byte, length)
101 if _, err := rand.Read(bytes); err != nil {
102 return "", err
103 }
104 return hex.EncodeToString(bytes), nil
105 }
106
107 func (s *MemoryStore) CreateChallenge(clientID, state, redirectURI string, ttl time.Duration) (*Challenge, error) {
108 nonce, err := generateToken(32)
109 if err != nil {
110 return nil, err
111 }
112
113 challenge := &Challenge{
114 Nonce: nonce,
115 ClientID: clientID,
116 State: state,
117 RedirectURI: redirectURI,
118 CreatedAt: time.Now(),
119 ExpiresAt: time.Now().Add(ttl),
120 }
121
122 s.mu.Lock()
123 s.challenges[nonce] = challenge
124 s.mu.Unlock()
125
126 return challenge, nil
127 }
128
129 func (s *MemoryStore) GetChallenge(nonce string) (*Challenge, error) {
130 s.mu.RLock()
131 defer s.mu.RUnlock()
132
133 challenge, ok := s.challenges[nonce]
134 if !ok || time.Now().After(challenge.ExpiresAt) {
135 return nil, nil
136 }
137 return challenge, nil
138 }
139
140 func (s *MemoryStore) DeleteChallenge(nonce string) error {
141 s.mu.Lock()
142 delete(s.challenges, nonce)
143 s.mu.Unlock()
144 return nil
145 }
146
147 func (s *MemoryStore) CreateAuthCode(clientID, redirectURI, pubkey, state string) (*AuthCode, error) {
148 code, err := generateToken(32)
149 if err != nil {
150 return nil, err
151 }
152
153 authCode := &AuthCode{
154 Code: code,
155 ClientID: clientID,
156 RedirectURI: redirectURI,
157 Pubkey: pubkey,
158 State: state,
159 CreatedAt: time.Now(),
160 ExpiresAt: time.Now().Add(10 * time.Minute),
161 }
162
163 s.mu.Lock()
164 s.authCodes[code] = authCode
165 s.mu.Unlock()
166
167 return authCode, nil
168 }
169
170 func (s *MemoryStore) GetAuthCode(code string) (*AuthCode, error) {
171 s.mu.RLock()
172 defer s.mu.RUnlock()
173
174 authCode, ok := s.authCodes[code]
175 if !ok || time.Now().After(authCode.ExpiresAt) {
176 return nil, nil
177 }
178 return authCode, nil
179 }
180
181 func (s *MemoryStore) DeleteAuthCode(code string) error {
182 s.mu.Lock()
183 delete(s.authCodes, code)
184 s.mu.Unlock()
185 return nil
186 }
187
188 func (s *MemoryStore) CreateAccessToken(pubkey, clientID string) (*AccessToken, error) {
189 token, err := generateToken(32)
190 if err != nil {
191 return nil, err
192 }
193
194 accessToken := &AccessToken{
195 Token: token,
196 Pubkey: pubkey,
197 ClientID: clientID,
198 CreatedAt: time.Now(),
199 ExpiresAt: time.Now().Add(24 * time.Hour),
200 }
201
202 s.mu.Lock()
203 s.accessTokens[token] = accessToken
204 s.mu.Unlock()
205
206 return accessToken, nil
207 }
208
209 func (s *MemoryStore) GetAccessToken(token string) (*AccessToken, error) {
210 s.mu.RLock()
211 defer s.mu.RUnlock()
212
213 accessToken, ok := s.accessTokens[token]
214 if !ok || time.Now().After(accessToken.ExpiresAt) {
215 return nil, nil
216 }
217 return accessToken, nil
218 }
219