1
0
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:
2026-06-01 15:34:08 +09:00
parent 4a1e89e421
commit 31d107ff2e
85 changed files with 2104 additions and 1149 deletions

View File

@@ -32,6 +32,7 @@ import (
"github.com/gofiber/fiber/v2"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"gorm.io/gorm"
)
// --- Mocks ---
@@ -113,7 +114,7 @@ func (m *MockKratosAdminService) ListIdentities(ctx context.Context) ([]service.
return nil, nil
}
func (m *MockKratosAdminService) UpdateIdentity(ctx context.Context, identityID string, traits map[string]interface{}, state string) (*service.KratosIdentity, error) {
func (m *MockKratosAdminService) UpdateIdentity(ctx context.Context, identityID string, traits map[string]any, state string) (*service.KratosIdentity, error) {
return nil, nil
}
@@ -214,6 +215,8 @@ func (r *passwordLoginUserRepo) FindByCompanyCodes(ctx context.Context, codes []
func (r *passwordLoginUserRepo) Delete(ctx context.Context, id string) error { return nil }
func (r *passwordLoginUserRepo) DB() *gorm.DB { return nil }
func (r *passwordLoginUserRepo) UpdateUserLoginIDs(ctx context.Context, userID string, loginIDs []domain.UserLoginID) error {
return nil
}
@@ -474,7 +477,7 @@ func runHeadlessPasswordLoginWithAssertionRequest(
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",
@@ -579,7 +582,7 @@ func runHeadlessPasswordLoginWithAssertionAndLoggerRequest(
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",
@@ -667,7 +670,7 @@ func TestPasswordLogin_OIDC_Success(t *testing.T) {
}
json.NewEncoder(w).Encode(domain.HydraLoginRequest{
Challenge: challenge,
Client: domain.HydraClient{ClientID: "client-1", Metadata: map[string]interface{}{"status": "active"}},
Client: domain.HydraClient{ClientID: "client-1", Metadata: map[string]any{"status": "active"}},
})
case strings.Contains(r.URL.Path, "/oauth2/auth/requests/login/accept") && r.Method == http.MethodPut:
// AcceptLoginRequest
@@ -710,7 +713,7 @@ func TestPasswordLogin_OIDC_Success(t *testing.T) {
t.Fatalf("expected 200, got %d, body: %s", resp.StatusCode, string(bodyBytes))
}
var got map[string]interface{}
var got map[string]any
json.NewDecoder(resp.Body).Decode(&got)
if got["redirectTo"] != "http://rp/cb" {
t.Errorf("expected redirectTo http://rp/cb, got %v", got["redirectTo"])
@@ -738,7 +741,7 @@ func TestPasswordLogin_OIDC_AuditIncludesClientMetadata(t *testing.T) {
Client: domain.HydraClient{
ClientID: "devfront",
ClientName: "DevFront",
Metadata: map[string]interface{}{"status": "active"},
Metadata: map[string]any{"status": "active"},
},
})
case strings.Contains(r.URL.Path, "/oauth2/auth/requests/login/accept") && r.Method == http.MethodPut:
@@ -902,7 +905,7 @@ func TestHeadlessPasswordLogin_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",
@@ -958,7 +961,7 @@ func TestHeadlessPasswordLogin_HeadlessLoginClientSuccess(t *testing.T) {
t.Fatalf("expected 200, got %d, body: %s", resp.StatusCode, string(bodyBytes))
}
var got map[string]interface{}
var got map[string]any
if err := json.NewDecoder(resp.Body).Decode(&got); err != nil {
t.Fatalf("failed to decode response: %v", err)
}
@@ -1005,7 +1008,7 @@ func TestHeadlessPasswordLogin_OIDCSubjectConflictBlocksMixedRP(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",
@@ -1051,7 +1054,7 @@ func TestHeadlessPasswordLogin_OIDCSubjectConflictBlocksMixedRP(t *testing.T) {
require.Equal(t, http.StatusConflict, resp.StatusCode)
require.False(t, acceptCalled)
require.Empty(t, resp.Cookies())
var got map[string]interface{}
var got map[string]any
require.NoError(t, json.NewDecoder(resp.Body).Decode(&got))
require.Equal(t, "oidc_subject_conflict", got["code"])
require.Equal(t, "redirect_to_userfront_login", got["recommendedAction"])
@@ -1090,7 +1093,7 @@ func TestHeadlessPasswordLogin_OIDCSubjectSameAllowsMixedRP(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",
@@ -1134,7 +1137,7 @@ func TestHeadlessPasswordLogin_OIDCSubjectSameAllowsMixedRP(t *testing.T) {
require.Equal(t, http.StatusOK, resp.StatusCode)
require.Empty(t, resp.Cookies())
var got map[string]interface{}
var got map[string]any
require.NoError(t, json.NewDecoder(resp.Body).Decode(&got))
require.Equal(t, "http://rp/cb", got["redirectTo"])
require.Nil(t, got["sessionJwt"])
@@ -1161,7 +1164,7 @@ func TestHeadlessPasswordLogin_AuditIncludesClientMetadata(t *testing.T) {
ClientID: "headless-login-client",
ClientName: "Headless Login Portal",
TokenEndpointAuthMethod: "none",
Metadata: map[string]interface{}{
Metadata: map[string]any{
"status": "active",
"headless_login_enabled": true,
"headless_token_endpoint_auth_method": "private_key_jwt",
@@ -1294,7 +1297,7 @@ func TestHeadlessPasswordLogin_IgnoresInlineHeadlessJWKSWhenJWKSURIIsConfigured(
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",
@@ -1395,7 +1398,7 @@ func TestHeadlessPasswordLogin_RefreshesJWKSWhenSignatureFailsForCachedKid(t *te
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",
@@ -1494,7 +1497,7 @@ func TestHeadlessPasswordLogin_MissingClientAssertionRejected(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",
@@ -1573,7 +1576,7 @@ func TestHeadlessPasswordLogin_InvalidClientAssertionRejected(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",
@@ -1995,7 +1998,7 @@ func TestHeadlessPasswordLogin_HeadlessDisabledRejected(t *testing.T) {
Client: domain.HydraClient{
ClientID: "headless-login-client",
TokenEndpointAuthMethod: "none",
Metadata: map[string]interface{}{
Metadata: map[string]any{
"status": "active",
"headless_jwks_uri": "https://rp.example.com/.well-known/jwks.json",
"headless_token_endpoint_auth_method": "private_key_jwt",
@@ -2048,7 +2051,7 @@ func TestHeadlessPasswordLogin_ClientIDMismatchRejected(t *testing.T) {
Client: domain.HydraClient{
ClientID: "other-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",
@@ -2102,7 +2105,7 @@ func TestPasswordLogin_OIDC_InactiveClient(t *testing.T) {
hydraHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if strings.Contains(r.URL.Path, "/oauth2/auth/requests/login") && r.Method == http.MethodGet {
json.NewEncoder(w).Encode(domain.HydraLoginRequest{
Client: domain.HydraClient{ClientID: "client-inactive", Metadata: map[string]interface{}{"status": "inactive"}},
Client: domain.HydraClient{ClientID: "client-inactive", Metadata: map[string]any{"status": "inactive"}},
})
return
}
@@ -2220,7 +2223,7 @@ func TestPasswordLogin_SharedBrowserSameSubjectAllowed(t *testing.T) {
defer resp.Body.Close()
require.Equal(t, http.StatusOK, resp.StatusCode)
var got map[string]interface{}
var got map[string]any
require.NoError(t, json.NewDecoder(resp.Body).Decode(&got))
require.Equal(t, "valid-jwt", got["sessionJwt"])
mockIdp.AssertExpectations(t)
@@ -2259,7 +2262,7 @@ func TestPasswordLogin_SharedBrowserDifferentSubjectConflicts(t *testing.T) {
defer resp.Body.Close()
require.Equal(t, http.StatusConflict, resp.StatusCode)
var got map[string]interface{}
var got map[string]any
require.NoError(t, json.NewDecoder(resp.Body).Decode(&got))
require.Equal(t, "session_subject_conflict", got["code"])
require.Empty(t, resp.Cookies())