1
0
forked from baron/baron-sso

기본 발송 중간

This commit is contained in:
Lectom C Han
2026-01-29 09:28:48 +09:00
parent b88de7ec91
commit 742964cf71
10 changed files with 603 additions and 110 deletions

View File

@@ -232,22 +232,64 @@ func (o *OryProvider) InitiateLinkLogin(loginID, returnTo string) (*domain.LinkL
return nil, fmt.Errorf("ory provider: loginID is required")
}
init, err := o.submitLoginCodeInit(loginID, returnTo)
effectiveLoginID, err := o.resolveEffectiveLoginID(loginID)
if err != nil {
return nil, err
}
if err := o.ensureCodeLoginIdentifier(effectiveLoginID); err != nil {
return nil, err
}
init, err := o.submitLoginCodeInit(effectiveLoginID, returnTo)
if err == nil {
init.LoginID = effectiveLoginID
return init, nil
}
if shouldBootstrapCodeLogin(err) {
if ensureErr := o.ensureCodeLoginIdentifier(loginID); ensureErr == nil {
return o.submitLoginCodeInit(loginID, returnTo)
if ensureErr := o.ensureCodeLoginIdentifier(effectiveLoginID); ensureErr == nil {
init, initErr := o.submitLoginCodeInit(effectiveLoginID, returnTo)
if initErr == nil {
init.LoginID = effectiveLoginID
}
return init, initErr
} else {
slog.Warn("Ory code login bootstrap failed", "loginID", loginID, "error", ensureErr)
slog.Warn("Ory code login bootstrap failed", "loginID", effectiveLoginID, "error", ensureErr)
}
}
return nil, err
}
func (o *OryProvider) resolveEffectiveLoginID(loginID string) (string, error) {
if strings.Contains(loginID, "@") {
return loginID, nil
}
identityID, err := o.findIdentityID(loginID)
if err != nil {
return "", err
}
if identityID == "" {
return "", fmt.Errorf("ory provider: identity not found for loginID=%s", loginID)
}
fullIdentity, err := o.fetchIdentityFull(identityID)
if err != nil {
return "", err
}
if fullIdentity != nil {
if emailRaw, ok := fullIdentity.Traits["email"]; ok {
if email, ok := emailRaw.(string); ok && email != "" {
return email, nil
}
}
}
return "", fmt.Errorf("ory provider: email trait missing for loginID=%s", loginID)
}
func (o *OryProvider) submitLoginCodeInit(loginID, returnTo string) (*domain.LinkLoginInit, error) {
flowID, err := o.startLoginFlow(returnTo)
if err != nil {
@@ -404,13 +446,83 @@ func (o *OryProvider) ensureCodeLoginIdentifier(loginID string) error {
return nil
}
return o.patchIdentity(identityID, ops)
if err := o.patchIdentity(identityID, ops); err != nil {
slog.Warn("Ory identity patch failed, trying full update", "identity_id", identityID, "error", err)
}
fullIdentity, err := o.fetchIdentityFull(identityID)
if err != nil {
return err
}
addresses = make([]kratosVerifiableAddress, 0, len(fullIdentity.VerifiableAddresses)+1)
found := false
for _, addr := range fullIdentity.VerifiableAddresses {
addresses = append(addresses, kratosVerifiableAddress{
Value: addr.Value,
Via: addr.Via,
Verified: addr.Verified,
Status: addr.Status,
})
if addr.Value == loginID && addr.Via == via {
found = true
}
}
if !found {
addresses = append(addresses, kratosVerifiableAddress{
Value: loginID,
Via: via,
Verified: true,
Status: "completed",
})
}
payload := map[string]interface{}{
"schema_id": fullIdentity.SchemaID,
"traits": fullIdentity.Traits,
"verifiable_addresses": addresses,
}
if len(fullIdentity.RecoveryAddresses) > 0 {
payload["recovery_addresses"] = fullIdentity.RecoveryAddresses
}
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 {
return fmt.Errorf("ory provider: build identity update failed: %w", err)
}
req.Header.Set("Content-Type", "application/json")
resp, err := o.httpClient().Do(req)
if err != nil {
return fmt.Errorf("ory provider: identity update failed: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode >= 300 {
respBody, _ := io.ReadAll(io.LimitReader(resp.Body, 1024))
return fmt.Errorf("ory provider: identity update failed status=%d body=%s", resp.StatusCode, string(respBody))
}
slog.Info("Ory identity updated with verifiable address", "identity_id", identityID, "loginID", loginID, "via", via)
return nil
}
type kratosIdentity struct {
VerifiableAddresses []kratosVerifiableAddress `json:"verifiable_addresses"`
}
type kratosRecoveryAddress struct {
Value string `json:"value"`
Via string `json:"via"`
}
type kratosIdentityFull struct {
SchemaID string `json:"schema_id"`
Traits map[string]interface{} `json:"traits"`
VerifiableAddresses []kratosVerifiableAddress `json:"verifiable_addresses"`
RecoveryAddresses []kratosRecoveryAddress `json:"recovery_addresses"`
}
func (o *OryProvider) patchIdentity(identityID string, ops []map[string]interface{}) 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))
@@ -457,6 +569,30 @@ func (o *OryProvider) fetchIdentity(identityID string) (*kratosIdentity, error)
return &identity, nil
}
func (o *OryProvider) fetchIdentityFull(identityID string) (*kratosIdentityFull, error) {
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, fmt.Sprintf("%s/admin/identities/%s", o.KratosAdminURL, identityID), nil)
if err != nil {
return nil, fmt.Errorf("ory provider: build identity get failed: %w", err)
}
resp, err := o.httpClient().Do(req)
if err != nil {
return nil, fmt.Errorf("ory provider: identity get failed: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode >= 300 {
body, _ := io.ReadAll(io.LimitReader(resp.Body, 1024))
return nil, fmt.Errorf("ory provider: identity get failed status=%d body=%s", resp.StatusCode, string(body))
}
var identity kratosIdentityFull
if err := json.NewDecoder(resp.Body).Decode(&identity); err != nil {
return nil, fmt.Errorf("ory provider: decode identity failed: %w", err)
}
return &identity, nil
}
// VerifyLoginCode는 Kratos 로그인 코드 제출로 세션을 발급합니다.
func (o *OryProvider) VerifyLoginCode(loginID, flowID, code string) (*domain.AuthInfo, error) {
if loginID == "" || flowID == "" || code == "" {