Initial DDC release

This commit is contained in:
SirBlob committed 2026-10-25 20:24:50 -04:00
commit 434f6441da
57 files changed
+4606

No files matched your search

+18
View File
@@ -0,0 +1,18 @@
package auth
import (
"crypto/sha256"
"crypto/subtle"
)
func CredentialsMatch(expectedUsername, expectedPassword, username, password string) bool {
usernameMatches := constantTimeEqual(expectedUsername, username)
passwordMatches := constantTimeEqual(expectedPassword, password)
return usernameMatches && passwordMatches
}
func constantTimeEqual(expected, actual string) bool {
expectedHash := sha256.Sum256([]byte(expected))
actualHash := sha256.Sum256([]byte(actual))
return subtle.ConstantTimeCompare(expectedHash[:], actualHash[:]) == 1
}
+76
View File
@@ -0,0 +1,76 @@
package auth
import (
"sync"
"time"
)
const maxTrackedClients = 10000
type attemptWindow struct {
failures int
startedAt time.Time
}
type LoginLimiter struct {
mutex sync.Mutex
maxFailures int
window time.Duration
attempts map[string]*attemptWindow
}
func NewLoginLimiter(maxFailures int, window time.Duration) *LoginLimiter {
return &LoginLimiter{
maxFailures: maxFailures,
window: window,
attempts: make(map[string]*attemptWindow),
}
}
func (limiter *LoginLimiter) Allowed(client string) bool {
limiter.mutex.Lock()
defer limiter.mutex.Unlock()
attempt := limiter.current(client)
return attempt == nil || attempt.failures < limiter.maxFailures
}
func (limiter *LoginLimiter) RecordFailure(client string) {
limiter.mutex.Lock()
defer limiter.mutex.Unlock()
attempt := limiter.current(client)
if attempt == nil {
if len(limiter.attempts) >= maxTrackedClients {
limiter.removeExpired()
}
attempt = &attemptWindow{startedAt: time.Now()}
limiter.attempts[client] = attempt
}
attempt.failures++
}
func (limiter *LoginLimiter) Reset(client string) {
limiter.mutex.Lock()
defer limiter.mutex.Unlock()
delete(limiter.attempts, client)
}
func (limiter *LoginLimiter) current(client string) *attemptWindow {
attempt, found := limiter.attempts[client]
if !found {
return nil
}
if time.Since(attempt.startedAt) > limiter.window {
delete(limiter.attempts, client)
return nil
}
return attempt
}
func (limiter *LoginLimiter) removeExpired() {
for client, attempt := range limiter.attempts {
if time.Since(attempt.startedAt) > limiter.window {
delete(limiter.attempts, client)
}
}
}
+75
View File
@@ -0,0 +1,75 @@
package auth
import (
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"sync"
"time"
)
const tokenBytes = 32
type SessionStore struct {
mutex sync.Mutex
ttl time.Duration
expirations map[string]time.Time
}
func NewSessionStore(ttl time.Duration) *SessionStore {
return &SessionStore{ttl: ttl, expirations: make(map[string]time.Time)}
}
func (store *SessionStore) Create() (string, error) {
buffer := make([]byte, tokenBytes)
if _, err := rand.Read(buffer); err != nil {
return "", err
}
token := base64.RawURLEncoding.EncodeToString(buffer)
store.mutex.Lock()
defer store.mutex.Unlock()
now := time.Now()
store.removeExpired(now)
store.expirations[hashToken(token)] = now.Add(store.ttl)
return token, nil
}
func (store *SessionStore) Valid(token string) bool {
if token == "" {
return false
}
key := hashToken(token)
store.mutex.Lock()
defer store.mutex.Unlock()
expiresAt, found := store.expirations[key]
if !found {
return false
}
if time.Now().After(expiresAt) {
delete(store.expirations, key)
return false
}
return true
}
func (store *SessionStore) Delete(token string) {
store.mutex.Lock()
defer store.mutex.Unlock()
delete(store.expirations, hashToken(token))
}
func (store *SessionStore) removeExpired(now time.Time) {
for key, expiresAt := range store.expirations {
if now.After(expiresAt) {
delete(store.expirations, key)
}
}
}
func hashToken(token string) string {
sum := sha256.Sum256([]byte(token))
return hex.EncodeToString(sum[:])
}