getOrCreateUserWithIDP authenticates a user via an identity provider (SSO). Login API has allow_without_credential, so there's no workspace in the token context. We resolve workspace from the IDP entity (IDP resource_id is globally unique).
(ctx context.Context, request *v1pb.LoginRequest)
| 555 | // Login API has allow_without_credential, so there's no workspace in the token context. |
| 556 | // We resolve workspace from the IDP entity (IDP resource_id is globally unique). |
| 557 | func (s *AuthService) getOrCreateUserWithIDP(ctx context.Context, request *v1pb.LoginRequest) (*store.UserMessage, error) { |
| 558 | idpID, err := common.GetIdentityProviderID(request.IdpName) |
| 559 | if err != nil { |
| 560 | return nil, connect.NewError(connect.CodeInvalidArgument, errors.Wrapf(err, "failed to get identity provider ID")) |
| 561 | } |
| 562 | // Look up IDP without workspace filter — IDP resource_id is globally unique. |
| 563 | // The workspace is resolved from the IDP entity. |
| 564 | idp, err := s.store.GetIdentityProviderByID(ctx, idpID) |
| 565 | if err != nil { |
| 566 | return nil, connect.NewError(connect.CodeInternal, errors.Wrapf(err, "failed to get identity provider")) |
| 567 | } |
| 568 | if idp == nil { |
| 569 | return nil, connect.NewError(connect.CodeNotFound, errors.Errorf("identity provider not found")) |
| 570 | } |
| 571 | |
| 572 | // For workspace-scoped IDPs, use the IDP's workspace. |
| 573 | // For global IDPs (SaaS), workspace is resolved after authentication from user membership. |
| 574 | workspaceID := idp.Workspace |
| 575 | externalURL, err := utils.GetEffectiveExternalURL(ctx, s.store, s.profile, workspaceID) |
| 576 | if err != nil { |
| 577 | return nil, err |
| 578 | } |
| 579 | |
| 580 | var userInfo *storepb.IdentityProviderUserInfo |
| 581 | switch idp.Type { |
| 582 | case storepb.IdentityProviderType_OAUTH2: |
| 583 | oauth2Context := request.IdpContext.GetOauth2Context() |
| 584 | if oauth2Context == nil { |
| 585 | return nil, connect.NewError(connect.CodeInvalidArgument, errors.Errorf("missing OAuth2 context")) |
| 586 | } |
| 587 | oauth2IdentityProvider, err := oauth2.NewIdentityProvider(idp.Config.GetOauth2Config()) |
| 588 | if err != nil { |
| 589 | return nil, connect.NewError(connect.CodeInternal, errors.Wrapf(err, "failed to create new OAuth2 identity provider")) |
| 590 | } |
| 591 | redirectURL := fmt.Sprintf("%s/oauth/callback", externalURL) |
| 592 | token, err := oauth2IdentityProvider.ExchangeToken(ctx, redirectURL, oauth2Context.Code) |
| 593 | if err != nil { |
| 594 | return nil, connect.NewError(connect.CodeInternal, errors.Wrapf(err, "failed to exchange token")) |
| 595 | } |
| 596 | userInfo, _, err = oauth2IdentityProvider.UserInfo(token) |
| 597 | if err != nil { |
| 598 | return nil, connect.NewError(connect.CodeInternal, errors.Wrapf(err, "failed to get user info")) |
| 599 | } |
| 600 | case storepb.IdentityProviderType_OIDC: |
| 601 | oidcContext := request.IdpContext.GetOidcContext() |
| 602 | if oidcContext == nil { |
| 603 | return nil, connect.NewError(connect.CodeInvalidArgument, errors.Errorf("missing OIDC context")) |
| 604 | } |
| 605 | |
| 606 | oidcIDP, err := oidc.NewIdentityProvider(ctx, idp.Config.GetOidcConfig()) |
| 607 | if err != nil { |
| 608 | return nil, connect.NewError(connect.CodeInternal, errors.Wrapf(err, "failed to create new OIDC identity provider")) |
| 609 | } |
| 610 | |
| 611 | redirectURL := fmt.Sprintf("%s/oidc/callback", externalURL) |
| 612 | token, err := oidcIDP.ExchangeToken(ctx, redirectURL, oidcContext.Code) |
| 613 | if err != nil { |
| 614 | return nil, connect.NewError(connect.CodeInternal, errors.Wrapf(err, "failed to exchange token")) |
no test coverage detected