package auth import ( "context" "strings" "gateway/internal/library/zitadel" domauth "gateway/internal/model/auth/domain/usecase" memberdom "gateway/internal/model/member/domain" dommember "gateway/internal/model/member/domain/usecase" "gateway/internal/svc" "gateway/internal/types" "github.com/zeromicro/go-zero/core/logx" ) type RegisterSocialCallbackLogic struct { logx.Logger ctx context.Context svcCtx *svc.ServiceContext } func NewRegisterSocialCallbackLogic(ctx context.Context, svcCtx *svc.ServiceContext) *RegisterSocialCallbackLogic { return &RegisterSocialCallbackLogic{ Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx, } } func (l *RegisterSocialCallbackLogic) RegisterSocialCallback(req *types.RegisterSocialCallbackReq) (*types.AuthTokenData, error) { if l.svcCtx.Zitadel == nil || l.svcCtx.AuthRegistrationSession == nil { return nil, errb.SysNotImplemented("social registration not configured") } if l.svcCtx.MemberProvisioning == nil || l.svcCtx.MemberProfile == nil { return nil, errb.SysNotImplemented("member provisioning not configured") } if l.svcCtx.AuthToken == nil { return nil, errb.SysNotImplemented("auth token not configured") } sessionID, err := parseRegisterOAuthState(req.State) if err != nil { return nil, err } session, err := l.svcCtx.AuthRegistrationSession.Get(l.ctx, sessionID) if err != nil { return nil, err } defer func() { if delErr := l.svcCtx.AuthRegistrationSession.Delete(l.ctx, sessionID); delErr != nil { logx.WithContext(l.ctx).Errorf("register social callback: delete session: %v", delErr) } }() tok, err := l.svcCtx.Zitadel.ExchangeAuthorizationCode(l.ctx, req.Code, session.RedirectURI) if err != nil { return nil, wrapZitadelErr(err) } var claims *zitadel.IDTokenClaims if tok.IDToken != "" { claims, err = l.svcCtx.Zitadel.VerifyIDToken(l.ctx, tok.IDToken) } else { claims, err = zitadelIdentityFromToken(l.ctx, l.svcCtx.Zitadel, tok) } if err != nil { return nil, wrapZitadelErr(err) } trustSocial := l.svcCtx.Config.Member.Defaults().Registration.TrustSocialEmailVerified if err := federatedEmailAllowed(claims, session.Provider, trustSocial); err != nil { return nil, err } isExisting := false if _, err := l.svcCtx.MemberProfile.GetByZitadelUserID(l.ctx, session.TenantID, claims.Sub); err == nil { isExisting = true } else if !isMemberNotFound(err) { return nil, err } if isExisting && session.InviteNewUsersOnly { return nil, errb.ResAlreadyExist("account already exists, please login").WithCause(memberdom.ErrDuplicateMember) } var inviteCodeID string if l.svcCtx.Config.Member.Defaults().Registration.RequireInviteCode { if l.svcCtx.AuthInvite == nil { return nil, errb.SysNotImplemented("invite validation not configured") } consumed, err := l.svcCtx.AuthInvite.Consume(l.ctx, &domauth.ConsumeInviteRequest{ TenantID: session.TenantID, Code: session.InviteCode, }) if err != nil { return nil, err } inviteCodeID = consumed.ID } provider := strings.ToLower(strings.TrimSpace(session.Provider)) var memberDTO *dommember.MemberDTO switch provider { case "ldap": memberDTO, err = l.svcCtx.MemberProvisioning.EnsureFromLDAP(l.ctx, &dommember.EnsureFromLDAPRequest{ TenantID: session.TenantID, ZitadelSub: claims.Sub, ExternalID: claims.Sub, Email: claims.Email, DisplayName: claims.Name, }) default: memberDTO, err = l.svcCtx.MemberProvisioning.EnsureFromOIDC(l.ctx, &dommember.EnsureFromOIDCRequest{ TenantID: session.TenantID, ZitadelSub: claims.Sub, Email: claims.Email, EmailVerified: claims.EmailVerified, DisplayName: claims.Name, Locale: firstNonEmpty(session.Language, claims.Locale), }) } if err != nil { return nil, err } if !isExisting { if err := recordRegistrationMeta(l.ctx, l.svcCtx, session.TenantID, memberDTO.UID, inviteCodeID, session.AcceptTermsVersion, session.MarketingOptIn, registrationChannelForProvider(provider)); err != nil { return nil, err } } return issueAuthToken(l.ctx, l.svcCtx, session.TenantID, memberDTO.UID) } func firstNonEmpty(values ...string) string { for _, v := range values { if v != "" { return v } } return "" }