| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180 |
- package auth
- import (
- "regexp"
- "strings"
- "testing"
- )
- // 32 字节 base64url 无填充后固定 43 个字符。
- const b64Len = 43
- func TestRandomTokenFormat(t *testing.T) {
- seen := make(map[string]bool, 100)
- for i := 0; i < 100; i++ {
- tok, err := randomToken()
- if err != nil {
- t.Fatalf("randomToken() 返回错误: %v", err)
- }
- if len(tok) != b64Len {
- t.Fatalf("base64url(32 字节) 长度应为 %d,实际 %d (%q)", b64Len, len(tok), tok)
- }
- if !regexp.MustCompile(`^[A-Za-z0-9_-]+$`).MatchString(tok) {
- t.Fatalf("token 含 base64url 之外的字符: %q", tok)
- }
- if seen[tok] {
- t.Fatalf("生成了重复 token: %q", tok)
- }
- seen[tok] = true
- }
- }
- func TestSessionAndDeviceTokenUnique(t *testing.T) {
- a, err := newSessionToken()
- if err != nil {
- t.Fatal(err)
- }
- if !strings.HasPrefix(a, sessionTokenPrefix) {
- t.Errorf("会话 token 应以 %q 开头,实际 %q", sessionTokenPrefix, a)
- }
- b, err := newDeviceToken()
- if err != nil {
- t.Fatal(err)
- }
- if !strings.HasPrefix(b, deviceTokenPrefix) {
- t.Errorf("设备 token 应以 %q 开头,实际 %q", deviceTokenPrefix, b)
- }
- if a == b {
- t.Error("两次生成的 token 不应相同")
- }
- }
- func TestPendingTokenTrustDevice(t *testing.T) {
- t.Run("不带授信标记", func(t *testing.T) {
- plain, err := newPendingToken(false)
- if err != nil {
- t.Fatal(err)
- }
- if strings.HasSuffix(plain, pendingTrustSuffixOn) {
- t.Fatalf("未请求授信时不应带后缀: %q", plain)
- }
- token, trust := splitPendingToken(plain)
- if trust {
- t.Error("splitPendingToken 应返回 trustDevice=false")
- }
- if token != plain {
- t.Errorf("无后缀时应原样返回,实际 %q", token)
- }
- })
- t.Run("带授信标记", func(t *testing.T) {
- plain, err := newPendingToken(true)
- if err != nil {
- t.Fatal(err)
- }
- if !strings.HasSuffix(plain, pendingTrustSuffixOn) {
- t.Fatalf("请求授信时应带后缀: %q", plain)
- }
- token, trust := splitPendingToken(plain)
- if !trust {
- t.Error("splitPendingToken 应返回 trustDevice=true")
- }
- if strings.HasSuffix(token, pendingTrustSuffixOn) {
- t.Errorf("剥离后不应残留后缀: %q", token)
- }
- if len(token) != len(pendingTokenPrefix)+b64Len {
- t.Errorf("剥离后长度异常: %d", len(token))
- }
- })
- }
- func TestPendingTokenHashIgnoresSuffix(t *testing.T) {
- // 同一个 token 是否请求授信,剥离后缀后哈希必须一致;
- // 不剥离直接哈希则不同 —— 这正是「先剥离再哈希」不可省略的原因。
- plain, err := newPendingToken(true)
- if err != nil {
- t.Fatal(err)
- }
- base, trust := splitPendingToken(plain)
- if !trust {
- t.Fatal("splitPendingToken 应识别出授信标记")
- }
- if HashToken(plain) == HashToken(base) {
- t.Error("未剥离后缀时哈希应不同,说明剥离步骤不可省略")
- }
- rebuilt := base + pendingTrustSuffixOn
- if stripped, _ := splitPendingToken(rebuilt); HashToken(stripped) != HashToken(base) {
- t.Error("带后缀的值剥离后应与 base 哈希一致")
- }
- }
- func TestPendingTokenSuffixStrippedBeforeHash(t *testing.T) {
- // 直接对带后缀的明文哈希会查不到记录,这是调用方必须遵守的顺序。
- if HashToken("tok:1") == HashToken("tok") {
- t.Error("带后缀与不带后缀的哈希应不同")
- }
- }
- func TestAPIToken(t *testing.T) {
- t.Run("read", func(t *testing.T) {
- token, prefix, err := newAPIToken("read")
- if err != nil {
- t.Fatal(err)
- }
- if !strings.HasPrefix(token, apiTokenReadPrefix) {
- t.Errorf("read token 应以 %q 开头,实际 %q", apiTokenReadPrefix, token)
- }
- if len(prefix) != tokenPrefixLen {
- t.Errorf("token_prefix 长度应为 %d,实际 %d (%q)", tokenPrefixLen, len(prefix), prefix)
- }
- if !strings.HasPrefix(token, prefix) {
- t.Errorf("token_prefix 应是明文前缀: prefix=%q token=%q", prefix, token)
- }
- })
- t.Run("read_write", func(t *testing.T) {
- token, _, err := newAPIToken("read_write")
- if err != nil {
- t.Fatal(err)
- }
- if !strings.HasPrefix(token, apiTokenWritePrefix) {
- t.Errorf("read_write token 应以 %q 开头,实际 %q", apiTokenWritePrefix, token)
- }
- })
- t.Run("非法 scope", func(t *testing.T) {
- if _, _, err := newAPIToken("admin"); err == nil {
- t.Error("非法 scope 应返回错误")
- }
- })
- }
- func TestHashToken(t *testing.T) {
- got := HashToken("hello")
- // 固定向量,防止哈希实现被意外改动
- const want = "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
- if got != want {
- t.Errorf("HashToken(\"hello\") = %q,期望 %q", got, want)
- }
- if len(got) != 64 {
- t.Errorf("SHA-256 十六进制长度应为 64,实际 %d", len(got))
- }
- }
- func TestSecureEqual(t *testing.T) {
- if !secureEqual("abc", "abc") {
- t.Error("相同字符串应返回 true")
- }
- if secureEqual("abc", "abd") {
- t.Error("不同字符串应返回 false")
- }
- if secureEqual("abc", "abcd") {
- t.Error("不同长度应返回 false")
- }
- }
|