100 lines
2.3 KiB
Go
100 lines
2.3 KiB
Go
package server
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"encoding/hex"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// tokenTTL is how long a web access token remains valid.
|
|
const tokenTTL = 1 * time.Hour
|
|
|
|
// tokenInfo holds the identity associated with a web access token.
|
|
// When a telnet user generates a token, their security levels are
|
|
// captured so the HTTP server can enforce access control without
|
|
// needing to query the database on every request.
|
|
type tokenInfo struct {
|
|
UserID int64
|
|
UserName string
|
|
SecLibrary int
|
|
ExpiresAt time.Time
|
|
}
|
|
|
|
// TokenStore manages time-limited web access tokens.
|
|
//
|
|
// Tokens are generated by telnet users and used in the browser to
|
|
// authenticate file downloads. This is the bridge between the telnet
|
|
// BBS session and the HTTP file server — conceptually similar to how
|
|
// some BBSes generated one-time download passwords.
|
|
//
|
|
// Thread-safe: tokens can be created from telnet goroutines and
|
|
// validated from HTTP handler goroutines concurrently.
|
|
type TokenStore struct {
|
|
mu sync.Mutex
|
|
tokens map[string]*tokenInfo
|
|
}
|
|
|
|
// NewTokenStore creates an empty token store.
|
|
func NewTokenStore() *TokenStore {
|
|
return &TokenStore{
|
|
tokens: make(map[string]*tokenInfo),
|
|
}
|
|
}
|
|
|
|
// Generate creates a new token for the given user and returns it.
|
|
// The token is a 16-byte hex string (32 characters).
|
|
func (ts *TokenStore) Generate(userID int64, userName string, secLibrary int) (string, error) {
|
|
b := make([]byte, 16)
|
|
if _, err := rand.Read(b); err != nil {
|
|
return "", err
|
|
}
|
|
token := hex.EncodeToString(b)
|
|
|
|
ts.mu.Lock()
|
|
ts.tokens[token] = &tokenInfo{
|
|
UserID: userID,
|
|
UserName: userName,
|
|
SecLibrary: secLibrary,
|
|
ExpiresAt: time.Now().Add(tokenTTL),
|
|
}
|
|
ts.mu.Unlock()
|
|
|
|
// Opportunistic cleanup of expired tokens
|
|
go ts.cleanup()
|
|
|
|
return token, nil
|
|
}
|
|
|
|
// Validate checks a token and returns the associated info if valid.
|
|
// Returns nil if the token is missing, expired, or invalid.
|
|
func (ts *TokenStore) Validate(token string) *tokenInfo {
|
|
ts.mu.Lock()
|
|
defer ts.mu.Unlock()
|
|
|
|
info, ok := ts.tokens[token]
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
if time.Now().After(info.ExpiresAt) {
|
|
delete(ts.tokens, token)
|
|
return nil
|
|
}
|
|
|
|
return info
|
|
}
|
|
|
|
// cleanup removes all expired tokens.
|
|
func (ts *TokenStore) cleanup() {
|
|
ts.mu.Lock()
|
|
defer ts.mu.Unlock()
|
|
|
|
now := time.Now()
|
|
for token, info := range ts.tokens {
|
|
if now.After(info.ExpiresAt) {
|
|
delete(ts.tokens, token)
|
|
}
|
|
}
|
|
}
|