feat: complete registration invitation and auth redesign
This commit is contained in:
@@ -126,6 +126,20 @@ func TestUserIdentitySchema(t *testing.T) {
|
||||
if count != 1 {
|
||||
t.Fatal("im_users.phone is missing")
|
||||
}
|
||||
if err := db.Get(&count, `SELECT COUNT(*) FROM information_schema.columns
|
||||
WHERE table_schema=DATABASE() AND table_name='im_users' AND column_name='username' AND is_nullable='NO'`); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if count != 1 {
|
||||
t.Fatal("im_users.username is missing or nullable")
|
||||
}
|
||||
if err := db.Get(&count, `SELECT COUNT(*) FROM information_schema.columns
|
||||
WHERE table_schema=DATABASE() AND table_name='im_auth' AND column_name IN ('uname','scheme')`); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if count != 0 {
|
||||
t.Fatal("legacy im_auth.uname or im_auth.scheme still exists")
|
||||
}
|
||||
if err := db.Get(&count, `SELECT COUNT(*) FROM information_schema.columns
|
||||
WHERE table_schema=DATABASE() AND table_name='im_user_profiles' AND column_name='email'`); err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -275,12 +289,12 @@ func TestTenantAuthIsolation(t *testing.T) {
|
||||
userBID := userAID + 1
|
||||
for index, userID := range []int64{userAID, userBID} {
|
||||
tenantDB := quoteIdent(dbNames[index])
|
||||
if _, err := db.Exec("INSERT INTO "+tenantDB+".im_users(id,createdat,updatedat,state,access,phone,public,trusted) "+
|
||||
"VALUES(?,?,?,?,?,?,?,?)", userID, now, now, types.StateOK, `{}`, "13800000000", nil, nil); err != nil {
|
||||
if _, err := db.Exec("INSERT INTO "+tenantDB+".im_users(id,username,createdat,updatedat,state,access,phone,public,trusted) "+
|
||||
"VALUES(?,?,?,?,?,?,?,?,?)", userID, "alice", now, now, types.StateOK, `{}`, "13800000000", nil, nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := db.Exec("INSERT INTO "+tenantDB+".im_auth(uname,userid,scheme,authlvl,secret) VALUES(?,?,?,?,?)",
|
||||
"basic:alice", userID, "basic", auth.LevelAuth, []byte("hash")); err != nil {
|
||||
if _, err := db.Exec("INSERT INTO "+tenantDB+".im_auth(userid,authlvl,secret) VALUES(?,?,?)",
|
||||
userID, auth.LevelAuth, []byte("hash")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := db.Exec("INSERT INTO "+tenantDB+".im_credentials(createdat,updatedat,method,value,synthetic,userid,resp,done) "+
|
||||
@@ -289,11 +303,11 @@ func TestTenantAuthIsolation(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
uidA, _, _, _, err := tenantAdp.TenantAuthGetUniqueRecord(tenantA, "basic:alice")
|
||||
uidA, _, _, _, err := tenantAdp.TenantAuthGetByLogin(tenantA, "alice")
|
||||
if err != nil || uidA != store.EncodeUid(userAID) {
|
||||
t.Fatalf("tenant A im_auth lookup: uid=%v err=%v", uidA, err)
|
||||
}
|
||||
uidB, _, _, _, err := tenantAdp.TenantAuthGetUniqueRecord(tenantB, "basic:alice")
|
||||
uidB, _, _, _, err := tenantAdp.TenantAuthGetByLogin(tenantB, "alice")
|
||||
if err != nil || uidB != store.EncodeUid(userBID) {
|
||||
t.Fatalf("tenant B im_auth lookup: uid=%v err=%v", uidB, err)
|
||||
}
|
||||
@@ -306,7 +320,7 @@ func TestTenantAuthIsolation(t *testing.T) {
|
||||
t.Fatalf("tenant B credential lookup: uid=%v err=%v", credB, err)
|
||||
}
|
||||
|
||||
if err = tenantAdp.TenantAuthUpdRecord(tenantA, uidB, "basic", "basic:changed",
|
||||
if err = tenantAdp.TenantAuthUpdateRecord(tenantA, uidB, "changed",
|
||||
auth.LevelAuth, nil, time.Time{}); err != types.ErrNotFound {
|
||||
t.Fatalf("cross-tenant im_auth update: got %v, want %v", err, types.ErrNotFound)
|
||||
}
|
||||
@@ -382,7 +396,7 @@ func TestCredUpsert(t *testing.T) {
|
||||
|
||||
func TestAuthAddRecord(t *testing.T) {
|
||||
for _, rec := range testData.Recs {
|
||||
err := adp.AuthAddRecord(types.ParseUserId("usr"+rec.UserId), rec.Scheme, rec.Unique,
|
||||
err := adp.AuthAddRecord(types.ParseUserId("usr"+rec.UserId), rec.Scheme, strings.TrimPrefix(rec.Unique, "basic:"),
|
||||
rec.AuthLvl, rec.Secret, rec.Expires)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -390,12 +404,74 @@ func TestAuthAddRecord(t *testing.T) {
|
||||
}
|
||||
//Test duplicate
|
||||
err := adp.AuthAddRecord(types.ParseUserId("usr"+testData.Users[0].Id), testData.Recs[0].Scheme,
|
||||
testData.Recs[0].Unique, testData.Recs[0].AuthLvl, testData.Recs[0].Secret, testData.Recs[0].Expires)
|
||||
strings.TrimPrefix(testData.Recs[0].Unique, "basic:"), testData.Recs[0].AuthLvl, testData.Recs[0].Secret, testData.Recs[0].Expires)
|
||||
if err != types.ErrDuplicate {
|
||||
t.Fatal("Should be duplicate error but got", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegistrationInviteLifecycle(t *testing.T) {
|
||||
inviteAdapter, ok := adp.(adapter.TenantRegistrationInviteAdapter)
|
||||
if !ok {
|
||||
t.Fatal("MySQL adapter does not implement TenantRegistrationInviteAdapter")
|
||||
}
|
||||
var tenantID types.TenantID
|
||||
if err := db.Get(&tenantID, "SELECT id FROM im_tenant WHERE code='default'"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
now := types.TimeNow()
|
||||
invite := &types.RegistrationInviteCode{
|
||||
Code: "AbCdE",
|
||||
ExpiresAt: now.Add(time.Hour),
|
||||
CreatedBy: 10001,
|
||||
Remark: "integration test",
|
||||
}
|
||||
if err := inviteAdapter.TenantRegistrationInviteCreate(tenantID, invite); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
stored, err := inviteAdapter.TenantRegistrationInviteGetByCode(tenantID, "AbCdE")
|
||||
if err != nil || stored.Code != "AbCdE" || stored.Status != types.RegistrationInviteActive {
|
||||
t.Fatalf("stored invite=%#v err=%v", stored, err)
|
||||
}
|
||||
if _, err = inviteAdapter.TenantRegistrationInviteGetByCode(tenantID, "abcde"); err != types.ErrInviteInvalid {
|
||||
t.Fatalf("case-insensitive invite lookup was accepted: %v", err)
|
||||
}
|
||||
firstUID := types.ParseUserId("usr" + testData.Users[0].Id)
|
||||
if err = inviteAdapter.TenantRegistrationInviteRecordUse(tenantID, "AbCdE", firstUID, "req-invite-test", now); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
secondUID := types.ParseUserId("usr" + testData.Users[1].Id)
|
||||
if err = inviteAdapter.TenantRegistrationInviteRecordUse(tenantID, "AbCdE", secondUID, "req-invite-second-user", now); err != nil {
|
||||
t.Fatalf("second user could not reuse invite: %v", err)
|
||||
}
|
||||
if err = inviteAdapter.TenantRegistrationInviteSyncUsage(tenantID, invite.ID); err != nil {
|
||||
t.Fatalf("invite usage projection failed: %v", err)
|
||||
}
|
||||
stored, err = inviteAdapter.TenantRegistrationInviteGetByCode(tenantID, "AbCdE")
|
||||
if err != nil || stored.Status != types.RegistrationInviteActive || stored.UsageCount != 2 {
|
||||
t.Fatalf("reusable invite=%#v err=%v", stored, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegistrationInviteReusableSchema(t *testing.T) {
|
||||
var count int
|
||||
if err := db.Get(&count, `SELECT COUNT(*) FROM information_schema.columns
|
||||
WHERE table_schema=DATABASE() AND table_name='im_registration_invite_codes' AND column_name IN ('used_by','used_at')`); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if count != 0 {
|
||||
t.Fatalf("single-use columns still exist: %d", count)
|
||||
}
|
||||
if err := db.Get(&count, `SELECT COUNT(*) FROM information_schema.statistics
|
||||
WHERE table_schema=DATABASE() AND table_name='im_registration_invite_uses'
|
||||
AND index_name='idx_im_registration_invite_use_code' AND non_unique=1`); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if count != 1 {
|
||||
t.Fatalf("reusable invite use index count: got %d, want 1", count)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTopicCreate(t *testing.T) {
|
||||
err := adp.TopicCreate(testData.Topics[0])
|
||||
if err != nil {
|
||||
@@ -610,7 +686,7 @@ func TestCredGetAll(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAuthGetUniqueRecord(t *testing.T) {
|
||||
uid, authLvl, secret, expires, err := adp.AuthGetUniqueRecord("basic:alice")
|
||||
uid, authLvl, secret, expires, err := adp.AuthGetUniqueRecord("alice")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -636,13 +712,14 @@ func TestAuthGetRecord(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if recId != testData.Recs[0].Unique ||
|
||||
wantLogin := strings.TrimPrefix(testData.Recs[0].Unique, "basic:")
|
||||
if recId != wantLogin ||
|
||||
authLvl != testData.Recs[0].AuthLvl ||
|
||||
!reflect.DeepEqual(secret, testData.Recs[0].Secret) ||
|
||||
expires != testData.Recs[0].Expires {
|
||||
|
||||
got := fmt.Sprintf("%v %v %v %v", recId, authLvl, secret, expires)
|
||||
want := fmt.Sprintf("%v %v %v %v", testData.Recs[0].Unique, testData.Recs[0].AuthLvl, testData.Recs[0].Secret, testData.Recs[0].Expires)
|
||||
want := fmt.Sprintf("%v %v %v %v", wantLogin, testData.Recs[0].AuthLvl, testData.Recs[0].Secret, testData.Recs[0].Expires)
|
||||
t.Error(mismatchErrorString("Auth record", got, want))
|
||||
}
|
||||
|
||||
@@ -957,13 +1034,13 @@ func TestCredConfirm(t *testing.T) {
|
||||
func TestAuthUpdRecord(t *testing.T) {
|
||||
rec := testData.Recs[1]
|
||||
newSecret := []byte{'s', 'e', 'c', 'r', 'e', 't'}
|
||||
err := adp.AuthUpdRecord(types.ParseUserId("usr"+rec.UserId), rec.Scheme, rec.Unique,
|
||||
err := adp.AuthUpdRecord(types.ParseUserId("usr"+rec.UserId), rec.Scheme, strings.TrimPrefix(rec.Unique, "basic:"),
|
||||
rec.AuthLvl, newSecret, rec.Expires)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var got []byte
|
||||
err = db.QueryRow("SELECT secret FROM im_auth WHERE uname=?", rec.Unique).Scan(&got)
|
||||
err = db.QueryRow("SELECT secret FROM im_auth WHERE userid=?", decodeUid(rec.UserId)).Scan(&got)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -972,7 +1049,7 @@ func TestAuthUpdRecord(t *testing.T) {
|
||||
}
|
||||
|
||||
// Test with auth ID (unique) change
|
||||
newId := "basic:bob12345"
|
||||
newId := "bob12345"
|
||||
err = adp.AuthUpdRecord(types.ParseUserId("usr"+rec.UserId), rec.Scheme, newId,
|
||||
rec.AuthLvl, newSecret, rec.Expires)
|
||||
if err != nil {
|
||||
@@ -980,7 +1057,7 @@ func TestAuthUpdRecord(t *testing.T) {
|
||||
}
|
||||
// Test if old ID deleted
|
||||
var count int
|
||||
err = db.QueryRow("SELECT COUNT(*) FROM im_auth WHERE uname=?", rec.Unique).Scan(&count)
|
||||
err = db.QueryRow("SELECT COUNT(*) FROM im_users WHERE id=? AND username=?", decodeUid(rec.UserId), strings.TrimPrefix(rec.Unique, "basic:")).Scan(&count)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user