feat: complete registration invitation and auth redesign
This commit is contained in:
+162
-1
@@ -1074,11 +1074,48 @@ func TestServeIMTenantResolveSuccessFromHeader(t *testing.T) {
|
||||
t.Fatalf("tenant payload must not expose internal id: %#v", tenant)
|
||||
}
|
||||
endpoints := data["endpoints"].(map[string]any)
|
||||
if endpoints["websocket"] != "https://im.example.com/v0/channels" {
|
||||
if endpoints["websocket"] != "wss://im.example.com/v0/channels" {
|
||||
t.Fatalf("unexpected websocket endpoint: %#v", endpoints)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServeIMTenantResolveUsesForwardedPublicHost(t *testing.T) {
|
||||
oldTenants := store.Tenants
|
||||
oldServingAt := globals.servingAt
|
||||
store.Tenants = testTenantStore{tenant: &types.Tenant{
|
||||
ID: 7,
|
||||
Code: "acme",
|
||||
Name: "Acme Corp",
|
||||
State: types.TenantStateActive,
|
||||
}}
|
||||
defer func() {
|
||||
store.Tenants = oldTenants
|
||||
globals.servingAt = oldServingAt
|
||||
}()
|
||||
globals.servingAt = "http://:6060/"
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/v1/im/tenants/resolve", nil)
|
||||
req.Host = "internal:6060"
|
||||
req.Header.Set("X-Forwarded-Proto", "https")
|
||||
req.Header.Set("X-Forwarded-Host", "api.118im.com")
|
||||
req.Header.Set("X-Tinode-APIKey", makeTestAPIKey(t))
|
||||
req.Header.Set("X-Tenant-Code", "acme")
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
serveIMTenantResolve(rec, req)
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("status: got %d, want %d; body=%s", rec.Code, http.StatusOK, rec.Body.String())
|
||||
}
|
||||
data := decodeIMHTTPResponse(t, rec).Data.(map[string]any)
|
||||
endpoints := data["endpoints"].(map[string]any)
|
||||
if endpoints["websocket"] != "wss://api.118im.com/v0/channels" ||
|
||||
endpoints["api"] != "https://api.118im.com/v1/im" ||
|
||||
endpoints["file"] != "https://api.118im.com/v0/file" {
|
||||
t.Fatalf("unexpected endpoints: %#v", endpoints)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServeIMTenantResolveSuccessFromBody(t *testing.T) {
|
||||
oldTenants := store.Tenants
|
||||
store.Tenants = testTenantStore{tenant: &types.Tenant{
|
||||
@@ -1204,6 +1241,7 @@ func TestServeIMTenantResolveHidesMissingTenant(t *testing.T) {
|
||||
func TestServeIMVerificationCodesCreatesChallenge(t *testing.T) {
|
||||
oldTenants := store.Tenants
|
||||
oldVerifications := store.Verifications
|
||||
oldSender := sendIMVerificationCode
|
||||
store.Tenants = testTenantStore{tenant: &types.Tenant{
|
||||
ID: 7,
|
||||
Code: "acme",
|
||||
@@ -1212,9 +1250,20 @@ func TestServeIMVerificationCodesCreatesChallenge(t *testing.T) {
|
||||
}}
|
||||
verifications := &testVerificationStore{}
|
||||
store.Verifications = verifications
|
||||
setRegistrationSMSConfigForTest(true)
|
||||
var deliveredCode string
|
||||
sendIMVerificationCode = func(_ types.TenantID, recipient, _, scene, code string) error {
|
||||
if recipient != "+8613800138000" || scene != "register" {
|
||||
t.Fatalf("unexpected delivery: recipient=%q scene=%q", recipient, scene)
|
||||
}
|
||||
deliveredCode = code
|
||||
return nil
|
||||
}
|
||||
defer func() {
|
||||
store.Tenants = oldTenants
|
||||
store.Verifications = oldVerifications
|
||||
sendIMVerificationCode = oldSender
|
||||
resetRegistrationSMSConfigForTest()
|
||||
}()
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/v1/im/auth/verification-codes",
|
||||
@@ -1237,6 +1286,11 @@ func TestServeIMVerificationCodesCreatesChallenge(t *testing.T) {
|
||||
if verifications.created.CodeHash == "" || strings.Contains(rec.Body.String(), verifications.created.CodeHash) {
|
||||
t.Fatalf("code hash missing or leaked: %#v body=%s", verifications.created, rec.Body.String())
|
||||
}
|
||||
if len(deliveredCode) != imVerificationCodeLength ||
|
||||
verifications.created.CodeHash != hashIMSecret(7, "register", verifications.created.ChallengeID,
|
||||
verifications.created.PhoneHash, deliveredCode) {
|
||||
t.Fatalf("stored challenge does not match delivered code %q", deliveredCode)
|
||||
}
|
||||
resp := decodeIMHTTPResponse(t, rec)
|
||||
data := resp.Data.(map[string]any)
|
||||
if data["challenge_id"] == "" || data["expires_in_seconds"].(float64) == 0 {
|
||||
@@ -1244,6 +1298,113 @@ func TestServeIMVerificationCodesCreatesChallenge(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestServeIMVerificationCodesSkipsDeliveryWhenRegistrationSMSDisabled(t *testing.T) {
|
||||
oldTenants := store.Tenants
|
||||
oldVerifications := store.Verifications
|
||||
oldSender := sendIMVerificationCode
|
||||
store.Tenants = testTenantStore{tenant: &types.Tenant{ID: 7, Code: "acme", State: types.TenantStateActive}}
|
||||
verifications := &testVerificationStore{}
|
||||
store.Verifications = verifications
|
||||
setRegistrationSMSConfigForTest(false)
|
||||
sendIMVerificationCode = func(_ types.TenantID, _, _, _, _ string) error {
|
||||
t.Fatal("SMS sender must not be called when registration SMS is disabled")
|
||||
return nil
|
||||
}
|
||||
defer func() {
|
||||
store.Tenants = oldTenants
|
||||
store.Verifications = oldVerifications
|
||||
sendIMVerificationCode = oldSender
|
||||
resetRegistrationSMSConfigForTest()
|
||||
}()
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/v1/im/auth/verification-codes",
|
||||
bytes.NewBufferString(`{"scene":"register","phone":"+8613800138000"}`))
|
||||
req.Header.Set("X-Tinode-APIKey", makeTestAPIKey(t))
|
||||
req.Header.Set("X-Tenant-Code", "acme")
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
serveIMVerificationCodes(rec, req)
|
||||
|
||||
if rec.Code != http.StatusOK || verifications.created == nil {
|
||||
t.Fatalf("status=%d body=%s created=%#v", rec.Code, rec.Body.String(), verifications.created)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServeIMPasswordResetCodeFindsPhoneWithoutTelCredential(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
oldTenants := store.Tenants
|
||||
oldVerifications := store.Verifications
|
||||
oldUsers := store.Users
|
||||
oldSender := sendIMVerificationCode
|
||||
store.Tenants = testTenantStore{tenant: &types.Tenant{ID: 7, Code: "acme", State: types.TenantStateActive}}
|
||||
verifications := &testVerificationStore{}
|
||||
store.Verifications = verifications
|
||||
users := mock_store.NewMockUsersPersistenceInterface(ctrl)
|
||||
users.EXPECT().GetByPhone(types.TenantID(7), "+8613800138000").Return(types.Uid(42), nil)
|
||||
store.Users = users
|
||||
sendIMVerificationCode = func(_ types.TenantID, recipient, _, scene, code string) error {
|
||||
if recipient != "+8613800138000" || scene != "reset_password" || len(code) != imVerificationCodeLength {
|
||||
t.Fatalf("unexpected reset delivery: recipient=%q scene=%q code=%q", recipient, scene, code)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
defer func() {
|
||||
store.Tenants = oldTenants
|
||||
store.Verifications = oldVerifications
|
||||
store.Users = oldUsers
|
||||
sendIMVerificationCode = oldSender
|
||||
}()
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/v1/im/auth/verification-codes",
|
||||
bytes.NewBufferString(`{"scene":"reset_password","phone":"13800138000"}`))
|
||||
req.Header.Set("X-Tinode-APIKey", makeTestAPIKey(t))
|
||||
req.Header.Set("X-Tenant-Code", "acme")
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
serveIMVerificationCodes(rec, req)
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("status=%d body=%s", rec.Code, rec.Body.String())
|
||||
}
|
||||
if verifications.created == nil || verifications.created.TargetUser != types.Uid(42) {
|
||||
t.Fatalf("password reset challenge was not bound to phone account: %#v", verifications.created)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServeIMVerificationCodesExpiresChallengeOnDeliveryFailure(t *testing.T) {
|
||||
oldTenants := store.Tenants
|
||||
oldVerifications := store.Verifications
|
||||
oldSender := sendIMVerificationCode
|
||||
store.Tenants = testTenantStore{tenant: &types.Tenant{ID: 7, Code: "acme", State: types.TenantStateActive}}
|
||||
verifications := &testVerificationStore{}
|
||||
store.Verifications = verifications
|
||||
setRegistrationSMSConfigForTest(true)
|
||||
sendIMVerificationCode = func(_ types.TenantID, _, _, _, _ string) error { return types.ErrInternal }
|
||||
defer func() {
|
||||
store.Tenants = oldTenants
|
||||
store.Verifications = oldVerifications
|
||||
sendIMVerificationCode = oldSender
|
||||
resetRegistrationSMSConfigForTest()
|
||||
}()
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/v1/im/auth/verification-codes",
|
||||
bytes.NewBufferString(`{"scene":"register","phone":"+8613800138000"}`))
|
||||
req.Header.Set("X-Tinode-APIKey", makeTestAPIKey(t))
|
||||
req.Header.Set("X-Tenant-Code", "acme")
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
serveIMVerificationCodes(rec, req)
|
||||
|
||||
if rec.Code != http.StatusBadGateway || !verifications.failed {
|
||||
t.Fatalf("status=%d failed=%v body=%s", rec.Code, verifications.failed, rec.Body.String())
|
||||
}
|
||||
if resp := decodeIMHTTPResponse(t, rec); resp.Reason != imReasonSMSDeliveryFailed {
|
||||
t.Fatalf("reason=%q", resp.Reason)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServeIMVerificationCodesRequiresTenantCode(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodPost, "/v1/im/auth/verification-codes",
|
||||
bytes.NewBufferString(`{"scene":"register","phone":"+8613800138000"}`))
|
||||
|
||||
Reference in New Issue
Block a user