Files
gitea-nostr-auth/internal/oauth2/store.go
mleku 896a7599a0 Release v0.0.1 - Initial OAuth2 server implementation
- Add Nostr OAuth2 server with NIP-98 authentication support
- Implement OAuth2 authorization and token endpoints
- Add .well-known/openid-configuration discovery endpoint
- Include Dockerfile for containerized deployment
- Add Claude Code release command for version management
- Create example configuration file

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-19 09:37:26 +01:00

219 lines
4.7 KiB
Go

package oauth2
import (
"crypto/rand"
"encoding/hex"
"sync"
"time"
)
// AuthCode represents an OAuth2 authorization code
type AuthCode struct {
Code string
ClientID string
RedirectURI string
Pubkey string // Nostr public key (hex)
State string
CreatedAt time.Time
ExpiresAt time.Time
}
// Challenge represents a Nostr authentication challenge
type Challenge struct {
Nonce string
ClientID string
State string
RedirectURI string
CreatedAt time.Time
ExpiresAt time.Time
}
// AccessToken represents an issued access token
type AccessToken struct {
Token string
Pubkey string
ClientID string
CreatedAt time.Time
ExpiresAt time.Time
}
// Store interface for OAuth2 data persistence
type Store interface {
// Challenge operations
CreateChallenge(clientID, state, redirectURI string, ttl time.Duration) (*Challenge, error)
GetChallenge(nonce string) (*Challenge, error)
DeleteChallenge(nonce string) error
// Auth code operations
CreateAuthCode(clientID, redirectURI, pubkey, state string) (*AuthCode, error)
GetAuthCode(code string) (*AuthCode, error)
DeleteAuthCode(code string) error
// Access token operations
CreateAccessToken(pubkey, clientID string) (*AccessToken, error)
GetAccessToken(token string) (*AccessToken, error)
}
// MemoryStore is an in-memory implementation of Store
type MemoryStore struct {
challenges map[string]*Challenge
authCodes map[string]*AuthCode
accessTokens map[string]*AccessToken
mu sync.RWMutex
}
func NewMemoryStore() *MemoryStore {
s := &MemoryStore{
challenges: make(map[string]*Challenge),
authCodes: make(map[string]*AuthCode),
accessTokens: make(map[string]*AccessToken),
}
go s.cleanup()
return s
}
func (s *MemoryStore) cleanup() {
ticker := time.NewTicker(time.Minute)
for range ticker.C {
s.mu.Lock()
now := time.Now()
for k, v := range s.challenges {
if now.After(v.ExpiresAt) {
delete(s.challenges, k)
}
}
for k, v := range s.authCodes {
if now.After(v.ExpiresAt) {
delete(s.authCodes, k)
}
}
for k, v := range s.accessTokens {
if now.After(v.ExpiresAt) {
delete(s.accessTokens, k)
}
}
s.mu.Unlock()
}
}
func generateToken(length int) (string, error) {
bytes := make([]byte, length)
if _, err := rand.Read(bytes); err != nil {
return "", err
}
return hex.EncodeToString(bytes), nil
}
func (s *MemoryStore) CreateChallenge(clientID, state, redirectURI string, ttl time.Duration) (*Challenge, error) {
nonce, err := generateToken(32)
if err != nil {
return nil, err
}
challenge := &Challenge{
Nonce: nonce,
ClientID: clientID,
State: state,
RedirectURI: redirectURI,
CreatedAt: time.Now(),
ExpiresAt: time.Now().Add(ttl),
}
s.mu.Lock()
s.challenges[nonce] = challenge
s.mu.Unlock()
return challenge, nil
}
func (s *MemoryStore) GetChallenge(nonce string) (*Challenge, error) {
s.mu.RLock()
defer s.mu.RUnlock()
challenge, ok := s.challenges[nonce]
if !ok || time.Now().After(challenge.ExpiresAt) {
return nil, nil
}
return challenge, nil
}
func (s *MemoryStore) DeleteChallenge(nonce string) error {
s.mu.Lock()
delete(s.challenges, nonce)
s.mu.Unlock()
return nil
}
func (s *MemoryStore) CreateAuthCode(clientID, redirectURI, pubkey, state string) (*AuthCode, error) {
code, err := generateToken(32)
if err != nil {
return nil, err
}
authCode := &AuthCode{
Code: code,
ClientID: clientID,
RedirectURI: redirectURI,
Pubkey: pubkey,
State: state,
CreatedAt: time.Now(),
ExpiresAt: time.Now().Add(10 * time.Minute),
}
s.mu.Lock()
s.authCodes[code] = authCode
s.mu.Unlock()
return authCode, nil
}
func (s *MemoryStore) GetAuthCode(code string) (*AuthCode, error) {
s.mu.RLock()
defer s.mu.RUnlock()
authCode, ok := s.authCodes[code]
if !ok || time.Now().After(authCode.ExpiresAt) {
return nil, nil
}
return authCode, nil
}
func (s *MemoryStore) DeleteAuthCode(code string) error {
s.mu.Lock()
delete(s.authCodes, code)
s.mu.Unlock()
return nil
}
func (s *MemoryStore) CreateAccessToken(pubkey, clientID string) (*AccessToken, error) {
token, err := generateToken(32)
if err != nil {
return nil, err
}
accessToken := &AccessToken{
Token: token,
Pubkey: pubkey,
ClientID: clientID,
CreatedAt: time.Now(),
ExpiresAt: time.Now().Add(24 * time.Hour),
}
s.mu.Lock()
s.accessTokens[token] = accessToken
s.mu.Unlock()
return accessToken, nil
}
func (s *MemoryStore) GetAccessToken(token string) (*AccessToken, error) {
s.mu.RLock()
defer s.mu.RUnlock()
accessToken, ok := s.accessTokens[token]
if !ok || time.Now().After(accessToken.ExpiresAt) {
return nil, nil
}
return accessToken, nil
}