mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-04-28 03:20:11 +00:00
200 lines
5.3 KiB
Go
200 lines
5.3 KiB
Go
package cloudauth
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestGenerateHandoffKey(t *testing.T) {
|
|
key, err := GenerateHandoffKey()
|
|
if err != nil {
|
|
t.Fatalf("GenerateHandoffKey: %v", err)
|
|
}
|
|
if len(key) != 32 {
|
|
t.Fatalf("expected 32 bytes, got %d", len(key))
|
|
}
|
|
// Ensure two keys are different (probabilistic but extremely reliable).
|
|
key2, _ := GenerateHandoffKey()
|
|
if string(key) == string(key2) {
|
|
t.Fatal("two generated keys are identical")
|
|
}
|
|
}
|
|
|
|
func TestSignVerifyRoundTrip(t *testing.T) {
|
|
key, _ := GenerateHandoffKey()
|
|
token, err := Sign(key, "alice@example.com", "t-abc123", 60*time.Second)
|
|
if err != nil {
|
|
t.Fatalf("Sign: %v", err)
|
|
}
|
|
if token == "" {
|
|
t.Fatal("Sign returned empty token")
|
|
}
|
|
|
|
email, tenantID, err := Verify(key, token)
|
|
if err != nil {
|
|
t.Fatalf("Verify: %v", err)
|
|
}
|
|
if email != "alice@example.com" {
|
|
t.Errorf("email = %q, want alice@example.com", email)
|
|
}
|
|
if tenantID != "t-abc123" {
|
|
t.Errorf("tenantID = %q, want t-abc123", tenantID)
|
|
}
|
|
}
|
|
|
|
func TestVerifyExpiredToken(t *testing.T) {
|
|
key, _ := GenerateHandoffKey()
|
|
// Sign with a TTL of -1 second so the token is already expired.
|
|
token, err := Sign(key, "bob@example.com", "t-xyz", -1*time.Second)
|
|
if err != nil {
|
|
t.Fatalf("Sign: %v", err)
|
|
}
|
|
|
|
_, _, err = Verify(key, token)
|
|
if err != ErrHandoffExpired {
|
|
t.Fatalf("expected ErrHandoffExpired, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestVerifyWrongKey(t *testing.T) {
|
|
key1, _ := GenerateHandoffKey()
|
|
key2, _ := GenerateHandoffKey()
|
|
|
|
token, _ := Sign(key1, "carol@example.com", "t-111", 60*time.Second)
|
|
|
|
_, _, err := Verify(key2, token)
|
|
if err != ErrHandoffInvalid {
|
|
t.Fatalf("expected ErrHandoffInvalid, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestVerifyTamperedPayload(t *testing.T) {
|
|
key, _ := GenerateHandoffKey()
|
|
token, _ := Sign(key, "dave@example.com", "t-222", 60*time.Second)
|
|
|
|
// Tamper with the payload portion (before the dot).
|
|
parts := strings.SplitN(token, ".", 2)
|
|
if len(parts) != 2 {
|
|
t.Fatal("token missing dot separator")
|
|
}
|
|
|
|
// Decode payload, modify, re-encode.
|
|
payloadBytes, _ := base64.RawURLEncoding.DecodeString(parts[0])
|
|
tampered := make([]byte, len(payloadBytes))
|
|
copy(tampered, payloadBytes)
|
|
// Flip a byte.
|
|
if len(tampered) > 5 {
|
|
tampered[5] ^= 0xFF
|
|
}
|
|
parts[0] = base64.RawURLEncoding.EncodeToString(tampered)
|
|
tamperedToken := parts[0] + "." + parts[1]
|
|
|
|
_, _, err := Verify(key, tamperedToken)
|
|
if err != ErrHandoffInvalid {
|
|
t.Fatalf("expected ErrHandoffInvalid, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestVerifyWithExpiryReturnsTokenExpiry(t *testing.T) {
|
|
key, _ := GenerateHandoffKey()
|
|
ttl := 2 * time.Minute
|
|
before := time.Now().UTC()
|
|
token, err := Sign(key, "eve@example.com", "t-expiry", ttl)
|
|
if err != nil {
|
|
t.Fatalf("Sign: %v", err)
|
|
}
|
|
|
|
email, tenantID, expiresAt, err := VerifyWithExpiry(key, token)
|
|
if err != nil {
|
|
t.Fatalf("VerifyWithExpiry: %v", err)
|
|
}
|
|
if email != "eve@example.com" {
|
|
t.Fatalf("email = %q, want eve@example.com", email)
|
|
}
|
|
if tenantID != "t-expiry" {
|
|
t.Fatalf("tenantID = %q, want t-expiry", tenantID)
|
|
}
|
|
|
|
lowerBound := before.Add(ttl - 5*time.Second)
|
|
upperBound := before.Add(ttl + 5*time.Second)
|
|
if expiresAt.Before(lowerBound) || expiresAt.After(upperBound) {
|
|
t.Fatalf("expiresAt = %s, expected between %s and %s", expiresAt, lowerBound, upperBound)
|
|
}
|
|
}
|
|
|
|
func TestSignVerifyClaimsRoundTrip(t *testing.T) {
|
|
key, _ := GenerateHandoffKey()
|
|
ttl := 90 * time.Second
|
|
before := time.Now().UTC()
|
|
token, err := SignWithClaims(key, Claims{
|
|
Email: "owner@example.com",
|
|
TenantID: "t-claims",
|
|
AccountID: "acct-claims",
|
|
UserID: "user-claims",
|
|
Role: "owner",
|
|
}, ttl)
|
|
if err != nil {
|
|
t.Fatalf("SignWithClaims: %v", err)
|
|
}
|
|
|
|
claims, err := VerifyClaimsWithExpiry(key, token)
|
|
if err != nil {
|
|
t.Fatalf("VerifyClaimsWithExpiry: %v", err)
|
|
}
|
|
if claims.Email != "owner@example.com" {
|
|
t.Fatalf("claims.Email = %q, want %q", claims.Email, "owner@example.com")
|
|
}
|
|
if claims.TenantID != "t-claims" {
|
|
t.Fatalf("claims.TenantID = %q, want %q", claims.TenantID, "t-claims")
|
|
}
|
|
if claims.AccountID != "acct-claims" {
|
|
t.Fatalf("claims.AccountID = %q, want %q", claims.AccountID, "acct-claims")
|
|
}
|
|
if claims.UserID != "user-claims" {
|
|
t.Fatalf("claims.UserID = %q, want %q", claims.UserID, "user-claims")
|
|
}
|
|
if claims.Role != "owner" {
|
|
t.Fatalf("claims.Role = %q, want %q", claims.Role, "owner")
|
|
}
|
|
|
|
lowerBound := before.Add(ttl - 5*time.Second)
|
|
upperBound := before.Add(ttl + 5*time.Second)
|
|
if claims.ExpiresAt.Before(lowerBound) || claims.ExpiresAt.After(upperBound) {
|
|
t.Fatalf("claims.ExpiresAt = %s, expected between %s and %s", claims.ExpiresAt, lowerBound, upperBound)
|
|
}
|
|
}
|
|
|
|
func TestVerifyEmptyInputs(t *testing.T) {
|
|
key, _ := GenerateHandoffKey()
|
|
|
|
_, _, err := Verify(nil, "some-token")
|
|
if err != ErrHandoffInvalid {
|
|
t.Errorf("nil key: expected ErrHandoffInvalid, got %v", err)
|
|
}
|
|
|
|
_, _, err = Verify(key, "")
|
|
if err != ErrHandoffInvalid {
|
|
t.Errorf("empty token: expected ErrHandoffInvalid, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestSignValidation(t *testing.T) {
|
|
key, _ := GenerateHandoffKey()
|
|
|
|
_, err := Sign(nil, "a@b.com", "t-1", time.Minute)
|
|
if err == nil {
|
|
t.Error("Sign with nil key should fail")
|
|
}
|
|
|
|
_, err = Sign(key, "", "t-1", time.Minute)
|
|
if err == nil {
|
|
t.Error("Sign with empty email should fail")
|
|
}
|
|
|
|
_, err = Sign(key, "a@b.com", "", time.Minute)
|
|
if err == nil {
|
|
t.Error("Sign with empty tenantID should fail")
|
|
}
|
|
}
|