forked from baron/baron-sso
merge: integrate origin dev into dev
Includes Worksmobile SSOT sync comparison updates, UUID import conflict resolution, and Playwright route mock stabilization.
This commit is contained in:
@@ -57,8 +57,10 @@ func (o *OryProvider) CreateUser(user *domain.BrokerUser, password string) (stri
|
||||
if user.Email == "" || password == "" {
|
||||
return "", fmt.Errorf("ory provider: email and password are required")
|
||||
}
|
||||
if strings.TrimSpace(user.ID) != "" {
|
||||
return "", fmt.Errorf("ory provider: requested identity id import is disabled; use backup/restore")
|
||||
}
|
||||
|
||||
// 중복 확인
|
||||
existingID, err := o.findIdentityID(user.Email)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("ory provider: search identity failed: %w", err)
|
||||
@@ -102,7 +104,7 @@ func (o *OryProvider) CreateUser(user *domain.BrokerUser, password string) (stri
|
||||
}
|
||||
}
|
||||
|
||||
traits := map[string]interface{}{
|
||||
traits := map[string]any{
|
||||
"email": user.Email,
|
||||
"name": user.Name,
|
||||
}
|
||||
@@ -123,21 +125,18 @@ func (o *OryProvider) CreateUser(user *domain.BrokerUser, password string) (stri
|
||||
traits[k] = v
|
||||
}
|
||||
|
||||
payload := map[string]interface{}{
|
||||
payload := map[string]any{
|
||||
"schema_id": "default",
|
||||
"traits": traits,
|
||||
"credentials": map[string]interface{}{
|
||||
"password": map[string]interface{}{
|
||||
"credentials": map[string]any{
|
||||
"password": map[string]any{
|
||||
"config": map[string]string{
|
||||
"password": password,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
if requestedID := strings.TrimSpace(user.ID); requestedID != "" {
|
||||
payload["id"] = requestedID
|
||||
}
|
||||
verifiable := []map[string]interface{}{
|
||||
verifiable := []map[string]any{
|
||||
{
|
||||
"value": user.Email,
|
||||
"verified": true,
|
||||
@@ -145,14 +144,14 @@ func (o *OryProvider) CreateUser(user *domain.BrokerUser, password string) (stri
|
||||
},
|
||||
}
|
||||
if user.PhoneNumber != "" {
|
||||
verifiable = append(verifiable, map[string]interface{}{
|
||||
verifiable = append(verifiable, map[string]any{
|
||||
"value": user.PhoneNumber,
|
||||
"verified": true,
|
||||
"via": "sms",
|
||||
})
|
||||
}
|
||||
payload["verifiable_addresses"] = verifiable
|
||||
payload["recovery_addresses"] = []map[string]interface{}{
|
||||
payload["recovery_addresses"] = []map[string]any{
|
||||
{
|
||||
"value": user.Email,
|
||||
"via": "email",
|
||||
@@ -182,10 +181,6 @@ func (o *OryProvider) CreateUser(user *domain.BrokerUser, password string) (stri
|
||||
if err := json.NewDecoder(resp.Body).Decode(&created); err != nil {
|
||||
return "", fmt.Errorf("ory provider: decode create identity response failed: %w", err)
|
||||
}
|
||||
if requestedID := strings.TrimSpace(user.ID); requestedID != "" && created.ID != requestedID {
|
||||
return "", fmt.Errorf("ory provider: requested identity id was not preserved requested=%s actual=%s", requestedID, created.ID)
|
||||
}
|
||||
|
||||
slog.Info("Ory identity created", "identity_id", created.ID, "email", user.Email)
|
||||
return created.ID, nil
|
||||
}
|
||||
@@ -460,12 +455,12 @@ func (o *OryProvider) ensureCodeLoginIdentifier(loginID string) error {
|
||||
existingIndex = idx
|
||||
}
|
||||
}
|
||||
ops := make([]map[string]interface{}, 0, 2)
|
||||
ops := make([]map[string]any, 0, 2)
|
||||
if !exists {
|
||||
ops = append(ops, map[string]interface{}{
|
||||
ops = append(ops, map[string]any{
|
||||
"op": "add",
|
||||
"path": "/verifiable_addresses/-",
|
||||
"value": map[string]interface{}{
|
||||
"value": map[string]any{
|
||||
"value": loginID,
|
||||
"via": via,
|
||||
"verified": true,
|
||||
@@ -475,14 +470,14 @@ func (o *OryProvider) ensureCodeLoginIdentifier(loginID string) error {
|
||||
} else {
|
||||
addr := identity.VerifiableAddresses[existingIndex]
|
||||
if !addr.Verified {
|
||||
ops = append(ops, map[string]interface{}{
|
||||
ops = append(ops, map[string]any{
|
||||
"op": "replace",
|
||||
"path": fmt.Sprintf("/verifiable_addresses/%d/verified", existingIndex),
|
||||
"value": true,
|
||||
})
|
||||
}
|
||||
if addr.Status != "" && addr.Status != "completed" {
|
||||
ops = append(ops, map[string]interface{}{
|
||||
ops = append(ops, map[string]any{
|
||||
"op": "replace",
|
||||
"path": fmt.Sprintf("/verifiable_addresses/%d/status", existingIndex),
|
||||
"value": "completed",
|
||||
@@ -526,7 +521,7 @@ func (o *OryProvider) ensureCodeLoginIdentifier(loginID string) error {
|
||||
})
|
||||
}
|
||||
|
||||
payload := map[string]interface{}{
|
||||
payload := map[string]any{
|
||||
"schema_id": fullIdentity.SchemaID,
|
||||
"traits": fullIdentity.Traits,
|
||||
"verifiable_addresses": addresses,
|
||||
@@ -567,12 +562,12 @@ type kratosRecoveryAddress struct {
|
||||
|
||||
type kratosIdentityFull struct {
|
||||
SchemaID string `json:"schema_id"`
|
||||
Traits map[string]interface{} `json:"traits"`
|
||||
Traits map[string]any `json:"traits"`
|
||||
VerifiableAddresses []kratosVerifiableAddress `json:"verifiable_addresses"`
|
||||
RecoveryAddresses []kratosRecoveryAddress `json:"recovery_addresses"`
|
||||
}
|
||||
|
||||
func (o *OryProvider) patchIdentity(identityID string, ops []map[string]interface{}) error {
|
||||
func (o *OryProvider) patchIdentity(identityID string, ops []map[string]any) error {
|
||||
body, _ := json.Marshal(ops)
|
||||
req, err := http.NewRequestWithContext(context.Background(), http.MethodPatch, fmt.Sprintf("%s/admin/identities/%s", o.KratosAdminURL, identityID), bytes.NewReader(body))
|
||||
if err != nil {
|
||||
@@ -756,12 +751,12 @@ func (o *OryProvider) UpdateUserPassword(loginID, newPassword string, r *http.Re
|
||||
return fmt.Errorf("ory provider: hash password failed: %w", err)
|
||||
}
|
||||
|
||||
payload := map[string]interface{}{
|
||||
payload := map[string]any{
|
||||
"schema_id": identity.SchemaID,
|
||||
"traits": identity.Traits,
|
||||
"state": identity.State,
|
||||
"credentials": map[string]interface{}{
|
||||
"password": map[string]interface{}{
|
||||
"credentials": map[string]any{
|
||||
"password": map[string]any{
|
||||
"config": map[string]string{
|
||||
"hashed_password": hashedPassword,
|
||||
},
|
||||
@@ -780,10 +775,6 @@ func (o *OryProvider) UpdateUserPassword(loginID, newPassword string, r *http.Re
|
||||
if identity.MetadataPublic != nil {
|
||||
payload["metadata_public"] = identity.MetadataPublic
|
||||
}
|
||||
if identity.ExternalID != "" {
|
||||
payload["external_id"] = identity.ExternalID
|
||||
}
|
||||
|
||||
body, _ := json.Marshal(payload)
|
||||
req, err := http.NewRequestWithContext(context.Background(), http.MethodPut, fmt.Sprintf("%s/admin/identities/%s", o.KratosAdminURL, identityID), bytes.NewReader(body))
|
||||
if err != nil {
|
||||
@@ -812,6 +803,18 @@ func getenv(key, fallback string) string {
|
||||
return fallback
|
||||
}
|
||||
|
||||
// findIdentityByID: Kratos Admin API에서 ID(UUID)로 직접 조회
|
||||
func (o *OryProvider) findIdentityByID(id string) (string, error) {
|
||||
identity, err := o.getIdentity(id)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if identity != nil {
|
||||
return identity.ID, nil
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// findIdentityID: Kratos Admin API에서 credentials_identifier로 검색 후 첫 번째 identity id 반환
|
||||
func (o *OryProvider) findIdentityID(loginID string) (string, error) {
|
||||
u, err := url.Parse(fmt.Sprintf("%s/admin/identities", o.KratosAdminURL))
|
||||
@@ -843,7 +846,8 @@ func (o *OryProvider) findIdentityID(loginID string) (string, error) {
|
||||
}
|
||||
|
||||
var identities []struct {
|
||||
ID string `json:"id"`
|
||||
ID string `json:"id"`
|
||||
Traits map[string]any `json:"traits"`
|
||||
}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&identities); err != nil {
|
||||
return "", fmt.Errorf("decode response failed: %w", err)
|
||||
@@ -851,7 +855,30 @@ func (o *OryProvider) findIdentityID(loginID string) (string, error) {
|
||||
if len(identities) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
return identities[0].ID, nil
|
||||
|
||||
// VERIFY: Double check traits to avoid Kratos ignoring the query param
|
||||
candidate := identities[0]
|
||||
if email, ok := candidate.Traits["email"].(string); ok && strings.EqualFold(email, loginID) {
|
||||
return candidate.ID, nil
|
||||
}
|
||||
if phone, ok := candidate.Traits["phone_number"].(string); ok && strings.EqualFold(phone, loginID) {
|
||||
return candidate.ID, nil
|
||||
}
|
||||
if lids, ok := candidate.Traits["custom_login_ids"].([]any); ok {
|
||||
for _, lid := range lids {
|
||||
if s, ok := lid.(string); ok && strings.EqualFold(s, loginID) {
|
||||
return candidate.ID, nil
|
||||
}
|
||||
}
|
||||
} else if lids, ok := candidate.Traits["custom_login_ids"].([]string); ok {
|
||||
for _, lid := range lids {
|
||||
if strings.EqualFold(lid, loginID) {
|
||||
return candidate.ID, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (o *OryProvider) getIdentity(identityID string) (*KratosIdentity, error) {
|
||||
|
||||
Reference in New Issue
Block a user