forked from baron/baron-sso
feat(user): support fixed UUID registration and enhance bulk import results
- Added support for fixed UUIDs during bulk registration (Search-first + ExternalID mapping) - Implemented idempotency and visibility restoration for soft-deleted users - Enhanced bulk upload UI to show 'New/Updated/Unchanged' status and modified fields - Added logic to reclaim identifiers (login_id) from colliding records - Added frontend E2E and backend unit tests for UUID integrity and conflict handling - Fixed i18n, formatting, and mock tests to satisfy code-check - Applied 'go fix' for 'omitzero' tags and general Go standards
This commit is contained in:
@@ -61,12 +61,12 @@ func newKratosWhoamiTestServer(t *testing.T, identityID string) *httptest.Server
|
||||
http.Error(w, "missing session", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
_ = json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{
|
||||
"id": "session-123",
|
||||
"authenticated_at": "2026-05-21T00:00:00Z",
|
||||
"identity": map[string]interface{}{
|
||||
"identity": map[string]any{
|
||||
"id": identityID,
|
||||
"traits": map[string]interface{}{
|
||||
"traits": map[string]any{
|
||||
"email": "user@example.com",
|
||||
},
|
||||
},
|
||||
@@ -113,7 +113,7 @@ func TestEnchantedLinkFlow_Email_Success(t *testing.T) {
|
||||
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
var initResp map[string]interface{}
|
||||
var initResp map[string]any
|
||||
json.NewDecoder(resp.Body).Decode(&initResp)
|
||||
pendingRef := initResp["pendingRef"].(string)
|
||||
assert.NotEmpty(t, pendingRef)
|
||||
@@ -129,7 +129,7 @@ func TestEnchantedLinkFlow_Email_Success(t *testing.T) {
|
||||
assert.NotEmpty(t, token)
|
||||
|
||||
// 2. Verify Magic Link
|
||||
verifyBody, _ := json.Marshal(map[string]interface{}{
|
||||
verifyBody, _ := json.Marshal(map[string]any{
|
||||
"token": token,
|
||||
"verifyOnly": true,
|
||||
})
|
||||
@@ -145,7 +145,7 @@ func TestEnchantedLinkFlow_Email_Success(t *testing.T) {
|
||||
resp, _ = app.Test(req, -1)
|
||||
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
var pollResp map[string]interface{}
|
||||
var pollResp map[string]any
|
||||
json.NewDecoder(resp.Body).Decode(&pollResp)
|
||||
assert.Equal(t, "ok", pollResp["status"])
|
||||
assert.Equal(t, "valid-jwt", pollResp["sessionJwt"])
|
||||
@@ -177,7 +177,7 @@ func TestEnchantedLinkFlow_Sms_Success(t *testing.T) {
|
||||
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
var initResp map[string]interface{}
|
||||
var initResp map[string]any
|
||||
json.NewDecoder(resp.Body).Decode(&initResp)
|
||||
assert.NotEmpty(t, initResp["userCode"])
|
||||
}
|
||||
@@ -193,7 +193,7 @@ func TestVerifyMagicLink_VerifyOnlyWithoutSharedBrowserSessionApprovesOnly(t *te
|
||||
app := fiber.New()
|
||||
app.Post("/api/v1/auth/magic-link/verify", h.VerifyMagicLink)
|
||||
|
||||
body, _ := json.Marshal(map[string]interface{}{
|
||||
body, _ := json.Marshal(map[string]any{
|
||||
"token": "token-123",
|
||||
"verifyOnly": true,
|
||||
})
|
||||
@@ -204,7 +204,7 @@ func TestVerifyMagicLink_VerifyOnlyWithoutSharedBrowserSessionApprovesOnly(t *te
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
assert.Empty(t, resp.Cookies())
|
||||
|
||||
var got map[string]interface{}
|
||||
var got map[string]any
|
||||
_ = json.NewDecoder(resp.Body).Decode(&got)
|
||||
assert.Equal(t, "approved", got["status"])
|
||||
assert.Nil(t, got["sessionJwt"])
|
||||
@@ -225,7 +225,7 @@ func TestVerifyMagicLink_VerifyOnlySharedBrowserSameSubjectApprovesOnly(t *testi
|
||||
app := fiber.New()
|
||||
app.Post("/api/v1/auth/magic-link/verify", h.VerifyMagicLink)
|
||||
|
||||
body, _ := json.Marshal(map[string]interface{}{
|
||||
body, _ := json.Marshal(map[string]any{
|
||||
"token": "token-123",
|
||||
"verifyOnly": true,
|
||||
})
|
||||
@@ -237,7 +237,7 @@ func TestVerifyMagicLink_VerifyOnlySharedBrowserSameSubjectApprovesOnly(t *testi
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
assert.Empty(t, resp.Cookies())
|
||||
|
||||
var got map[string]interface{}
|
||||
var got map[string]any
|
||||
_ = json.NewDecoder(resp.Body).Decode(&got)
|
||||
assert.Equal(t, "approved", got["status"])
|
||||
assert.Nil(t, got["sessionJwt"])
|
||||
@@ -258,7 +258,7 @@ func TestVerifyMagicLink_VerifyOnlySharedBrowserDifferentSubjectApprovesOnly(t *
|
||||
app := fiber.New()
|
||||
app.Post("/api/v1/auth/magic-link/verify", h.VerifyMagicLink)
|
||||
|
||||
body, _ := json.Marshal(map[string]interface{}{
|
||||
body, _ := json.Marshal(map[string]any{
|
||||
"token": "token-123",
|
||||
"verifyOnly": true,
|
||||
})
|
||||
@@ -270,7 +270,7 @@ func TestVerifyMagicLink_VerifyOnlySharedBrowserDifferentSubjectApprovesOnly(t *
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
assert.Empty(t, resp.Cookies())
|
||||
|
||||
var got map[string]interface{}
|
||||
var got map[string]any
|
||||
_ = json.NewDecoder(resp.Body).Decode(&got)
|
||||
assert.Equal(t, "approved", got["status"])
|
||||
assert.Nil(t, got["sessionJwt"])
|
||||
@@ -312,7 +312,7 @@ func TestVerifyLoginCode_VerifyOnlySharedBrowserDifferentSubjectApprovesOnly(t *
|
||||
app := fiber.New()
|
||||
app.Post("/api/v1/auth/login/code/verify", h.VerifyLoginCode)
|
||||
|
||||
body, _ := json.Marshal(map[string]interface{}{
|
||||
body, _ := json.Marshal(map[string]any{
|
||||
"loginId": "user@example.com",
|
||||
"code": "569765",
|
||||
"pendingRef": "pending-123",
|
||||
@@ -326,7 +326,7 @@ func TestVerifyLoginCode_VerifyOnlySharedBrowserDifferentSubjectApprovesOnly(t *
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
assert.Empty(t, resp.Cookies())
|
||||
|
||||
var got map[string]interface{}
|
||||
var got map[string]any
|
||||
_ = json.NewDecoder(resp.Body).Decode(&got)
|
||||
assert.Equal(t, "approved", got["status"])
|
||||
assert.Nil(t, got["sessionJwt"])
|
||||
@@ -349,7 +349,7 @@ func TestVerifyLoginCode_MapsSmsPhoneBeforeFlowLookup(t *testing.T) {
|
||||
app := fiber.New()
|
||||
app.Post("/api/v1/auth/login/code/verify", h.VerifyLoginCode)
|
||||
|
||||
body, _ := json.Marshal(map[string]interface{}{
|
||||
body, _ := json.Marshal(map[string]any{
|
||||
"loginId": "01041585840",
|
||||
"code": "569765",
|
||||
"pendingRef": "pending-123",
|
||||
@@ -360,7 +360,7 @@ func TestVerifyLoginCode_MapsSmsPhoneBeforeFlowLookup(t *testing.T) {
|
||||
resp, _ := app.Test(req, -1)
|
||||
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
var got map[string]interface{}
|
||||
var got map[string]any
|
||||
_ = json.NewDecoder(resp.Body).Decode(&got)
|
||||
assert.Equal(t, "approved", got["status"])
|
||||
assert.Equal(t, "pending-123", got["pendingRef"])
|
||||
@@ -383,7 +383,7 @@ func TestPollEnchantedLink_ExpiredToken_ReturnsCode(t *testing.T) {
|
||||
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
var got map[string]interface{}
|
||||
var got map[string]any
|
||||
json.NewDecoder(resp.Body).Decode(&got)
|
||||
assert.Equal(t, "expired_token", got["error"])
|
||||
assert.Equal(t, "expired_token", got["code"])
|
||||
@@ -415,7 +415,7 @@ func TestPollEnchantedLink_SharedBrowserSameSubjectIssuesSession(t *testing.T) {
|
||||
resp, _ := app.Test(req, -1)
|
||||
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
var got map[string]interface{}
|
||||
var got map[string]any
|
||||
_ = json.NewDecoder(resp.Body).Decode(&got)
|
||||
assert.Equal(t, "ok", got["status"])
|
||||
assert.Equal(t, "valid-jwt", got["sessionJwt"])
|
||||
@@ -447,7 +447,7 @@ func TestPollEnchantedLink_SharedBrowserDifferentSubjectConflicts(t *testing.T)
|
||||
resp, _ := app.Test(req, -1)
|
||||
|
||||
assert.Equal(t, http.StatusConflict, resp.StatusCode)
|
||||
var got map[string]interface{}
|
||||
var got map[string]any
|
||||
_ = json.NewDecoder(resp.Body).Decode(&got)
|
||||
assert.Equal(t, "session_subject_conflict", got["code"])
|
||||
assert.NotContains(t, redis.data[prefixSession+"pending-123"], "valid-jwt")
|
||||
@@ -481,7 +481,7 @@ func TestHeadlessLinkInit_HeadlessLoginClientSuccess(t *testing.T) {
|
||||
Client: domain.HydraClient{
|
||||
ClientID: "headless-login-client",
|
||||
TokenEndpointAuthMethod: "none",
|
||||
Metadata: map[string]interface{}{
|
||||
Metadata: map[string]any{
|
||||
"status": "active",
|
||||
"headless_login_enabled": true,
|
||||
"headless_token_endpoint_auth_method": "private_key_jwt",
|
||||
@@ -519,7 +519,7 @@ func TestHeadlessLinkInit_HeadlessLoginClientSuccess(t *testing.T) {
|
||||
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
var got map[string]interface{}
|
||||
var got map[string]any
|
||||
_ = json.NewDecoder(resp.Body).Decode(&got)
|
||||
assert.NotEmpty(t, got["pendingRef"])
|
||||
_, hasUserCode := got["userCode"]
|
||||
@@ -551,7 +551,7 @@ func TestHeadlessLinkPoll_AfterApprovalReturnsRedirect(t *testing.T) {
|
||||
ClientID: "headless-login-client",
|
||||
ClientName: "local-demo-rp",
|
||||
TokenEndpointAuthMethod: "none",
|
||||
Metadata: map[string]interface{}{
|
||||
Metadata: map[string]any{
|
||||
"status": "active",
|
||||
"headless_login_enabled": true,
|
||||
"headless_token_endpoint_auth_method": "private_key_jwt",
|
||||
@@ -604,7 +604,7 @@ func TestHeadlessLinkPoll_AfterApprovalReturnsRedirect(t *testing.T) {
|
||||
resp, _ := app.Test(req, -1)
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
var initResp map[string]interface{}
|
||||
var initResp map[string]any
|
||||
_ = json.NewDecoder(resp.Body).Decode(&initResp)
|
||||
pendingRef := initResp["pendingRef"].(string)
|
||||
assert.NotEmpty(t, pendingRef)
|
||||
@@ -618,7 +618,7 @@ func TestHeadlessLinkPoll_AfterApprovalReturnsRedirect(t *testing.T) {
|
||||
}
|
||||
assert.NotEmpty(t, token)
|
||||
|
||||
verifyBody, _ := json.Marshal(map[string]interface{}{
|
||||
verifyBody, _ := json.Marshal(map[string]any{
|
||||
"token": token,
|
||||
"verifyOnly": true,
|
||||
})
|
||||
@@ -637,7 +637,7 @@ func TestHeadlessLinkPoll_AfterApprovalReturnsRedirect(t *testing.T) {
|
||||
resp, _ = app.Test(req, -1)
|
||||
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
var pollResp map[string]interface{}
|
||||
var pollResp map[string]any
|
||||
_ = json.NewDecoder(resp.Body).Decode(&pollResp)
|
||||
assert.Equal(t, "http://rp/cb", pollResp["redirectTo"])
|
||||
assert.Equal(t, "ok", pollResp["status"])
|
||||
@@ -677,7 +677,7 @@ func TestHeadlessLinkPoll_ApproverSubjectConflictBlocksMixedRP(t *testing.T) {
|
||||
ClientID: "headless-login-client",
|
||||
ClientName: "local-demo-rp",
|
||||
TokenEndpointAuthMethod: "none",
|
||||
Metadata: map[string]interface{}{
|
||||
Metadata: map[string]any{
|
||||
"status": "active",
|
||||
"headless_login_enabled": true,
|
||||
"headless_token_endpoint_auth_method": "private_key_jwt",
|
||||
@@ -734,7 +734,7 @@ func TestHeadlessLinkPoll_ApproverSubjectConflictBlocksMixedRP(t *testing.T) {
|
||||
resp, _ := app.Test(req, -1)
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
var initResp map[string]interface{}
|
||||
var initResp map[string]any
|
||||
_ = json.NewDecoder(resp.Body).Decode(&initResp)
|
||||
pendingRef := initResp["pendingRef"].(string)
|
||||
assert.NotEmpty(t, pendingRef)
|
||||
@@ -751,7 +751,7 @@ func TestHeadlessLinkPoll_ApproverSubjectConflictBlocksMixedRP(t *testing.T) {
|
||||
kratosPublic := newKratosWhoamiTestServer(t, "kratos-userfront-a")
|
||||
t.Setenv("KRATOS_PUBLIC_URL", kratosPublic.URL)
|
||||
|
||||
verifyBody, _ := json.Marshal(map[string]interface{}{
|
||||
verifyBody, _ := json.Marshal(map[string]any{
|
||||
"token": token,
|
||||
"verifyOnly": true,
|
||||
})
|
||||
@@ -773,7 +773,7 @@ func TestHeadlessLinkPoll_ApproverSubjectConflictBlocksMixedRP(t *testing.T) {
|
||||
assert.Equal(t, http.StatusConflict, resp.StatusCode)
|
||||
assert.False(t, acceptCalled)
|
||||
assert.Empty(t, resp.Cookies())
|
||||
var got map[string]interface{}
|
||||
var got map[string]any
|
||||
_ = json.NewDecoder(resp.Body).Decode(&got)
|
||||
assert.Equal(t, "oidc_subject_conflict", got["code"])
|
||||
assert.Equal(t, "redirect_to_userfront_login", got["recommendedAction"])
|
||||
@@ -802,7 +802,7 @@ func TestHeadlessLinkPoll_RequestCookieSubjectConflictBlocksMixedRP(t *testing.T
|
||||
Client: domain.HydraClient{
|
||||
ClientID: "headless-login-client",
|
||||
TokenEndpointAuthMethod: "none",
|
||||
Metadata: map[string]interface{}{
|
||||
Metadata: map[string]any{
|
||||
"status": "active",
|
||||
"headless_login_enabled": true,
|
||||
"headless_token_endpoint_auth_method": "private_key_jwt",
|
||||
@@ -857,7 +857,7 @@ func TestHeadlessLinkPoll_RequestCookieSubjectConflictBlocksMixedRP(t *testing.T
|
||||
resp, _ := app.Test(req, -1)
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
var initResp map[string]interface{}
|
||||
var initResp map[string]any
|
||||
_ = json.NewDecoder(resp.Body).Decode(&initResp)
|
||||
pendingRef := initResp["pendingRef"].(string)
|
||||
assert.NotEmpty(t, pendingRef)
|
||||
@@ -871,7 +871,7 @@ func TestHeadlessLinkPoll_RequestCookieSubjectConflictBlocksMixedRP(t *testing.T
|
||||
}
|
||||
assert.NotEmpty(t, token)
|
||||
|
||||
verifyBody, _ := json.Marshal(map[string]interface{}{
|
||||
verifyBody, _ := json.Marshal(map[string]any{
|
||||
"token": token,
|
||||
"verifyOnly": true,
|
||||
})
|
||||
@@ -896,7 +896,7 @@ func TestHeadlessLinkPoll_RequestCookieSubjectConflictBlocksMixedRP(t *testing.T
|
||||
assert.Equal(t, http.StatusConflict, resp.StatusCode)
|
||||
assert.False(t, acceptCalled)
|
||||
assert.Empty(t, resp.Cookies())
|
||||
var got map[string]interface{}
|
||||
var got map[string]any
|
||||
_ = json.NewDecoder(resp.Body).Decode(&got)
|
||||
assert.Equal(t, "oidc_subject_conflict", got["code"])
|
||||
assert.Equal(t, "kratos-userfront-a", got["currentSubject"])
|
||||
|
||||
Reference in New Issue
Block a user