Add HMAC restore-by-host for phone-less rebind; resolve gateway at request time and recreate web after stack up. Co-authored-by: Cursor <cursoragent@cursor.com>
89 lines
2.3 KiB
Go
89 lines
2.3 KiB
Go
package yuhticket
|
||
|
||
import (
|
||
"testing"
|
||
"time"
|
||
)
|
||
|
||
func TestSignVerifyRoundTrip(t *testing.T) {
|
||
secret := "test-secret"
|
||
now := time.Unix(1_700_000_000, 0).UTC()
|
||
c := Claims{
|
||
Iss: "yuheng", Aud: "aijianzhan", Phone: "13531041944",
|
||
HostKey: "host-1", Exp: now.Add(90 * time.Second).Unix(), JTI: "jti-ok", Scope: "sync_bind",
|
||
}
|
||
tok, err := Sign(secret, c)
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
got, err := Verify(tok, VerifyOpts{Secret: secret, Now: now})
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if got.Phone != c.Phone || got.HostKey != c.HostKey {
|
||
t.Fatalf("claims mismatch: %+v", got)
|
||
}
|
||
}
|
||
|
||
func TestVerifyBadSig(t *testing.T) {
|
||
now := time.Unix(1_700_000_000, 0).UTC()
|
||
tok, _ := Sign("a", Claims{
|
||
Phone: "13800000001", HostKey: "h", Exp: now.Add(60 * time.Second).Unix(), JTI: "j1",
|
||
})
|
||
if _, err := Verify(tok, VerifyOpts{Secret: "b", Now: now}); err == nil {
|
||
t.Fatal("expected bad sig")
|
||
}
|
||
}
|
||
|
||
func TestSignVerifyRestoreNoPhone(t *testing.T) {
|
||
secret := "test-secret"
|
||
now := time.Unix(1_700_000_000, 0).UTC()
|
||
c := Claims{
|
||
Iss: "yuheng", Aud: "aijianzhan", HostKey: "6655aabbccddeeff00112233",
|
||
Exp: now.Add(90 * time.Second).Unix(), JTI: "jti-restore", Scope: "sync_restore",
|
||
YuhengUserID: "6655aabbccddeeff00112233",
|
||
}
|
||
tok, err := Sign(secret, c)
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
got, err := Verify(tok, VerifyOpts{
|
||
Secret: secret, Now: now, AllowEmptyPhone: true,
|
||
AllowedScopes: []string{"sync_bind", "sync_restore"},
|
||
})
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if got.HostKey != c.HostKey || got.Phone != "" {
|
||
t.Fatalf("claims mismatch: %+v", got)
|
||
}
|
||
// 默认 Verify(须 phone)应拒绝无 phone 的 sync_bind;sync_restore 在默认 scopes 外也应拒绝
|
||
if _, err := Verify(tok, VerifyOpts{Secret: secret, Now: now}); err == nil {
|
||
t.Fatal("expected scope reject without AllowedScopes")
|
||
}
|
||
}
|
||
|
||
func TestSignRequiresPhoneForSyncBind(t *testing.T) {
|
||
_, err := Sign("s", Claims{
|
||
HostKey: "h", Exp: time.Now().Add(time.Minute).Unix(), JTI: "j", Scope: "sync_bind",
|
||
})
|
||
if err == nil {
|
||
t.Fatal("expected phone required for sync_bind")
|
||
}
|
||
}
|
||
|
||
func TestJTIConsume(t *testing.T) {
|
||
dir := t.TempDir()
|
||
st, err := NewJTIStore(dir)
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
exp := time.Now().Add(time.Minute).Unix()
|
||
if err := st.Consume("x", exp); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if err := st.Consume("x", exp); err == nil {
|
||
t.Fatal("expected replay")
|
||
}
|
||
}
|