feat: add im auth verification flows

This commit is contained in:
richjohnrambo
2026-08-12 00:20:27 +08:00
parent 1ffcb85bee
commit d239e37f56
16 changed files with 2030 additions and 22 deletions
+70 -11
View File
@@ -6,9 +6,23 @@ import (
"time"
"github.com/tinode/chat/server/auth"
"github.com/tinode/chat/server/store"
"github.com/tinode/chat/server/store/types"
)
type testSecurityStore struct {
version int
err error
}
func (s testSecurityStore) GetAuthVersion(types.TenantID, types.Uid) (int, error) {
return s.version, s.err
}
func (s testSecurityStore) BumpAuthVersion(types.TenantID, types.Uid, time.Time) (int, error) {
return s.version + 1, s.err
}
func newTestAuthenticator(t *testing.T) *authenticator {
t.Helper()
conf, err := json.Marshal(map[string]any{
@@ -26,20 +40,25 @@ func newTestAuthenticator(t *testing.T) *authenticator {
return ta
}
func TestTokenV2TenantRoundTrip(t *testing.T) {
func TestTokenV3TenantRoundTrip(t *testing.T) {
oldSecurity := store.Security
store.Security = testSecurityStore{version: 11}
defer func() { store.Security = oldSecurity }()
ta := newTestAuthenticator(t)
rec := &auth.Rec{
TenantID: 7,
Uid: 42,
AuthLevel: auth.LevelAuth,
Lifetime: auth.Duration(time.Hour),
Features: auth.FeatureValidated,
TenantID: 7,
Uid: 42,
AuthLevel: auth.LevelAuth,
Lifetime: auth.Duration(time.Hour),
Features: auth.FeatureValidated,
AuthVersion: 11,
}
token, _, err := ta.GenSecret(rec)
if err != nil {
t.Fatal(err)
}
if got, want := len(token), 59; got != want {
if got, want := len(token), 63; got != want {
t.Fatalf("token length: got %d, want %d", got, want)
}
@@ -50,7 +69,8 @@ func TestTokenV2TenantRoundTrip(t *testing.T) {
if challenge != nil {
t.Fatal("unexpected challenge")
}
if got.TenantID != rec.TenantID || got.Uid != rec.Uid || got.AuthLevel != rec.AuthLevel || got.Features != rec.Features {
if got.TenantID != rec.TenantID || got.Uid != rec.Uid || got.AuthLevel != rec.AuthLevel ||
got.Features != rec.Features || got.AuthVersion != rec.AuthVersion {
t.Fatalf("round trip mismatch: got %+v, want %+v", got, rec)
}
@@ -61,10 +81,14 @@ func TestTokenV2TenantRoundTrip(t *testing.T) {
}
}
func TestTokenV2RejectsWrongOrTamperedTenant(t *testing.T) {
func TestTokenV3RejectsWrongOrTamperedTenant(t *testing.T) {
oldSecurity := store.Security
store.Security = testSecurityStore{version: 1}
defer func() { store.Security = oldSecurity }()
ta := newTestAuthenticator(t)
token, _, err := ta.GenSecret(&auth.Rec{
TenantID: 7, Uid: 42, AuthLevel: auth.LevelAuth, Lifetime: auth.Duration(time.Hour),
TenantID: 7, Uid: 42, AuthLevel: auth.LevelAuth, Lifetime: auth.Duration(time.Hour), AuthVersion: 1,
})
if err != nil {
t.Fatal(err)
@@ -81,7 +105,11 @@ func TestTokenV2RejectsWrongOrTamperedTenant(t *testing.T) {
}
}
func TestTokenV2RejectsLegacyAndMissingIdentity(t *testing.T) {
func TestTokenV3RejectsLegacyMissingIdentityAndStaleVersion(t *testing.T) {
oldSecurity := store.Security
store.Security = testSecurityStore{version: 2}
defer func() { store.Security = oldSecurity }()
ta := newTestAuthenticator(t)
if _, _, err := ta.Authenticate(auth.AuthContext{}, make([]byte, 50)); err != types.ErrMalformed {
t.Fatalf("legacy token: got %v, want %v", err, types.ErrMalformed)
@@ -92,4 +120,35 @@ func TestTokenV2RejectsLegacyAndMissingIdentity(t *testing.T) {
if _, _, err := ta.GenSecret(&auth.Rec{TenantID: 7, AuthLevel: auth.LevelAuth}); err != types.ErrMalformed {
t.Fatalf("missing uid: got %v, want %v", err, types.ErrMalformed)
}
token, _, err := ta.GenSecret(&auth.Rec{
TenantID: 7, Uid: 42, AuthLevel: auth.LevelAuth, Lifetime: auth.Duration(time.Hour), AuthVersion: 1,
})
if err != nil {
t.Fatal(err)
}
if _, _, err = ta.Authenticate(auth.AuthContext{TenantID: 7}, token); err != types.ErrFailed {
t.Fatalf("stale auth version: got %v, want %v", err, types.ErrFailed)
}
}
func TestTokenV3NoLoginDoesNotRequireAuthVersion(t *testing.T) {
oldSecurity := store.Security
store.Security = testSecurityStore{version: 99}
defer func() { store.Security = oldSecurity }()
ta := newTestAuthenticator(t)
token, _, err := ta.GenSecret(&auth.Rec{
TenantID: 7, Uid: 42, AuthLevel: auth.LevelAuth, Lifetime: auth.Duration(time.Hour), Features: auth.FeatureNoLogin,
})
if err != nil {
t.Fatal(err)
}
got, _, err := ta.Authenticate(auth.AuthContext{TenantID: 7}, token)
if err != nil {
t.Fatal(err)
}
if got.AuthVersion != 0 || got.Features&auth.FeatureNoLogin == 0 {
t.Fatalf("unexpected no-login token auth: %+v", got)
}
}