81 lines
2.0 KiB
Go
81 lines
2.0 KiB
Go
package handlers
|
||
|
||
import (
|
||
"crypto/rand"
|
||
"crypto/sha256"
|
||
"crypto/subtle"
|
||
"encoding/hex"
|
||
"fmt"
|
||
"net/http"
|
||
"strings"
|
||
|
||
"github.com/gin-gonic/gin"
|
||
|
||
"sproutgate-backend/internal/auth"
|
||
"sproutgate-backend/internal/models"
|
||
)
|
||
|
||
func bearerToken(header string) string {
|
||
if header == "" {
|
||
return ""
|
||
}
|
||
if strings.HasPrefix(strings.ToLower(header), "bearer ") {
|
||
return strings.TrimSpace(header[7:])
|
||
}
|
||
return ""
|
||
}
|
||
|
||
func adminTokenFromRequest(c *gin.Context) string {
|
||
if token := strings.TrimSpace(c.Query("token")); token != "" {
|
||
return token
|
||
}
|
||
if token := strings.TrimSpace(c.GetHeader("X-Admin-Token")); token != "" {
|
||
return token
|
||
}
|
||
authHeader := strings.TrimSpace(c.GetHeader("Authorization"))
|
||
return bearerToken(authHeader)
|
||
}
|
||
|
||
func generateVerificationCode() (string, error) {
|
||
randomBytes := make([]byte, 3)
|
||
if _, err := rand.Read(randomBytes); err != nil {
|
||
return "", err
|
||
}
|
||
number := int(randomBytes[0])<<16 | int(randomBytes[1])<<8 | int(randomBytes[2])
|
||
return fmt.Sprintf("%06d", number%1000000), nil
|
||
}
|
||
|
||
func hashCode(code string) string {
|
||
sum := sha256.Sum256([]byte(code))
|
||
return hex.EncodeToString(sum[:])
|
||
}
|
||
|
||
func verifyCode(code string, hash string) bool {
|
||
return subtle.ConstantTimeCompare([]byte(hashCode(code)), []byte(hash)) == 1
|
||
}
|
||
|
||
func writeBanJSON(c *gin.Context, reason string) {
|
||
h := gin.H{"error": "account is banned", "valid": false}
|
||
if r := strings.TrimSpace(reason); r != "" {
|
||
h["banReason"] = r
|
||
}
|
||
c.JSON(http.StatusForbidden, h)
|
||
}
|
||
|
||
// abortIfTokenEpochStale 若 JWT 内 epoch 与用户当前 token_epoch 不一致,则拒绝(例如管理员已封禁并递增 epoch)。
|
||
func abortIfTokenEpochStale(c *gin.Context, claims *auth.Claims, u models.UserRecord) bool {
|
||
if claims.TokenEpoch == u.TokenEpoch {
|
||
return false
|
||
}
|
||
c.JSON(http.StatusUnauthorized, gin.H{"valid": false, "error": "token revoked"})
|
||
return true
|
||
}
|
||
|
||
func abortIfUserBanned(c *gin.Context, u models.UserRecord) bool {
|
||
if !u.Banned {
|
||
return false
|
||
}
|
||
writeBanJSON(c, u.BanReason)
|
||
return true
|
||
}
|