idk
This commit is contained in:
@@ -2,4 +2,14 @@ module go-socket
|
|||||||
|
|
||||||
go 1.26
|
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
|
||||||
|
)
|
||||||
|
|||||||
@@ -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 h1:KEVeLJkUywCKVsnLIDlD/5gtayKp8VoCkksHCGGfT9Y=
|
||||||
nhooyr.io/websocket v1.8.17/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c=
|
nhooyr.io/websocket v1.8.17/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c=
|
||||||
|
|||||||
@@ -1,23 +1,101 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
_ "context"
|
"context"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
_ "fmt"
|
"fmt"
|
||||||
_ "time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/redis/go-redis/v9"
|
||||||
)
|
)
|
||||||
|
|
||||||
func hashToken(token string) string {
|
var rdb *redis.Client
|
||||||
var hash = sha256.Sum256([]byte(token))
|
|
||||||
return hex.EncodeToString(hash[:])
|
func init() {
|
||||||
|
rdb = redis.NewClient(&redis.Options{
|
||||||
|
Addr: "localhost:6379",
|
||||||
|
Password: "",
|
||||||
|
DB: 0,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateToken() string {
|
func getAndSaveTokenFor(ctx context.Context, userID string, timeToDie time.Duration) string {
|
||||||
var bytes = make([]byte, 32)
|
bytes := make([]byte, 32)
|
||||||
if _, err := rand.Read(bytes); err != nil {
|
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
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user