- Implement LogoutHandler to handle user logout requests - Add DeleteTokenInfo and GetUserIDFromToken functions to auth module - Update README.md with logout endpoint documentation
226 lines
5.0 KiB
Go
226 lines
5.0 KiB
Go
package main
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"crypto/sha256"
|
|
"encoding/base64"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
"super-frpc/postLog"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
type TokenInfo struct {
|
|
Token string
|
|
CreatedAt time.Time
|
|
UserID int
|
|
}
|
|
|
|
var (
|
|
tokenMap = make(map[int]*TokenInfo)
|
|
tokenMux sync.RWMutex
|
|
tokenTTL = time.Hour
|
|
)
|
|
|
|
func GenerateToken(userID int) (string, error) {
|
|
randomBytes := make([]byte, 32)
|
|
_, err := rand.Read(randomBytes)
|
|
if err != nil {
|
|
return "", fmt.Errorf("Failed to generate random bytes: %w", err)
|
|
}
|
|
|
|
hash := sha256.Sum256(append(randomBytes, []byte(fmt.Sprintf("%d", userID))...))
|
|
token := base64.URLEncoding.EncodeToString(hash[:])
|
|
|
|
tokenMux.Lock()
|
|
defer tokenMux.Unlock()
|
|
|
|
tokenMap[userID] = &TokenInfo{
|
|
Token: token,
|
|
CreatedAt: time.Now(),
|
|
UserID: userID,
|
|
}
|
|
|
|
postLog.Debug(fmt.Sprintf("[GenerateToken] Generated token for userID %d: %s", userID, token))
|
|
return token, nil
|
|
}
|
|
|
|
func ValidateToken(userID int, token string) error {
|
|
tokenMux.RLock()
|
|
defer tokenMux.RUnlock()
|
|
|
|
tokenInfo, exists := tokenMap[userID]
|
|
if !exists {
|
|
return fmt.Errorf("Token not found for userID %d: %s", userID, token)
|
|
}
|
|
|
|
if tokenInfo.Token != token {
|
|
return fmt.Errorf("Invalid token for userID %d: %s", userID, token)
|
|
}
|
|
|
|
if time.Since(tokenInfo.CreatedAt) > tokenTTL {
|
|
return fmt.Errorf("Token expired for userID %d: %s", userID, token)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func RefreshToken(userID int) (string, error) {
|
|
tokenMux.Lock()
|
|
defer tokenMux.Unlock()
|
|
|
|
randomBytes := make([]byte, 32)
|
|
_, err := rand.Read(randomBytes)
|
|
if err != nil {
|
|
return "", fmt.Errorf("Failed to generate random bytes: %w", err)
|
|
}
|
|
|
|
hash := sha256.Sum256(append(randomBytes, []byte(fmt.Sprintf("%d", userID))...))
|
|
token := base64.URLEncoding.EncodeToString(hash[:])
|
|
|
|
tokenMap[userID] = &TokenInfo{
|
|
Token: token,
|
|
CreatedAt: time.Now(),
|
|
UserID: userID,
|
|
}
|
|
|
|
postLog.Debug(fmt.Sprintf("[RefreshToken] Refreshed token for userID %d: %s", userID, token))
|
|
return token, nil
|
|
}
|
|
|
|
func RemoveToken(userID int) {
|
|
tokenMux.Lock()
|
|
defer tokenMux.Unlock()
|
|
delete(tokenMap, userID)
|
|
postLog.Debug(fmt.Sprintf("[RemoveToken] Removed token for userID %d: %s", userID, tokenMap[userID].Token))
|
|
}
|
|
|
|
func GetTokenInfo(userID int) (*TokenInfo, error) {
|
|
tokenMux.RLock()
|
|
defer tokenMux.RUnlock()
|
|
|
|
tokenInfo, exists := tokenMap[userID]
|
|
if !exists {
|
|
return nil, fmt.Errorf("Token not found for userID %d", userID)
|
|
|
|
}
|
|
|
|
return tokenInfo, nil
|
|
}
|
|
|
|
func extractUserIDFromToken(token string) (int, error) {
|
|
tokenMux.RLock()
|
|
defer tokenMux.RUnlock()
|
|
for userID, tokenInfo := range tokenMap {
|
|
if tokenInfo.Token == token {
|
|
// postLog.Debug(fmt.Sprintf("[extractUserIDFromToken] Extracted userID %d from token: %s", userID, token))
|
|
return userID, nil
|
|
}
|
|
}
|
|
return 0, fmt.Errorf("Invalid token: %s", token)
|
|
}
|
|
|
|
func CleanupExpiredTokens() {
|
|
tokenMux.Lock()
|
|
defer tokenMux.Unlock()
|
|
|
|
for userID, tokenInfo := range tokenMap {
|
|
if time.Since(tokenInfo.CreatedAt) > tokenTTL {
|
|
delete(tokenMap, userID)
|
|
postLog.Debug(fmt.Sprintf("[CleanupExpiredTokens] Removed expired token for userID %d: %s", userID, tokenInfo.Token))
|
|
}
|
|
}
|
|
}
|
|
|
|
func hashPassword(password string) (string, error) {
|
|
hash := sha256.Sum256([]byte(password))
|
|
return hex.EncodeToString(hash[:]), nil
|
|
}
|
|
|
|
func verifyPassword(password, hashedPassword string) bool {
|
|
hash, err := hashPassword(password)
|
|
if err != nil {
|
|
postLog.Error(fmt.Sprintf("[verifyPassword] Failed to hash password: %v", err))
|
|
return false
|
|
}
|
|
return hash == hashedPassword
|
|
}
|
|
|
|
func isValidPassword(password string) bool { // Validate password complexity and generate hash
|
|
if len(password) < 8 {
|
|
return false
|
|
}
|
|
|
|
hasUpper := false
|
|
hasLower := false
|
|
hasDigit := false
|
|
hasSpecial := false
|
|
|
|
specialChars := "!@#$%^&*()_+-=[]{}|;:,.<>?"
|
|
|
|
for _, char := range password {
|
|
switch {
|
|
case char >= 'A' && char <= 'Z':
|
|
hasUpper = true
|
|
case char >= 'a' && char <= 'z':
|
|
hasLower = true
|
|
case char >= '0' && char <= '9':
|
|
hasDigit = true
|
|
case strings.ContainsRune(specialChars, char):
|
|
hasSpecial = true
|
|
}
|
|
}
|
|
|
|
return hasUpper && hasLower && hasDigit && hasSpecial
|
|
}
|
|
|
|
func ValidateTimeStamp(header http.Header) bool {
|
|
if isDebug {
|
|
return true
|
|
}
|
|
|
|
timeStampStr := header.Get("X-Timestamp")
|
|
if timeStampStr == "" {
|
|
return false
|
|
}
|
|
|
|
timeStamp, err := strconv.ParseInt(timeStampStr, 10, 64)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
currentTime := time.Now().UnixMilli()
|
|
if currentTime-timeStamp > 3000 || timeStamp-currentTime > 3000 {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
func GetUserIDFromToken(token string) (int, error) {
|
|
userID, err := extractUserIDFromToken(token)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("Failed to extract userID from token: %w", err)
|
|
}
|
|
return userID, nil
|
|
}
|
|
|
|
func DeleteTokenInfo(userID int) error {
|
|
tokenMux.Lock()
|
|
defer tokenMux.Unlock()
|
|
delete(tokenMap, userID)
|
|
postLog.Debug(fmt.Sprintf("[DeleteTokenInfo] Removed token for userID %d", userID))
|
|
return nil
|
|
}
|
|
|
|
func GetUsernameByID(userID int) (string) {
|
|
user, err := GetUserByID(userID)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
return user.Username
|
|
}
|