From f8b43cc54f3207fd192e10993e57ee5a63c79c42 Mon Sep 17 00:00:00 2001 From: GitProtogen Date: Tue, 10 Mar 2026 15:43:47 +0100 Subject: [PATCH] idk --- go.mod | 12 ++++++- go.sum | 24 ++++++++++++++ tokens.go | 98 +++++++++++++++++++++++++++++++++++++++++++++++++------ 3 files changed, 123 insertions(+), 11 deletions(-) diff --git a/go.mod b/go.mod index 83804e5..4ee0756 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,14 @@ module go-socket go 1.26 -require nhooyr.io/websocket v1.8.17 +require ( + github.com/redis/go-redis/v9 v9.18.0 + nhooyr.io/websocket v1.8.17 +) + +require ( + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/golang-jwt/jwt/v5 v5.3.1 // indirect + go.uber.org/atomic v1.11.0 // indirect +) diff --git a/go.sum b/go.sum index 9c3072b..1c8f8cd 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,26 @@ +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= +github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/redis/go-redis/v9 v9.18.0 h1:pMkxYPkEbMPwRdenAzUNyFNrDgHx9U+DrBabWNfSRQs= +github.com/redis/go-redis/v9 v9.18.0/go.mod h1:k3ufPphLU5YXwNTUcCRXGxUoF1fqxnhFQmscfkCoDA0= +github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= +github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= nhooyr.io/websocket v1.8.17 h1:KEVeLJkUywCKVsnLIDlD/5gtayKp8VoCkksHCGGfT9Y= nhooyr.io/websocket v1.8.17/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c= diff --git a/tokens.go b/tokens.go index 5155820..d73e4c6 100644 --- a/tokens.go +++ b/tokens.go @@ -1,23 +1,101 @@ package main import ( - _ "context" + "context" "crypto/rand" "crypto/sha256" "encoding/hex" - _ "fmt" - _ "time" + "fmt" + "time" + + "github.com/redis/go-redis/v9" ) -func hashToken(token string) string { - var hash = sha256.Sum256([]byte(token)) - return hex.EncodeToString(hash[:]) +var rdb *redis.Client + +func init() { + rdb = redis.NewClient(&redis.Options{ + Addr: "localhost:6379", + Password: "", + DB: 0, + }) } -func generateToken() string { - var bytes = make([]byte, 32) +func getAndSaveTokenFor(ctx context.Context, userID string, timeToDie time.Duration) string { + bytes := make([]byte, 32) if _, err := rand.Read(bytes); err != nil { - panic(err) + panic(fmt.Sprintf("failed to generate random bytes: %v", err)) } - return hex.EncodeToString(bytes) + token := hex.EncodeToString(bytes) + tokenHash := hashToken(token) + tokenKey := fmt.Sprintf("token:%s", tokenHash) + userKey := fmt.Sprintf("user_tokens:%s", userID) + + now := time.Now() + expiresAt := now.Add(timeToDie) + + pipe := rdb.Pipeline() + pipe.Set(ctx, tokenKey, userID, timeToDie) + // score = expiration unix timestamp + pipe.ZAdd(ctx, userKey, redis.Z{Score: float64(expiresAt.Unix()), Member: tokenHash}) + // remove already-expired members + pipe.ZRemRangeByScore(ctx, userKey, "-inf", fmt.Sprintf("%d", now.Unix())) + // set key expiry to latest possible token death + pipe.ExpireAt(ctx, userKey, expiresAt) + + if _, err := pipe.Exec(ctx); err != nil { + panic(fmt.Sprintf("failed to execute redis pipeline: %v", err)) + } + + return token +} + +func hashToken(plaintext string) string { + h := sha256.Sum256([]byte(plaintext)) + return hex.EncodeToString(h[:]) +} + +// GetUserID resolves a plaintext token to its owning userID. +// Returns an error if the token is missing or expired. +func GetUserID(ctx context.Context, plaintext string) (string, error) { + key := fmt.Sprintf("token:%s", hashToken(plaintext)) + + userID, err := rdb.Get(ctx, key).Result() + if err == redis.Nil { + return "", fmt.Errorf("token not found or expired") + } + return userID, err +} + +// RevokeToken deletes a single token by its plaintext value. +func RevokeToken(ctx context.Context, plaintext, userID string) error { + hashed := hashToken(plaintext) + tokenKey := fmt.Sprintf("token:%s", hashed) + userKey := fmt.Sprintf("user_tokens:%s", userID) + + pipe := rdb.Pipeline() + pipe.Del(ctx, tokenKey) + pipe.SRem(ctx, userKey, hashed) + + _, err := pipe.Exec(ctx) + return err +} + +// RevokeAllUserTokens deletes every token belonging to a userID. +func RevokeAllUserTokens(ctx context.Context, userID string) error { + userKey := fmt.Sprintf("user_tokens:%s", userID) + + hashedTokens, err := rdb.SMembers(ctx, userKey).Result() + if err != nil { + return err + } + + pipe := rdb.Pipeline() + for _, hashed := range hashedTokens { + pipe.Del(ctx, fmt.Sprintf("token:%s", hashed)) + } + pipe.Del(ctx, userKey) + + _, err = pipe.Exec(ctx) + return err }