mirror of
https://github.com/Buriburizaem0n/nezha_domains.git
synced 2026-09-19 09:40:12 +00:00
Replace the {user_id, ip} claim pair with {keyId, uid}:
- keyId is a 32-byte random id that points to a row in the new
jwt_sessions table holding the real user id, bound IP, UA hash,
TokenVersion and expiry.
- uid is the user id encoded through pkg/idcodec; mismatch between
claim uid and session.UserID trips WAF block on the caller IP.
- identityHandler now rejects unknown/revoked/expired sessions, IP
drift and stale TokenVersion. Refresh updates session.ExpiresAt.
User.TokenVersion bumps on password change and revokes outstanding
sessions, so a leaked JWT secret alone is no longer enough to forge
a token. JWTSession rows are GC'd every 10 minutes (expired + grace
or revoked >24h). OAuth2 callback shares the same issue path.
Includes regression tests for happy path, mismatched claim uid,
revoked session, TokenVersion bump, IP drift and unknown keyId.
Co-authored-by: cloudcode <cloudcode@users.noreply.github.com>
56 lines
1.3 KiB
Go
56 lines
1.3 KiB
Go
package singleton
|
|
|
|
import (
|
|
"log"
|
|
"time"
|
|
|
|
"github.com/nezhahq/nezha/model"
|
|
)
|
|
|
|
const (
|
|
JWTSessionGCSchedule = "@every 10m"
|
|
JWTSessionRevokedRetention = 24 * time.Hour
|
|
JWTSessionExpiredGrace = 1 * time.Hour
|
|
)
|
|
|
|
func StartJWTSessionGC() error {
|
|
if _, err := CronShared.AddFunc(JWTSessionGCSchedule, RunJWTSessionGC); err != nil {
|
|
return err
|
|
}
|
|
RunJWTSessionGC()
|
|
return nil
|
|
}
|
|
|
|
func RunJWTSessionGC() {
|
|
if DB == nil {
|
|
return
|
|
}
|
|
now := time.Now()
|
|
|
|
if err := DB.
|
|
Where("expires_at < ?", now.Add(-JWTSessionExpiredGrace)).
|
|
Delete(&model.JWTSession{}).Error; err != nil {
|
|
log.Printf("NEZHA>> JWTSession GC delete expired failed: %v", err)
|
|
}
|
|
|
|
if err := DB.
|
|
Where("revoked_at IS NOT NULL AND revoked_at < ?", now.Add(-JWTSessionRevokedRetention)).
|
|
Delete(&model.JWTSession{}).Error; err != nil {
|
|
log.Printf("NEZHA>> JWTSession GC delete revoked failed: %v", err)
|
|
}
|
|
}
|
|
|
|
func RevokeJWTSession(keyID string) error {
|
|
now := time.Now()
|
|
return DB.Model(&model.JWTSession{}).
|
|
Where("key_id = ? AND revoked_at IS NULL", keyID).
|
|
Update("revoked_at", &now).Error
|
|
}
|
|
|
|
func RevokeJWTSessionsByUser(userID uint64) error {
|
|
now := time.Now()
|
|
return DB.Model(&model.JWTSession{}).
|
|
Where("user_id = ? AND revoked_at IS NULL", userID).
|
|
Update("revoked_at", &now).Error
|
|
}
|