~bigbes/lethe

ref: 96e95ab9e44d2234ab036319836a5087eb4c2a2f lethe/internal/testutil/oidcstub/codestore.go -rw-r--r-- 2.0 KiB
96e95ab9 — Eugene Blikh fix: add tool column to search table; remove conversation bleed from comments 23 days ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
package oidcstub

import (
	"crypto/rand"
	"encoding/base64"
	"sync"
	"time"
)

// codeEntry holds the data stored with an authorization code.
type codeEntry struct {
	Sub           string
	CodeChallenge string
	RedirectURI   string
	ExpiresAt     time.Time
}

// codeStore is a thread-safe in-memory store for single-use authorization codes.
// now is injected for deterministic testing; nil defaults to time.Now.
type codeStore struct {
	mu      sync.Mutex
	entries map[string]codeEntry
	now     func() time.Time
}

// newCodeStore constructs a codeStore. now may be nil, in which case time.Now
// is used.
func newCodeStore(now func() time.Time) *codeStore {
	if now == nil {
		now = time.Now
	}
	return &codeStore{
		entries: make(map[string]codeEntry),
		now:     now,
	}
}

// Issue generates an opaque base64url 32-byte authorization code, stores it
// with the supplied sub, code_challenge, redirect_uri, and TTL, and returns
// the code string. The code is URL-safe (base64.RawURLEncoding — no +, /, =).
func (s *codeStore) Issue(sub, challenge, redirect string, ttl time.Duration) string {
	var buf [32]byte
	if _, err := rand.Read(buf[:]); err != nil {
		panic("oidcstub: codeStore.Issue: crypto/rand.Read: " + err.Error())
	}
	code := base64.RawURLEncoding.EncodeToString(buf[:])

	s.mu.Lock()
	defer s.mu.Unlock()
	s.entries[code] = codeEntry{
		Sub:           sub,
		CodeChallenge: challenge,
		RedirectURI:   redirect,
		ExpiresAt:     s.now().Add(ttl),
	}
	return code
}

// Consume retrieves and deletes the entry for code. Returns (entry, true) on
// first call for a valid, unexpired code; (zero, false) on miss, expiry, or
// any subsequent call (IV2, IV3).
func (s *codeStore) Consume(code string) (codeEntry, bool) {
	s.mu.Lock()
	defer s.mu.Unlock()

	entry, ok := s.entries[code]
	if !ok {
		return codeEntry{}, false
	}
	// Always delete — even expired entries must not be reusable.
	delete(s.entries, code)

	if s.now().After(entry.ExpiresAt) {
		return codeEntry{}, false
	}
	return entry, true
}