feat: add admin auth identity repair binding
This commit is contained in:
@@ -25,6 +25,7 @@ func setupAdminRouter() (*gin.Engine, *stubAdminService) {
|
||||
router.GET("/api/v1/admin/users/auth-identity-migration-reports/summary", userHandler.GetAuthIdentityMigrationReportSummary)
|
||||
router.GET("/api/v1/admin/users/auth-identity-migration-reports", userHandler.ListAuthIdentityMigrationReports)
|
||||
router.GET("/api/v1/admin/users/:id", userHandler.GetByID)
|
||||
router.POST("/api/v1/admin/users/:id/auth-identities", userHandler.BindAuthIdentity)
|
||||
router.POST("/api/v1/admin/users", userHandler.Create)
|
||||
router.PUT("/api/v1/admin/users/:id", userHandler.Update)
|
||||
router.DELETE("/api/v1/admin/users/:id", userHandler.Delete)
|
||||
@@ -87,8 +88,26 @@ func TestUserHandlerEndpoints(t *testing.T) {
|
||||
router.ServeHTTP(rec, req)
|
||||
require.Equal(t, http.StatusOK, rec.Code)
|
||||
|
||||
bindBody := map[string]any{
|
||||
"provider_type": "wechat",
|
||||
"provider_key": "wechat-main",
|
||||
"provider_subject": "union-123",
|
||||
"metadata": map[string]any{"source": "admin-repair"},
|
||||
"channel": map[string]any{
|
||||
"channel": "open",
|
||||
"channel_app_id": "wx-open",
|
||||
"channel_subject": "openid-123",
|
||||
},
|
||||
}
|
||||
body, _ := json.Marshal(bindBody)
|
||||
rec = httptest.NewRecorder()
|
||||
req = httptest.NewRequest(http.MethodPost, "/api/v1/admin/users/1/auth-identities", bytes.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
router.ServeHTTP(rec, req)
|
||||
require.Equal(t, http.StatusOK, rec.Code)
|
||||
|
||||
createBody := map[string]any{"email": "new@example.com", "password": "pass123", "balance": 1, "concurrency": 2}
|
||||
body, _ := json.Marshal(createBody)
|
||||
body, _ = json.Marshal(createBody)
|
||||
rec = httptest.NewRecorder()
|
||||
req = httptest.NewRequest(http.MethodPost, "/api/v1/admin/users", bytes.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
@@ -125,6 +144,33 @@ func TestUserHandlerEndpoints(t *testing.T) {
|
||||
require.Equal(t, http.StatusOK, rec.Code)
|
||||
}
|
||||
|
||||
func TestUserHandlerBindAuthIdentityMapsRequest(t *testing.T) {
|
||||
router, adminSvc := setupAdminRouter()
|
||||
|
||||
body, err := json.Marshal(map[string]any{
|
||||
"provider_type": "oidc",
|
||||
"provider_key": "https://issuer.example",
|
||||
"provider_subject": "subject-123",
|
||||
"issuer": "https://issuer.example",
|
||||
"metadata": map[string]any{"report_id": 12},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
rec := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/admin/users/9/auth-identities", bytes.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
router.ServeHTTP(rec, req)
|
||||
|
||||
require.Equal(t, http.StatusOK, rec.Code)
|
||||
require.Equal(t, int64(9), adminSvc.boundAuthIdentityFor)
|
||||
require.NotNil(t, adminSvc.boundAuthIdentity)
|
||||
require.Equal(t, "oidc", adminSvc.boundAuthIdentity.ProviderType)
|
||||
require.Equal(t, "https://issuer.example", adminSvc.boundAuthIdentity.ProviderKey)
|
||||
require.Equal(t, "subject-123", adminSvc.boundAuthIdentity.ProviderSubject)
|
||||
require.Nil(t, adminSvc.boundAuthIdentity.Channel)
|
||||
require.Equal(t, float64(12), adminSvc.boundAuthIdentity.Metadata["report_id"])
|
||||
}
|
||||
|
||||
func TestGroupHandlerEndpoints(t *testing.T) {
|
||||
router, _ := setupAdminRouter()
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@ type stubAdminService struct {
|
||||
proxyCounts []service.ProxyWithAccountCount
|
||||
redeems []service.RedeemCode
|
||||
migrationReports []service.AuthIdentityMigrationReport
|
||||
boundAuthIdentity *service.AdminBindAuthIdentityInput
|
||||
boundAuthIdentityFor int64
|
||||
createdAccounts []*service.CreateAccountInput
|
||||
createdProxies []*service.CreateProxyInput
|
||||
updatedProxyIDs []int64
|
||||
@@ -201,6 +203,52 @@ func (s *stubAdminService) GetAuthIdentityMigrationReportSummary(ctx context.Con
|
||||
return summary, nil
|
||||
}
|
||||
|
||||
func (s *stubAdminService) BindUserAuthIdentity(ctx context.Context, userID int64, input service.AdminBindAuthIdentityInput) (*service.AdminBoundAuthIdentity, error) {
|
||||
s.boundAuthIdentityFor = userID
|
||||
copied := input
|
||||
if input.Metadata != nil {
|
||||
copied.Metadata = map[string]any{}
|
||||
for key, value := range input.Metadata {
|
||||
copied.Metadata[key] = value
|
||||
}
|
||||
}
|
||||
if input.Channel != nil {
|
||||
channel := *input.Channel
|
||||
if input.Channel.Metadata != nil {
|
||||
channel.Metadata = map[string]any{}
|
||||
for key, value := range input.Channel.Metadata {
|
||||
channel.Metadata[key] = value
|
||||
}
|
||||
}
|
||||
copied.Channel = &channel
|
||||
}
|
||||
s.boundAuthIdentity = &copied
|
||||
|
||||
now := time.Now().UTC()
|
||||
result := &service.AdminBoundAuthIdentity{
|
||||
UserID: userID,
|
||||
ProviderType: input.ProviderType,
|
||||
ProviderKey: input.ProviderKey,
|
||||
ProviderSubject: input.ProviderSubject,
|
||||
VerifiedAt: &now,
|
||||
Issuer: input.Issuer,
|
||||
Metadata: input.Metadata,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
if input.Channel != nil {
|
||||
result.Channel = &service.AdminBoundAuthIdentityChannel{
|
||||
Channel: input.Channel.Channel,
|
||||
ChannelAppID: input.Channel.ChannelAppID,
|
||||
ChannelSubject: input.Channel.ChannelSubject,
|
||||
Metadata: input.Channel.Metadata,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *stubAdminService) ListGroups(ctx context.Context, page, pageSize int, platform, status, search string, isExclusive *bool, sortBy, sortOrder string) ([]service.Group, int64, error) {
|
||||
return s.groups, int64(len(s.groups)), nil
|
||||
}
|
||||
|
||||
@@ -66,6 +66,22 @@ type UpdateBalanceRequest struct {
|
||||
Notes string `json:"notes"`
|
||||
}
|
||||
|
||||
type BindUserAuthIdentityRequest struct {
|
||||
ProviderType string `json:"provider_type"`
|
||||
ProviderKey string `json:"provider_key"`
|
||||
ProviderSubject string `json:"provider_subject"`
|
||||
Issuer *string `json:"issuer"`
|
||||
Metadata map[string]any `json:"metadata"`
|
||||
Channel *BindUserAuthIdentityChannelRequest `json:"channel"`
|
||||
}
|
||||
|
||||
type BindUserAuthIdentityChannelRequest struct {
|
||||
Channel string `json:"channel"`
|
||||
ChannelAppID string `json:"channel_app_id"`
|
||||
ChannelSubject string `json:"channel_subject"`
|
||||
Metadata map[string]any `json:"metadata"`
|
||||
}
|
||||
|
||||
// List handles listing all users with pagination
|
||||
// GET /api/v1/admin/users
|
||||
// Query params:
|
||||
@@ -197,6 +213,45 @@ func (h *UserHandler) ListAuthIdentityMigrationReports(c *gin.Context) {
|
||||
response.Paginated(c, reports, total, page, pageSize)
|
||||
}
|
||||
|
||||
// BindAuthIdentity manually binds a canonical auth identity to a user.
|
||||
// POST /api/v1/admin/users/:id/auth-identities
|
||||
func (h *UserHandler) BindAuthIdentity(c *gin.Context) {
|
||||
userID, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
response.BadRequest(c, "Invalid user ID")
|
||||
return
|
||||
}
|
||||
|
||||
var req BindUserAuthIdentityRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "Invalid request: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
input := service.AdminBindAuthIdentityInput{
|
||||
ProviderType: req.ProviderType,
|
||||
ProviderKey: req.ProviderKey,
|
||||
ProviderSubject: req.ProviderSubject,
|
||||
Issuer: req.Issuer,
|
||||
Metadata: req.Metadata,
|
||||
}
|
||||
if req.Channel != nil {
|
||||
input.Channel = &service.AdminBindAuthIdentityChannelInput{
|
||||
Channel: req.Channel.Channel,
|
||||
ChannelAppID: req.Channel.ChannelAppID,
|
||||
ChannelSubject: req.Channel.ChannelSubject,
|
||||
Metadata: req.Channel.Metadata,
|
||||
}
|
||||
}
|
||||
|
||||
result, err := h.adminService.BindUserAuthIdentity(c.Request.Context(), userID, input)
|
||||
if err != nil {
|
||||
response.ErrorFrom(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, result)
|
||||
}
|
||||
|
||||
// Create handles creating a new user
|
||||
// POST /api/v1/admin/users
|
||||
func (h *UserHandler) Create(c *gin.Context) {
|
||||
|
||||
Reference in New Issue
Block a user