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) } } }