diff --git a/codecov.yml b/codecov.yml index 620228c31857..595b2071aee2 100644 --- a/codecov.yml +++ b/codecov.yml @@ -10,3 +10,4 @@ ignore: - "internal" - "docs" - "contrib" + - "selfservice/strategy/oidc/provider_netid.go" # No way to test this provider automatically diff --git a/driver/registry.go b/driver/registry.go index e284d6f6a6dd..9f0e7cb3cdde 100644 --- a/driver/registry.go +++ b/driver/registry.go @@ -185,6 +185,7 @@ type options struct { extraGoMigrations popx.Migrations replacementStrategies []NewStrategy extraHooks map[string]func(config.SelfServiceHook) any + extraHandlers []NewHandlerRegistrar disableMigrationLogging bool jsonnetPool jsonnetsecure.Pool } @@ -236,6 +237,14 @@ func WithExtraHooks(hooks map[string]func(config.SelfServiceHook) any) RegistryO } } +type NewHandlerRegistrar func(deps any) x.HandlerRegistrar + +func WithExtraHandlers(handlers ...NewHandlerRegistrar) RegistryOption { + return func(o *options) { + o.extraHandlers = handlers + } +} + func Inspect(f func(reg Registry) error) RegistryOption { return func(o *options) { o.inspect = f diff --git a/driver/registry_default.go b/driver/registry_default.go index 464f7881f626..73f0ef14fd8a 100644 --- a/driver/registry_default.go +++ b/driver/registry_default.go @@ -78,6 +78,8 @@ type RegistryDefault struct { ctxer contextx.Contextualizer injectedSelfserviceHooks map[string]func(config.SelfServiceHook) interface{} + extraHandlerFactories []NewHandlerRegistrar + extraHandlers []x.HandlerRegistrar nosurf nosurf.Handler trc *otelx.Tracer @@ -175,6 +177,9 @@ func (m *RegistryDefault) Audit() *logrusx.Logger { } func (m *RegistryDefault) RegisterPublicRoutes(ctx context.Context, router *x.RouterPublic) { + for _, h := range m.ExtraHandlers() { + h.RegisterPublicRoutes(router) + } m.LoginHandler().RegisterPublicRoutes(router) m.RegistrationHandler().RegisterPublicRoutes(router) m.LogoutHandler().RegisterPublicRoutes(router) @@ -198,6 +203,9 @@ func (m *RegistryDefault) RegisterPublicRoutes(ctx context.Context, router *x.Ro } func (m *RegistryDefault) RegisterAdminRoutes(ctx context.Context, router *x.RouterAdmin) { + for _, h := range m.ExtraHandlers() { + h.RegisterAdminRoutes(router) + } m.RegistrationHandler().RegisterAdminRoutes(router) m.LoginHandler().RegisterAdminRoutes(router) m.LogoutHandler().RegisterAdminRoutes(router) @@ -640,6 +648,9 @@ func (m *RegistryDefault) Init(ctx context.Context, ctxer contextx.Contextualize if o.extraHooks != nil { m.WithHooks(o.extraHooks) } + if o.extraHandlers != nil { + m.WithExtraHandlers(o.extraHandlers) + } if o.replaceIdentitySchemaProvider != nil { m.identitySchemaProvider = o.replaceIdentitySchemaProvider(m) @@ -904,3 +915,12 @@ func (m *RegistryDefault) SessionTokenizer() *session.Tokenizer { } return m.sessionTokenizer } + +func (m *RegistryDefault) ExtraHandlers() []x.HandlerRegistrar { + if m.extraHandlers == nil { + for _, newHandler := range m.extraHandlerFactories { + m.extraHandlers = append(m.extraHandlers, newHandler(m)) + } + } + return m.extraHandlers +} diff --git a/driver/registry_default_hooks.go b/driver/registry_default_hooks.go index 73a855daadc5..8b5bfd8bb2a0 100644 --- a/driver/registry_default_hooks.go +++ b/driver/registry_default_hooks.go @@ -60,6 +60,9 @@ func (m *RegistryDefault) HookTwoStepRegistration() *hook.TwoStepRegistration { func (m *RegistryDefault) WithHooks(hooks map[string]func(config.SelfServiceHook) interface{}) { m.injectedSelfserviceHooks = hooks } +func (m *RegistryDefault) WithExtraHandlers(handlers []NewHandlerRegistrar) { + m.extraHandlerFactories = handlers +} func (m *RegistryDefault) getHooks(credentialsType string, configs []config.SelfServiceHook) (i []interface{}) { var addSessionIssuer bool diff --git a/embedx/config.schema.json b/embedx/config.schema.json index 5fcf826f4c2a..049de330a512 100644 --- a/embedx/config.schema.json +++ b/embedx/config.schema.json @@ -460,7 +460,8 @@ "linkedin", "linkedin_v2", "lark", - "x" + "x", + "fedcm-test" ], "examples": ["google"] }, @@ -578,6 +579,19 @@ "type": "string", "enum": ["auto", "never", "force"], "default": "auto" + }, + "fedcm_config_url": { + "title": "Federation Configuration URL", + "description": "The URL where the FedCM IdP configuration is located for the provider. This is only effective in the Ory Network.", + "type": "string", + "format": "uri", + "examples": ["https://example.com/config.json"] + }, + "net_id_token_origin_header": { + "title": "NetID Token Origin Header", + "description": "Contains the orgin header to be used when exchanging a NetID FedCM token for an ID token", + "type": "string", + "examples": ["https://example.com"] } }, "additionalProperties": false, diff --git a/internal/client-go/.openapi-generator/FILES b/internal/client-go/.openapi-generator/FILES index 118cf9b06463..e5608d3b70a9 100644 --- a/internal/client-go/.openapi-generator/FILES +++ b/internal/client-go/.openapi-generator/FILES @@ -24,6 +24,7 @@ docs/ContinueWithVerificationUiFlow.md docs/CourierAPI.md docs/CourierMessageStatus.md docs/CourierMessageType.md +docs/CreateFedcmFlowResponse.md docs/CreateIdentityBody.md docs/CreateRecoveryCodeForIdentityBody.md docs/CreateRecoveryLinkForIdentityBody.md @@ -70,6 +71,7 @@ docs/OAuth2ConsentRequestOpenIDConnectContext.md docs/OAuth2LoginRequest.md docs/PatchIdentitiesBody.md docs/PerformNativeLogoutBody.md +docs/Provider.md docs/RecoveryCodeForIdentity.md docs/RecoveryFlow.md docs/RecoveryFlowState.md @@ -98,6 +100,7 @@ docs/UiNodeMeta.md docs/UiNodeScriptAttributes.md docs/UiNodeTextAttributes.md docs/UiText.md +docs/UpdateFedcmFlowBody.md docs/UpdateIdentityBody.md docs/UpdateLoginFlowBody.md docs/UpdateLoginFlowWithCodeMethod.md @@ -150,6 +153,7 @@ model_continue_with_verification_ui.go model_continue_with_verification_ui_flow.go model_courier_message_status.go model_courier_message_type.go +model_create_fedcm_flow_response.go model_create_identity_body.go model_create_recovery_code_for_identity_body.go model_create_recovery_link_for_identity_body.go @@ -193,6 +197,7 @@ model_o_auth2_consent_request_open_id_connect_context.go model_o_auth2_login_request.go model_patch_identities_body.go model_perform_native_logout_body.go +model_provider.go model_recovery_code_for_identity.go model_recovery_flow.go model_recovery_flow_state.go @@ -221,6 +226,7 @@ model_ui_node_meta.go model_ui_node_script_attributes.go model_ui_node_text_attributes.go model_ui_text.go +model_update_fedcm_flow_body.go model_update_identity_body.go model_update_login_flow_body.go model_update_login_flow_with_code_method.go diff --git a/internal/client-go/README.md b/internal/client-go/README.md index 97593523117a..b418e308083f 100644 --- a/internal/client-go/README.md +++ b/internal/client-go/README.md @@ -87,6 +87,7 @@ Class | Method | HTTP request | Description *FrontendAPI* | [**CreateBrowserRegistrationFlow**](docs/FrontendAPI.md#createbrowserregistrationflow) | **Get** /self-service/registration/browser | Create Registration Flow for Browsers *FrontendAPI* | [**CreateBrowserSettingsFlow**](docs/FrontendAPI.md#createbrowsersettingsflow) | **Get** /self-service/settings/browser | Create Settings Flow for Browsers *FrontendAPI* | [**CreateBrowserVerificationFlow**](docs/FrontendAPI.md#createbrowserverificationflow) | **Get** /self-service/verification/browser | Create Verification Flow for Browser Clients +*FrontendAPI* | [**CreateFedcmFlow**](docs/FrontendAPI.md#createfedcmflow) | **Get** /self-service/fed-cm/parameters | Get FedCM Parameters *FrontendAPI* | [**CreateNativeLoginFlow**](docs/FrontendAPI.md#createnativeloginflow) | **Get** /self-service/login/api | Create Login Flow for Native Apps *FrontendAPI* | [**CreateNativeRecoveryFlow**](docs/FrontendAPI.md#createnativerecoveryflow) | **Get** /self-service/recovery/api | Create Recovery Flow for Native Apps *FrontendAPI* | [**CreateNativeRegistrationFlow**](docs/FrontendAPI.md#createnativeregistrationflow) | **Get** /self-service/registration/api | Create Registration Flow for Native Apps @@ -105,6 +106,7 @@ Class | Method | HTTP request | Description *FrontendAPI* | [**ListMySessions**](docs/FrontendAPI.md#listmysessions) | **Get** /sessions | Get My Active Sessions *FrontendAPI* | [**PerformNativeLogout**](docs/FrontendAPI.md#performnativelogout) | **Delete** /self-service/logout/api | Perform Logout for Native Apps *FrontendAPI* | [**ToSession**](docs/FrontendAPI.md#tosession) | **Get** /sessions/whoami | Check Who the Current HTTP Session Belongs To +*FrontendAPI* | [**UpdateFedcmFlow**](docs/FrontendAPI.md#updatefedcmflow) | **Post** /self-service/fed-cm/token | Submit a FedCM token *FrontendAPI* | [**UpdateLoginFlow**](docs/FrontendAPI.md#updateloginflow) | **Post** /self-service/login | Submit a Login Flow *FrontendAPI* | [**UpdateLogoutFlow**](docs/FrontendAPI.md#updatelogoutflow) | **Get** /self-service/logout | Update Logout Flow *FrontendAPI* | [**UpdateRecoveryFlow**](docs/FrontendAPI.md#updaterecoveryflow) | **Post** /self-service/recovery | Update Recovery Flow @@ -150,6 +152,7 @@ Class | Method | HTTP request | Description - [ContinueWithVerificationUiFlow](docs/ContinueWithVerificationUiFlow.md) - [CourierMessageStatus](docs/CourierMessageStatus.md) - [CourierMessageType](docs/CourierMessageType.md) + - [CreateFedcmFlowResponse](docs/CreateFedcmFlowResponse.md) - [CreateIdentityBody](docs/CreateIdentityBody.md) - [CreateRecoveryCodeForIdentityBody](docs/CreateRecoveryCodeForIdentityBody.md) - [CreateRecoveryLinkForIdentityBody](docs/CreateRecoveryLinkForIdentityBody.md) @@ -193,6 +196,7 @@ Class | Method | HTTP request | Description - [OAuth2LoginRequest](docs/OAuth2LoginRequest.md) - [PatchIdentitiesBody](docs/PatchIdentitiesBody.md) - [PerformNativeLogoutBody](docs/PerformNativeLogoutBody.md) + - [Provider](docs/Provider.md) - [RecoveryCodeForIdentity](docs/RecoveryCodeForIdentity.md) - [RecoveryFlow](docs/RecoveryFlow.md) - [RecoveryFlowState](docs/RecoveryFlowState.md) @@ -221,6 +225,7 @@ Class | Method | HTTP request | Description - [UiNodeScriptAttributes](docs/UiNodeScriptAttributes.md) - [UiNodeTextAttributes](docs/UiNodeTextAttributes.md) - [UiText](docs/UiText.md) + - [UpdateFedcmFlowBody](docs/UpdateFedcmFlowBody.md) - [UpdateIdentityBody](docs/UpdateIdentityBody.md) - [UpdateLoginFlowBody](docs/UpdateLoginFlowBody.md) - [UpdateLoginFlowWithCodeMethod](docs/UpdateLoginFlowWithCodeMethod.md) diff --git a/internal/client-go/api_frontend.go b/internal/client-go/api_frontend.go index 97266e9c4c94..cd243b065b4b 100644 --- a/internal/client-go/api_frontend.go +++ b/internal/client-go/api_frontend.go @@ -201,6 +201,20 @@ type FrontendAPI interface { */ CreateBrowserVerificationFlowExecute(r FrontendAPIApiCreateBrowserVerificationFlowRequest) (*VerificationFlow, *http.Response, error) + /* + * CreateFedcmFlow Get FedCM Parameters + * This endpoint returns a list of all available FedCM providers. It is only supported on the Ory Network. + * @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). + * @return FrontendAPIApiCreateFedcmFlowRequest + */ + CreateFedcmFlow(ctx context.Context) FrontendAPIApiCreateFedcmFlowRequest + + /* + * CreateFedcmFlowExecute executes the request + * @return CreateFedcmFlowResponse + */ + CreateFedcmFlowExecute(r FrontendAPIApiCreateFedcmFlowRequest) (*CreateFedcmFlowResponse, *http.Response, error) + /* * CreateNativeLoginFlow Create Login Flow for Native Apps * This endpoint initiates a login flow for native apps that do not use a browser, such as mobile devices, smart TVs, and so on. @@ -709,6 +723,23 @@ type FrontendAPI interface { */ ToSessionExecute(r FrontendAPIApiToSessionRequest) (*Session, *http.Response, error) + /* + * UpdateFedcmFlow Submit a FedCM token + * Use this endpoint to submit a token from a FedCM provider through + `navigator.credentials.get` and log the user in. The parameters from + `navigator.credentials.get` must have come from `GET + self-service/fed-cm/parameters`. + * @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). + * @return FrontendAPIApiUpdateFedcmFlowRequest + */ + UpdateFedcmFlow(ctx context.Context) FrontendAPIApiUpdateFedcmFlowRequest + + /* + * UpdateFedcmFlowExecute executes the request + * @return SuccessfulNativeLogin + */ + UpdateFedcmFlowExecute(r FrontendAPIApiUpdateFedcmFlowRequest) (*SuccessfulNativeLogin, *http.Response, error) + /* * UpdateLoginFlow Submit a Login Flow * Use this endpoint to complete a login flow. This endpoint @@ -1890,6 +1921,124 @@ func (a *FrontendAPIService) CreateBrowserVerificationFlowExecute(r FrontendAPIA return localVarReturnValue, localVarHTTPResponse, nil } +type FrontendAPIApiCreateFedcmFlowRequest struct { + ctx context.Context + ApiService FrontendAPI +} + +func (r FrontendAPIApiCreateFedcmFlowRequest) Execute() (*CreateFedcmFlowResponse, *http.Response, error) { + return r.ApiService.CreateFedcmFlowExecute(r) +} + +/* + * CreateFedcmFlow Get FedCM Parameters + * This endpoint returns a list of all available FedCM providers. It is only supported on the Ory Network. + * @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). + * @return FrontendAPIApiCreateFedcmFlowRequest + */ +func (a *FrontendAPIService) CreateFedcmFlow(ctx context.Context) FrontendAPIApiCreateFedcmFlowRequest { + return FrontendAPIApiCreateFedcmFlowRequest{ + ApiService: a, + ctx: ctx, + } +} + +/* + * Execute executes the request + * @return CreateFedcmFlowResponse + */ +func (a *FrontendAPIService) CreateFedcmFlowExecute(r FrontendAPIApiCreateFedcmFlowRequest) (*CreateFedcmFlowResponse, *http.Response, error) { + var ( + localVarHTTPMethod = http.MethodGet + localVarPostBody interface{} + localVarFormFileName string + localVarFileName string + localVarFileBytes []byte + localVarReturnValue *CreateFedcmFlowResponse + ) + + localBasePath, err := a.client.cfg.ServerURLWithContext(r.ctx, "FrontendAPIService.CreateFedcmFlow") + if err != nil { + return localVarReturnValue, nil, &GenericOpenAPIError{error: err.Error()} + } + + localVarPath := localBasePath + "/self-service/fed-cm/parameters" + + localVarHeaderParams := make(map[string]string) + localVarQueryParams := url.Values{} + localVarFormParams := url.Values{} + + // to determine the Content-Type header + localVarHTTPContentTypes := []string{} + + // set Content-Type header + localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes) + if localVarHTTPContentType != "" { + localVarHeaderParams["Content-Type"] = localVarHTTPContentType + } + + // to determine the Accept header + localVarHTTPHeaderAccepts := []string{"application/json"} + + // set Accept header + localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts) + if localVarHTTPHeaderAccept != "" { + localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept + } + req, err := a.client.prepareRequest(r.ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, localVarFormFileName, localVarFileName, localVarFileBytes) + if err != nil { + return localVarReturnValue, nil, err + } + + localVarHTTPResponse, err := a.client.callAPI(req) + if err != nil || localVarHTTPResponse == nil { + return localVarReturnValue, localVarHTTPResponse, err + } + + localVarBody, err := io.ReadAll(io.LimitReader(localVarHTTPResponse.Body, 1024*1024)) + localVarHTTPResponse.Body.Close() + localVarHTTPResponse.Body = io.NopCloser(bytes.NewBuffer(localVarBody)) + if err != nil { + return localVarReturnValue, localVarHTTPResponse, err + } + + if localVarHTTPResponse.StatusCode >= 300 { + newErr := &GenericOpenAPIError{ + body: localVarBody, + error: localVarHTTPResponse.Status, + } + if localVarHTTPResponse.StatusCode == 400 { + var v ErrorGeneric + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.model = v + return localVarReturnValue, localVarHTTPResponse, newErr + } + var v ErrorGeneric + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.model = v + return localVarReturnValue, localVarHTTPResponse, newErr + } + + err = a.client.decode(&localVarReturnValue, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr := &GenericOpenAPIError{ + body: localVarBody, + error: err.Error(), + } + return localVarReturnValue, localVarHTTPResponse, newErr + } + + return localVarReturnValue, localVarHTTPResponse, nil +} + type FrontendAPIApiCreateNativeLoginFlowRequest struct { ctx context.Context ApiService FrontendAPI @@ -4751,6 +4900,159 @@ func (a *FrontendAPIService) ToSessionExecute(r FrontendAPIApiToSessionRequest) return localVarReturnValue, localVarHTTPResponse, nil } +type FrontendAPIApiUpdateFedcmFlowRequest struct { + ctx context.Context + ApiService FrontendAPI + updateFedcmFlowBody *UpdateFedcmFlowBody +} + +func (r FrontendAPIApiUpdateFedcmFlowRequest) UpdateFedcmFlowBody(updateFedcmFlowBody UpdateFedcmFlowBody) FrontendAPIApiUpdateFedcmFlowRequest { + r.updateFedcmFlowBody = &updateFedcmFlowBody + return r +} + +func (r FrontendAPIApiUpdateFedcmFlowRequest) Execute() (*SuccessfulNativeLogin, *http.Response, error) { + return r.ApiService.UpdateFedcmFlowExecute(r) +} + +/* + - UpdateFedcmFlow Submit a FedCM token + - Use this endpoint to submit a token from a FedCM provider through + +`navigator.credentials.get` and log the user in. The parameters from +`navigator.credentials.get` must have come from `GET +self-service/fed-cm/parameters`. + - @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). + - @return FrontendAPIApiUpdateFedcmFlowRequest +*/ +func (a *FrontendAPIService) UpdateFedcmFlow(ctx context.Context) FrontendAPIApiUpdateFedcmFlowRequest { + return FrontendAPIApiUpdateFedcmFlowRequest{ + ApiService: a, + ctx: ctx, + } +} + +/* + * Execute executes the request + * @return SuccessfulNativeLogin + */ +func (a *FrontendAPIService) UpdateFedcmFlowExecute(r FrontendAPIApiUpdateFedcmFlowRequest) (*SuccessfulNativeLogin, *http.Response, error) { + var ( + localVarHTTPMethod = http.MethodPost + localVarPostBody interface{} + localVarFormFileName string + localVarFileName string + localVarFileBytes []byte + localVarReturnValue *SuccessfulNativeLogin + ) + + localBasePath, err := a.client.cfg.ServerURLWithContext(r.ctx, "FrontendAPIService.UpdateFedcmFlow") + if err != nil { + return localVarReturnValue, nil, &GenericOpenAPIError{error: err.Error()} + } + + localVarPath := localBasePath + "/self-service/fed-cm/token" + + localVarHeaderParams := make(map[string]string) + localVarQueryParams := url.Values{} + localVarFormParams := url.Values{} + if r.updateFedcmFlowBody == nil { + return localVarReturnValue, nil, reportError("updateFedcmFlowBody is required and must be specified") + } + + // to determine the Content-Type header + localVarHTTPContentTypes := []string{"application/json", "application/x-www-form-urlencoded"} + + // set Content-Type header + localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes) + if localVarHTTPContentType != "" { + localVarHeaderParams["Content-Type"] = localVarHTTPContentType + } + + // to determine the Accept header + localVarHTTPHeaderAccepts := []string{"application/json"} + + // set Accept header + localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts) + if localVarHTTPHeaderAccept != "" { + localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept + } + // body params + localVarPostBody = r.updateFedcmFlowBody + req, err := a.client.prepareRequest(r.ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, localVarFormFileName, localVarFileName, localVarFileBytes) + if err != nil { + return localVarReturnValue, nil, err + } + + localVarHTTPResponse, err := a.client.callAPI(req) + if err != nil || localVarHTTPResponse == nil { + return localVarReturnValue, localVarHTTPResponse, err + } + + localVarBody, err := io.ReadAll(io.LimitReader(localVarHTTPResponse.Body, 1024*1024)) + localVarHTTPResponse.Body.Close() + localVarHTTPResponse.Body = io.NopCloser(bytes.NewBuffer(localVarBody)) + if err != nil { + return localVarReturnValue, localVarHTTPResponse, err + } + + if localVarHTTPResponse.StatusCode >= 300 { + newErr := &GenericOpenAPIError{ + body: localVarBody, + error: localVarHTTPResponse.Status, + } + if localVarHTTPResponse.StatusCode == 400 { + var v LoginFlow + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.model = v + return localVarReturnValue, localVarHTTPResponse, newErr + } + if localVarHTTPResponse.StatusCode == 410 { + var v ErrorGeneric + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.model = v + return localVarReturnValue, localVarHTTPResponse, newErr + } + if localVarHTTPResponse.StatusCode == 422 { + var v ErrorBrowserLocationChangeRequired + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.model = v + return localVarReturnValue, localVarHTTPResponse, newErr + } + var v ErrorGeneric + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.model = v + return localVarReturnValue, localVarHTTPResponse, newErr + } + + err = a.client.decode(&localVarReturnValue, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr := &GenericOpenAPIError{ + body: localVarBody, + error: err.Error(), + } + return localVarReturnValue, localVarHTTPResponse, newErr + } + + return localVarReturnValue, localVarHTTPResponse, nil +} + type FrontendAPIApiUpdateLoginFlowRequest struct { ctx context.Context ApiService FrontendAPI diff --git a/internal/client-go/model_create_fedcm_flow_response.go b/internal/client-go/model_create_fedcm_flow_response.go new file mode 100644 index 000000000000..fdca32672c63 --- /dev/null +++ b/internal/client-go/model_create_fedcm_flow_response.go @@ -0,0 +1,150 @@ +/* + * Ory Identities API + * + * This is the API specification for Ory Identities with features such as registration, login, recovery, account verification, profile settings, password reset, identity management, session management, email and sms delivery, and more. + * + * API version: + * Contact: office@ory.sh + */ + +// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT. + +package client + +import ( + "encoding/json" +) + +// CreateFedcmFlowResponse Contains a list of all available FedCM providers. +type CreateFedcmFlowResponse struct { + CsrfToken *string `json:"csrf_token,omitempty"` + Providers []Provider `json:"providers,omitempty"` +} + +// NewCreateFedcmFlowResponse instantiates a new CreateFedcmFlowResponse object +// This constructor will assign default values to properties that have it defined, +// and makes sure properties required by API are set, but the set of arguments +// will change when the set of required properties is changed +func NewCreateFedcmFlowResponse() *CreateFedcmFlowResponse { + this := CreateFedcmFlowResponse{} + return &this +} + +// NewCreateFedcmFlowResponseWithDefaults instantiates a new CreateFedcmFlowResponse object +// This constructor will only assign default values to properties that have it defined, +// but it doesn't guarantee that properties required by API are set +func NewCreateFedcmFlowResponseWithDefaults() *CreateFedcmFlowResponse { + this := CreateFedcmFlowResponse{} + return &this +} + +// GetCsrfToken returns the CsrfToken field value if set, zero value otherwise. +func (o *CreateFedcmFlowResponse) GetCsrfToken() string { + if o == nil || o.CsrfToken == nil { + var ret string + return ret + } + return *o.CsrfToken +} + +// GetCsrfTokenOk returns a tuple with the CsrfToken field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *CreateFedcmFlowResponse) GetCsrfTokenOk() (*string, bool) { + if o == nil || o.CsrfToken == nil { + return nil, false + } + return o.CsrfToken, true +} + +// HasCsrfToken returns a boolean if a field has been set. +func (o *CreateFedcmFlowResponse) HasCsrfToken() bool { + if o != nil && o.CsrfToken != nil { + return true + } + + return false +} + +// SetCsrfToken gets a reference to the given string and assigns it to the CsrfToken field. +func (o *CreateFedcmFlowResponse) SetCsrfToken(v string) { + o.CsrfToken = &v +} + +// GetProviders returns the Providers field value if set, zero value otherwise. +func (o *CreateFedcmFlowResponse) GetProviders() []Provider { + if o == nil || o.Providers == nil { + var ret []Provider + return ret + } + return o.Providers +} + +// GetProvidersOk returns a tuple with the Providers field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *CreateFedcmFlowResponse) GetProvidersOk() ([]Provider, bool) { + if o == nil || o.Providers == nil { + return nil, false + } + return o.Providers, true +} + +// HasProviders returns a boolean if a field has been set. +func (o *CreateFedcmFlowResponse) HasProviders() bool { + if o != nil && o.Providers != nil { + return true + } + + return false +} + +// SetProviders gets a reference to the given []Provider and assigns it to the Providers field. +func (o *CreateFedcmFlowResponse) SetProviders(v []Provider) { + o.Providers = v +} + +func (o CreateFedcmFlowResponse) MarshalJSON() ([]byte, error) { + toSerialize := map[string]interface{}{} + if o.CsrfToken != nil { + toSerialize["csrf_token"] = o.CsrfToken + } + if o.Providers != nil { + toSerialize["providers"] = o.Providers + } + return json.Marshal(toSerialize) +} + +type NullableCreateFedcmFlowResponse struct { + value *CreateFedcmFlowResponse + isSet bool +} + +func (v NullableCreateFedcmFlowResponse) Get() *CreateFedcmFlowResponse { + return v.value +} + +func (v *NullableCreateFedcmFlowResponse) Set(val *CreateFedcmFlowResponse) { + v.value = val + v.isSet = true +} + +func (v NullableCreateFedcmFlowResponse) IsSet() bool { + return v.isSet +} + +func (v *NullableCreateFedcmFlowResponse) Unset() { + v.value = nil + v.isSet = false +} + +func NewNullableCreateFedcmFlowResponse(val *CreateFedcmFlowResponse) *NullableCreateFedcmFlowResponse { + return &NullableCreateFedcmFlowResponse{value: val, isSet: true} +} + +func (v NullableCreateFedcmFlowResponse) MarshalJSON() ([]byte, error) { + return json.Marshal(v.value) +} + +func (v *NullableCreateFedcmFlowResponse) UnmarshalJSON(src []byte) error { + v.isSet = true + return json.Unmarshal(src, &v.value) +} diff --git a/internal/client-go/model_provider.go b/internal/client-go/model_provider.go new file mode 100644 index 000000000000..2c9a79590e0e --- /dev/null +++ b/internal/client-go/model_provider.go @@ -0,0 +1,337 @@ +/* + * Ory Identities API + * + * This is the API specification for Ory Identities with features such as registration, login, recovery, account verification, profile settings, password reset, identity management, session management, email and sms delivery, and more. + * + * API version: + * Contact: office@ory.sh + */ + +// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT. + +package client + +import ( + "encoding/json" +) + +// Provider struct for Provider +type Provider struct { + // The RP's client identifier, issued by the IdP. + ClientId *string `json:"client_id,omitempty"` + // A full path of the IdP config file. + ConfigUrl *string `json:"config_url,omitempty"` + // By specifying one of domain_hints values provided by the accounts endpoints, the FedCM dialog selectively shows the specified account. + DomainHint *string `json:"domain_hint,omitempty"` + // Array of strings that specifies the user information (\"name\", \" email\", \"picture\") that RP needs IdP to share with them. Note: Field API is supported by Chrome 132 and later. + Fields []string `json:"fields,omitempty"` + // By specifying one of login_hints values provided by the accounts endpoints, the FedCM dialog selectively shows the specified account. + LoginHint *string `json:"login_hint,omitempty"` + // A random string to ensure the response is issued for this specific request. Prevents replay attacks. + Nonce *string `json:"nonce,omitempty"` + // Custom object that allows to specify additional key-value parameters: scope: A string value containing additional permissions that RP needs to request, for example \" drive.readonly calendar.readonly\" nonce: A random string to ensure the response is issued for this specific request. Prevents replay attacks. Other custom key-value parameters. Note: parameters is supported from Chrome 132. + Parameters *map[string]string `json:"parameters,omitempty"` +} + +// NewProvider instantiates a new Provider object +// This constructor will assign default values to properties that have it defined, +// and makes sure properties required by API are set, but the set of arguments +// will change when the set of required properties is changed +func NewProvider() *Provider { + this := Provider{} + return &this +} + +// NewProviderWithDefaults instantiates a new Provider object +// This constructor will only assign default values to properties that have it defined, +// but it doesn't guarantee that properties required by API are set +func NewProviderWithDefaults() *Provider { + this := Provider{} + return &this +} + +// GetClientId returns the ClientId field value if set, zero value otherwise. +func (o *Provider) GetClientId() string { + if o == nil || o.ClientId == nil { + var ret string + return ret + } + return *o.ClientId +} + +// GetClientIdOk returns a tuple with the ClientId field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *Provider) GetClientIdOk() (*string, bool) { + if o == nil || o.ClientId == nil { + return nil, false + } + return o.ClientId, true +} + +// HasClientId returns a boolean if a field has been set. +func (o *Provider) HasClientId() bool { + if o != nil && o.ClientId != nil { + return true + } + + return false +} + +// SetClientId gets a reference to the given string and assigns it to the ClientId field. +func (o *Provider) SetClientId(v string) { + o.ClientId = &v +} + +// GetConfigUrl returns the ConfigUrl field value if set, zero value otherwise. +func (o *Provider) GetConfigUrl() string { + if o == nil || o.ConfigUrl == nil { + var ret string + return ret + } + return *o.ConfigUrl +} + +// GetConfigUrlOk returns a tuple with the ConfigUrl field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *Provider) GetConfigUrlOk() (*string, bool) { + if o == nil || o.ConfigUrl == nil { + return nil, false + } + return o.ConfigUrl, true +} + +// HasConfigUrl returns a boolean if a field has been set. +func (o *Provider) HasConfigUrl() bool { + if o != nil && o.ConfigUrl != nil { + return true + } + + return false +} + +// SetConfigUrl gets a reference to the given string and assigns it to the ConfigUrl field. +func (o *Provider) SetConfigUrl(v string) { + o.ConfigUrl = &v +} + +// GetDomainHint returns the DomainHint field value if set, zero value otherwise. +func (o *Provider) GetDomainHint() string { + if o == nil || o.DomainHint == nil { + var ret string + return ret + } + return *o.DomainHint +} + +// GetDomainHintOk returns a tuple with the DomainHint field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *Provider) GetDomainHintOk() (*string, bool) { + if o == nil || o.DomainHint == nil { + return nil, false + } + return o.DomainHint, true +} + +// HasDomainHint returns a boolean if a field has been set. +func (o *Provider) HasDomainHint() bool { + if o != nil && o.DomainHint != nil { + return true + } + + return false +} + +// SetDomainHint gets a reference to the given string and assigns it to the DomainHint field. +func (o *Provider) SetDomainHint(v string) { + o.DomainHint = &v +} + +// GetFields returns the Fields field value if set, zero value otherwise. +func (o *Provider) GetFields() []string { + if o == nil || o.Fields == nil { + var ret []string + return ret + } + return o.Fields +} + +// GetFieldsOk returns a tuple with the Fields field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *Provider) GetFieldsOk() ([]string, bool) { + if o == nil || o.Fields == nil { + return nil, false + } + return o.Fields, true +} + +// HasFields returns a boolean if a field has been set. +func (o *Provider) HasFields() bool { + if o != nil && o.Fields != nil { + return true + } + + return false +} + +// SetFields gets a reference to the given []string and assigns it to the Fields field. +func (o *Provider) SetFields(v []string) { + o.Fields = v +} + +// GetLoginHint returns the LoginHint field value if set, zero value otherwise. +func (o *Provider) GetLoginHint() string { + if o == nil || o.LoginHint == nil { + var ret string + return ret + } + return *o.LoginHint +} + +// GetLoginHintOk returns a tuple with the LoginHint field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *Provider) GetLoginHintOk() (*string, bool) { + if o == nil || o.LoginHint == nil { + return nil, false + } + return o.LoginHint, true +} + +// HasLoginHint returns a boolean if a field has been set. +func (o *Provider) HasLoginHint() bool { + if o != nil && o.LoginHint != nil { + return true + } + + return false +} + +// SetLoginHint gets a reference to the given string and assigns it to the LoginHint field. +func (o *Provider) SetLoginHint(v string) { + o.LoginHint = &v +} + +// GetNonce returns the Nonce field value if set, zero value otherwise. +func (o *Provider) GetNonce() string { + if o == nil || o.Nonce == nil { + var ret string + return ret + } + return *o.Nonce +} + +// GetNonceOk returns a tuple with the Nonce field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *Provider) GetNonceOk() (*string, bool) { + if o == nil || o.Nonce == nil { + return nil, false + } + return o.Nonce, true +} + +// HasNonce returns a boolean if a field has been set. +func (o *Provider) HasNonce() bool { + if o != nil && o.Nonce != nil { + return true + } + + return false +} + +// SetNonce gets a reference to the given string and assigns it to the Nonce field. +func (o *Provider) SetNonce(v string) { + o.Nonce = &v +} + +// GetParameters returns the Parameters field value if set, zero value otherwise. +func (o *Provider) GetParameters() map[string]string { + if o == nil || o.Parameters == nil { + var ret map[string]string + return ret + } + return *o.Parameters +} + +// GetParametersOk returns a tuple with the Parameters field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *Provider) GetParametersOk() (*map[string]string, bool) { + if o == nil || o.Parameters == nil { + return nil, false + } + return o.Parameters, true +} + +// HasParameters returns a boolean if a field has been set. +func (o *Provider) HasParameters() bool { + if o != nil && o.Parameters != nil { + return true + } + + return false +} + +// SetParameters gets a reference to the given map[string]string and assigns it to the Parameters field. +func (o *Provider) SetParameters(v map[string]string) { + o.Parameters = &v +} + +func (o Provider) MarshalJSON() ([]byte, error) { + toSerialize := map[string]interface{}{} + if o.ClientId != nil { + toSerialize["client_id"] = o.ClientId + } + if o.ConfigUrl != nil { + toSerialize["config_url"] = o.ConfigUrl + } + if o.DomainHint != nil { + toSerialize["domain_hint"] = o.DomainHint + } + if o.Fields != nil { + toSerialize["fields"] = o.Fields + } + if o.LoginHint != nil { + toSerialize["login_hint"] = o.LoginHint + } + if o.Nonce != nil { + toSerialize["nonce"] = o.Nonce + } + if o.Parameters != nil { + toSerialize["parameters"] = o.Parameters + } + return json.Marshal(toSerialize) +} + +type NullableProvider struct { + value *Provider + isSet bool +} + +func (v NullableProvider) Get() *Provider { + return v.value +} + +func (v *NullableProvider) Set(val *Provider) { + v.value = val + v.isSet = true +} + +func (v NullableProvider) IsSet() bool { + return v.isSet +} + +func (v *NullableProvider) Unset() { + v.value = nil + v.isSet = false +} + +func NewNullableProvider(val *Provider) *NullableProvider { + return &NullableProvider{value: val, isSet: true} +} + +func (v NullableProvider) MarshalJSON() ([]byte, error) { + return json.Marshal(v.value) +} + +func (v *NullableProvider) UnmarshalJSON(src []byte) error { + v.isSet = true + return json.Unmarshal(src, &v.value) +} diff --git a/internal/client-go/model_update_fedcm_flow_body.go b/internal/client-go/model_update_fedcm_flow_body.go new file mode 100644 index 000000000000..2d630d8ece53 --- /dev/null +++ b/internal/client-go/model_update_fedcm_flow_body.go @@ -0,0 +1,175 @@ +/* + * Ory Identities API + * + * This is the API specification for Ory Identities with features such as registration, login, recovery, account verification, profile settings, password reset, identity management, session management, email and sms delivery, and more. + * + * API version: + * Contact: office@ory.sh + */ + +// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT. + +package client + +import ( + "encoding/json" +) + +// UpdateFedcmFlowBody struct for UpdateFedcmFlowBody +type UpdateFedcmFlowBody struct { + // CSRFToken is the anti-CSRF token. + CsrfToken string `json:"csrf_token"` + // Nonce is the nonce that was used in the `navigator.credentials.get` call. If specified, it must match the `nonce` claim in the token. + Nonce *string `json:"nonce,omitempty"` + // Token contains the result of `navigator.credentials.get`. + Token string `json:"token"` +} + +// NewUpdateFedcmFlowBody instantiates a new UpdateFedcmFlowBody object +// This constructor will assign default values to properties that have it defined, +// and makes sure properties required by API are set, but the set of arguments +// will change when the set of required properties is changed +func NewUpdateFedcmFlowBody(csrfToken string, token string) *UpdateFedcmFlowBody { + this := UpdateFedcmFlowBody{} + this.CsrfToken = csrfToken + this.Token = token + return &this +} + +// NewUpdateFedcmFlowBodyWithDefaults instantiates a new UpdateFedcmFlowBody object +// This constructor will only assign default values to properties that have it defined, +// but it doesn't guarantee that properties required by API are set +func NewUpdateFedcmFlowBodyWithDefaults() *UpdateFedcmFlowBody { + this := UpdateFedcmFlowBody{} + return &this +} + +// GetCsrfToken returns the CsrfToken field value +func (o *UpdateFedcmFlowBody) GetCsrfToken() string { + if o == nil { + var ret string + return ret + } + + return o.CsrfToken +} + +// GetCsrfTokenOk returns a tuple with the CsrfToken field value +// and a boolean to check if the value has been set. +func (o *UpdateFedcmFlowBody) GetCsrfTokenOk() (*string, bool) { + if o == nil { + return nil, false + } + return &o.CsrfToken, true +} + +// SetCsrfToken sets field value +func (o *UpdateFedcmFlowBody) SetCsrfToken(v string) { + o.CsrfToken = v +} + +// GetNonce returns the Nonce field value if set, zero value otherwise. +func (o *UpdateFedcmFlowBody) GetNonce() string { + if o == nil || o.Nonce == nil { + var ret string + return ret + } + return *o.Nonce +} + +// GetNonceOk returns a tuple with the Nonce field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *UpdateFedcmFlowBody) GetNonceOk() (*string, bool) { + if o == nil || o.Nonce == nil { + return nil, false + } + return o.Nonce, true +} + +// HasNonce returns a boolean if a field has been set. +func (o *UpdateFedcmFlowBody) HasNonce() bool { + if o != nil && o.Nonce != nil { + return true + } + + return false +} + +// SetNonce gets a reference to the given string and assigns it to the Nonce field. +func (o *UpdateFedcmFlowBody) SetNonce(v string) { + o.Nonce = &v +} + +// GetToken returns the Token field value +func (o *UpdateFedcmFlowBody) GetToken() string { + if o == nil { + var ret string + return ret + } + + return o.Token +} + +// GetTokenOk returns a tuple with the Token field value +// and a boolean to check if the value has been set. +func (o *UpdateFedcmFlowBody) GetTokenOk() (*string, bool) { + if o == nil { + return nil, false + } + return &o.Token, true +} + +// SetToken sets field value +func (o *UpdateFedcmFlowBody) SetToken(v string) { + o.Token = v +} + +func (o UpdateFedcmFlowBody) MarshalJSON() ([]byte, error) { + toSerialize := map[string]interface{}{} + if true { + toSerialize["csrf_token"] = o.CsrfToken + } + if o.Nonce != nil { + toSerialize["nonce"] = o.Nonce + } + if true { + toSerialize["token"] = o.Token + } + return json.Marshal(toSerialize) +} + +type NullableUpdateFedcmFlowBody struct { + value *UpdateFedcmFlowBody + isSet bool +} + +func (v NullableUpdateFedcmFlowBody) Get() *UpdateFedcmFlowBody { + return v.value +} + +func (v *NullableUpdateFedcmFlowBody) Set(val *UpdateFedcmFlowBody) { + v.value = val + v.isSet = true +} + +func (v NullableUpdateFedcmFlowBody) IsSet() bool { + return v.isSet +} + +func (v *NullableUpdateFedcmFlowBody) Unset() { + v.value = nil + v.isSet = false +} + +func NewNullableUpdateFedcmFlowBody(val *UpdateFedcmFlowBody) *NullableUpdateFedcmFlowBody { + return &NullableUpdateFedcmFlowBody{value: val, isSet: true} +} + +func (v NullableUpdateFedcmFlowBody) MarshalJSON() ([]byte, error) { + return json.Marshal(v.value) +} + +func (v *NullableUpdateFedcmFlowBody) UnmarshalJSON(src []byte) error { + v.isSet = true + return json.Unmarshal(src, &v.value) +} diff --git a/internal/httpclient/.openapi-generator/FILES b/internal/httpclient/.openapi-generator/FILES index 118cf9b06463..e5608d3b70a9 100644 --- a/internal/httpclient/.openapi-generator/FILES +++ b/internal/httpclient/.openapi-generator/FILES @@ -24,6 +24,7 @@ docs/ContinueWithVerificationUiFlow.md docs/CourierAPI.md docs/CourierMessageStatus.md docs/CourierMessageType.md +docs/CreateFedcmFlowResponse.md docs/CreateIdentityBody.md docs/CreateRecoveryCodeForIdentityBody.md docs/CreateRecoveryLinkForIdentityBody.md @@ -70,6 +71,7 @@ docs/OAuth2ConsentRequestOpenIDConnectContext.md docs/OAuth2LoginRequest.md docs/PatchIdentitiesBody.md docs/PerformNativeLogoutBody.md +docs/Provider.md docs/RecoveryCodeForIdentity.md docs/RecoveryFlow.md docs/RecoveryFlowState.md @@ -98,6 +100,7 @@ docs/UiNodeMeta.md docs/UiNodeScriptAttributes.md docs/UiNodeTextAttributes.md docs/UiText.md +docs/UpdateFedcmFlowBody.md docs/UpdateIdentityBody.md docs/UpdateLoginFlowBody.md docs/UpdateLoginFlowWithCodeMethod.md @@ -150,6 +153,7 @@ model_continue_with_verification_ui.go model_continue_with_verification_ui_flow.go model_courier_message_status.go model_courier_message_type.go +model_create_fedcm_flow_response.go model_create_identity_body.go model_create_recovery_code_for_identity_body.go model_create_recovery_link_for_identity_body.go @@ -193,6 +197,7 @@ model_o_auth2_consent_request_open_id_connect_context.go model_o_auth2_login_request.go model_patch_identities_body.go model_perform_native_logout_body.go +model_provider.go model_recovery_code_for_identity.go model_recovery_flow.go model_recovery_flow_state.go @@ -221,6 +226,7 @@ model_ui_node_meta.go model_ui_node_script_attributes.go model_ui_node_text_attributes.go model_ui_text.go +model_update_fedcm_flow_body.go model_update_identity_body.go model_update_login_flow_body.go model_update_login_flow_with_code_method.go diff --git a/internal/httpclient/README.md b/internal/httpclient/README.md index 97593523117a..b418e308083f 100644 --- a/internal/httpclient/README.md +++ b/internal/httpclient/README.md @@ -87,6 +87,7 @@ Class | Method | HTTP request | Description *FrontendAPI* | [**CreateBrowserRegistrationFlow**](docs/FrontendAPI.md#createbrowserregistrationflow) | **Get** /self-service/registration/browser | Create Registration Flow for Browsers *FrontendAPI* | [**CreateBrowserSettingsFlow**](docs/FrontendAPI.md#createbrowsersettingsflow) | **Get** /self-service/settings/browser | Create Settings Flow for Browsers *FrontendAPI* | [**CreateBrowserVerificationFlow**](docs/FrontendAPI.md#createbrowserverificationflow) | **Get** /self-service/verification/browser | Create Verification Flow for Browser Clients +*FrontendAPI* | [**CreateFedcmFlow**](docs/FrontendAPI.md#createfedcmflow) | **Get** /self-service/fed-cm/parameters | Get FedCM Parameters *FrontendAPI* | [**CreateNativeLoginFlow**](docs/FrontendAPI.md#createnativeloginflow) | **Get** /self-service/login/api | Create Login Flow for Native Apps *FrontendAPI* | [**CreateNativeRecoveryFlow**](docs/FrontendAPI.md#createnativerecoveryflow) | **Get** /self-service/recovery/api | Create Recovery Flow for Native Apps *FrontendAPI* | [**CreateNativeRegistrationFlow**](docs/FrontendAPI.md#createnativeregistrationflow) | **Get** /self-service/registration/api | Create Registration Flow for Native Apps @@ -105,6 +106,7 @@ Class | Method | HTTP request | Description *FrontendAPI* | [**ListMySessions**](docs/FrontendAPI.md#listmysessions) | **Get** /sessions | Get My Active Sessions *FrontendAPI* | [**PerformNativeLogout**](docs/FrontendAPI.md#performnativelogout) | **Delete** /self-service/logout/api | Perform Logout for Native Apps *FrontendAPI* | [**ToSession**](docs/FrontendAPI.md#tosession) | **Get** /sessions/whoami | Check Who the Current HTTP Session Belongs To +*FrontendAPI* | [**UpdateFedcmFlow**](docs/FrontendAPI.md#updatefedcmflow) | **Post** /self-service/fed-cm/token | Submit a FedCM token *FrontendAPI* | [**UpdateLoginFlow**](docs/FrontendAPI.md#updateloginflow) | **Post** /self-service/login | Submit a Login Flow *FrontendAPI* | [**UpdateLogoutFlow**](docs/FrontendAPI.md#updatelogoutflow) | **Get** /self-service/logout | Update Logout Flow *FrontendAPI* | [**UpdateRecoveryFlow**](docs/FrontendAPI.md#updaterecoveryflow) | **Post** /self-service/recovery | Update Recovery Flow @@ -150,6 +152,7 @@ Class | Method | HTTP request | Description - [ContinueWithVerificationUiFlow](docs/ContinueWithVerificationUiFlow.md) - [CourierMessageStatus](docs/CourierMessageStatus.md) - [CourierMessageType](docs/CourierMessageType.md) + - [CreateFedcmFlowResponse](docs/CreateFedcmFlowResponse.md) - [CreateIdentityBody](docs/CreateIdentityBody.md) - [CreateRecoveryCodeForIdentityBody](docs/CreateRecoveryCodeForIdentityBody.md) - [CreateRecoveryLinkForIdentityBody](docs/CreateRecoveryLinkForIdentityBody.md) @@ -193,6 +196,7 @@ Class | Method | HTTP request | Description - [OAuth2LoginRequest](docs/OAuth2LoginRequest.md) - [PatchIdentitiesBody](docs/PatchIdentitiesBody.md) - [PerformNativeLogoutBody](docs/PerformNativeLogoutBody.md) + - [Provider](docs/Provider.md) - [RecoveryCodeForIdentity](docs/RecoveryCodeForIdentity.md) - [RecoveryFlow](docs/RecoveryFlow.md) - [RecoveryFlowState](docs/RecoveryFlowState.md) @@ -221,6 +225,7 @@ Class | Method | HTTP request | Description - [UiNodeScriptAttributes](docs/UiNodeScriptAttributes.md) - [UiNodeTextAttributes](docs/UiNodeTextAttributes.md) - [UiText](docs/UiText.md) + - [UpdateFedcmFlowBody](docs/UpdateFedcmFlowBody.md) - [UpdateIdentityBody](docs/UpdateIdentityBody.md) - [UpdateLoginFlowBody](docs/UpdateLoginFlowBody.md) - [UpdateLoginFlowWithCodeMethod](docs/UpdateLoginFlowWithCodeMethod.md) diff --git a/internal/httpclient/api_frontend.go b/internal/httpclient/api_frontend.go index 97266e9c4c94..cd243b065b4b 100644 --- a/internal/httpclient/api_frontend.go +++ b/internal/httpclient/api_frontend.go @@ -201,6 +201,20 @@ type FrontendAPI interface { */ CreateBrowserVerificationFlowExecute(r FrontendAPIApiCreateBrowserVerificationFlowRequest) (*VerificationFlow, *http.Response, error) + /* + * CreateFedcmFlow Get FedCM Parameters + * This endpoint returns a list of all available FedCM providers. It is only supported on the Ory Network. + * @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). + * @return FrontendAPIApiCreateFedcmFlowRequest + */ + CreateFedcmFlow(ctx context.Context) FrontendAPIApiCreateFedcmFlowRequest + + /* + * CreateFedcmFlowExecute executes the request + * @return CreateFedcmFlowResponse + */ + CreateFedcmFlowExecute(r FrontendAPIApiCreateFedcmFlowRequest) (*CreateFedcmFlowResponse, *http.Response, error) + /* * CreateNativeLoginFlow Create Login Flow for Native Apps * This endpoint initiates a login flow for native apps that do not use a browser, such as mobile devices, smart TVs, and so on. @@ -709,6 +723,23 @@ type FrontendAPI interface { */ ToSessionExecute(r FrontendAPIApiToSessionRequest) (*Session, *http.Response, error) + /* + * UpdateFedcmFlow Submit a FedCM token + * Use this endpoint to submit a token from a FedCM provider through + `navigator.credentials.get` and log the user in. The parameters from + `navigator.credentials.get` must have come from `GET + self-service/fed-cm/parameters`. + * @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). + * @return FrontendAPIApiUpdateFedcmFlowRequest + */ + UpdateFedcmFlow(ctx context.Context) FrontendAPIApiUpdateFedcmFlowRequest + + /* + * UpdateFedcmFlowExecute executes the request + * @return SuccessfulNativeLogin + */ + UpdateFedcmFlowExecute(r FrontendAPIApiUpdateFedcmFlowRequest) (*SuccessfulNativeLogin, *http.Response, error) + /* * UpdateLoginFlow Submit a Login Flow * Use this endpoint to complete a login flow. This endpoint @@ -1890,6 +1921,124 @@ func (a *FrontendAPIService) CreateBrowserVerificationFlowExecute(r FrontendAPIA return localVarReturnValue, localVarHTTPResponse, nil } +type FrontendAPIApiCreateFedcmFlowRequest struct { + ctx context.Context + ApiService FrontendAPI +} + +func (r FrontendAPIApiCreateFedcmFlowRequest) Execute() (*CreateFedcmFlowResponse, *http.Response, error) { + return r.ApiService.CreateFedcmFlowExecute(r) +} + +/* + * CreateFedcmFlow Get FedCM Parameters + * This endpoint returns a list of all available FedCM providers. It is only supported on the Ory Network. + * @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). + * @return FrontendAPIApiCreateFedcmFlowRequest + */ +func (a *FrontendAPIService) CreateFedcmFlow(ctx context.Context) FrontendAPIApiCreateFedcmFlowRequest { + return FrontendAPIApiCreateFedcmFlowRequest{ + ApiService: a, + ctx: ctx, + } +} + +/* + * Execute executes the request + * @return CreateFedcmFlowResponse + */ +func (a *FrontendAPIService) CreateFedcmFlowExecute(r FrontendAPIApiCreateFedcmFlowRequest) (*CreateFedcmFlowResponse, *http.Response, error) { + var ( + localVarHTTPMethod = http.MethodGet + localVarPostBody interface{} + localVarFormFileName string + localVarFileName string + localVarFileBytes []byte + localVarReturnValue *CreateFedcmFlowResponse + ) + + localBasePath, err := a.client.cfg.ServerURLWithContext(r.ctx, "FrontendAPIService.CreateFedcmFlow") + if err != nil { + return localVarReturnValue, nil, &GenericOpenAPIError{error: err.Error()} + } + + localVarPath := localBasePath + "/self-service/fed-cm/parameters" + + localVarHeaderParams := make(map[string]string) + localVarQueryParams := url.Values{} + localVarFormParams := url.Values{} + + // to determine the Content-Type header + localVarHTTPContentTypes := []string{} + + // set Content-Type header + localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes) + if localVarHTTPContentType != "" { + localVarHeaderParams["Content-Type"] = localVarHTTPContentType + } + + // to determine the Accept header + localVarHTTPHeaderAccepts := []string{"application/json"} + + // set Accept header + localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts) + if localVarHTTPHeaderAccept != "" { + localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept + } + req, err := a.client.prepareRequest(r.ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, localVarFormFileName, localVarFileName, localVarFileBytes) + if err != nil { + return localVarReturnValue, nil, err + } + + localVarHTTPResponse, err := a.client.callAPI(req) + if err != nil || localVarHTTPResponse == nil { + return localVarReturnValue, localVarHTTPResponse, err + } + + localVarBody, err := io.ReadAll(io.LimitReader(localVarHTTPResponse.Body, 1024*1024)) + localVarHTTPResponse.Body.Close() + localVarHTTPResponse.Body = io.NopCloser(bytes.NewBuffer(localVarBody)) + if err != nil { + return localVarReturnValue, localVarHTTPResponse, err + } + + if localVarHTTPResponse.StatusCode >= 300 { + newErr := &GenericOpenAPIError{ + body: localVarBody, + error: localVarHTTPResponse.Status, + } + if localVarHTTPResponse.StatusCode == 400 { + var v ErrorGeneric + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.model = v + return localVarReturnValue, localVarHTTPResponse, newErr + } + var v ErrorGeneric + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.model = v + return localVarReturnValue, localVarHTTPResponse, newErr + } + + err = a.client.decode(&localVarReturnValue, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr := &GenericOpenAPIError{ + body: localVarBody, + error: err.Error(), + } + return localVarReturnValue, localVarHTTPResponse, newErr + } + + return localVarReturnValue, localVarHTTPResponse, nil +} + type FrontendAPIApiCreateNativeLoginFlowRequest struct { ctx context.Context ApiService FrontendAPI @@ -4751,6 +4900,159 @@ func (a *FrontendAPIService) ToSessionExecute(r FrontendAPIApiToSessionRequest) return localVarReturnValue, localVarHTTPResponse, nil } +type FrontendAPIApiUpdateFedcmFlowRequest struct { + ctx context.Context + ApiService FrontendAPI + updateFedcmFlowBody *UpdateFedcmFlowBody +} + +func (r FrontendAPIApiUpdateFedcmFlowRequest) UpdateFedcmFlowBody(updateFedcmFlowBody UpdateFedcmFlowBody) FrontendAPIApiUpdateFedcmFlowRequest { + r.updateFedcmFlowBody = &updateFedcmFlowBody + return r +} + +func (r FrontendAPIApiUpdateFedcmFlowRequest) Execute() (*SuccessfulNativeLogin, *http.Response, error) { + return r.ApiService.UpdateFedcmFlowExecute(r) +} + +/* + - UpdateFedcmFlow Submit a FedCM token + - Use this endpoint to submit a token from a FedCM provider through + +`navigator.credentials.get` and log the user in. The parameters from +`navigator.credentials.get` must have come from `GET +self-service/fed-cm/parameters`. + - @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). + - @return FrontendAPIApiUpdateFedcmFlowRequest +*/ +func (a *FrontendAPIService) UpdateFedcmFlow(ctx context.Context) FrontendAPIApiUpdateFedcmFlowRequest { + return FrontendAPIApiUpdateFedcmFlowRequest{ + ApiService: a, + ctx: ctx, + } +} + +/* + * Execute executes the request + * @return SuccessfulNativeLogin + */ +func (a *FrontendAPIService) UpdateFedcmFlowExecute(r FrontendAPIApiUpdateFedcmFlowRequest) (*SuccessfulNativeLogin, *http.Response, error) { + var ( + localVarHTTPMethod = http.MethodPost + localVarPostBody interface{} + localVarFormFileName string + localVarFileName string + localVarFileBytes []byte + localVarReturnValue *SuccessfulNativeLogin + ) + + localBasePath, err := a.client.cfg.ServerURLWithContext(r.ctx, "FrontendAPIService.UpdateFedcmFlow") + if err != nil { + return localVarReturnValue, nil, &GenericOpenAPIError{error: err.Error()} + } + + localVarPath := localBasePath + "/self-service/fed-cm/token" + + localVarHeaderParams := make(map[string]string) + localVarQueryParams := url.Values{} + localVarFormParams := url.Values{} + if r.updateFedcmFlowBody == nil { + return localVarReturnValue, nil, reportError("updateFedcmFlowBody is required and must be specified") + } + + // to determine the Content-Type header + localVarHTTPContentTypes := []string{"application/json", "application/x-www-form-urlencoded"} + + // set Content-Type header + localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes) + if localVarHTTPContentType != "" { + localVarHeaderParams["Content-Type"] = localVarHTTPContentType + } + + // to determine the Accept header + localVarHTTPHeaderAccepts := []string{"application/json"} + + // set Accept header + localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts) + if localVarHTTPHeaderAccept != "" { + localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept + } + // body params + localVarPostBody = r.updateFedcmFlowBody + req, err := a.client.prepareRequest(r.ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, localVarFormFileName, localVarFileName, localVarFileBytes) + if err != nil { + return localVarReturnValue, nil, err + } + + localVarHTTPResponse, err := a.client.callAPI(req) + if err != nil || localVarHTTPResponse == nil { + return localVarReturnValue, localVarHTTPResponse, err + } + + localVarBody, err := io.ReadAll(io.LimitReader(localVarHTTPResponse.Body, 1024*1024)) + localVarHTTPResponse.Body.Close() + localVarHTTPResponse.Body = io.NopCloser(bytes.NewBuffer(localVarBody)) + if err != nil { + return localVarReturnValue, localVarHTTPResponse, err + } + + if localVarHTTPResponse.StatusCode >= 300 { + newErr := &GenericOpenAPIError{ + body: localVarBody, + error: localVarHTTPResponse.Status, + } + if localVarHTTPResponse.StatusCode == 400 { + var v LoginFlow + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.model = v + return localVarReturnValue, localVarHTTPResponse, newErr + } + if localVarHTTPResponse.StatusCode == 410 { + var v ErrorGeneric + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.model = v + return localVarReturnValue, localVarHTTPResponse, newErr + } + if localVarHTTPResponse.StatusCode == 422 { + var v ErrorBrowserLocationChangeRequired + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.model = v + return localVarReturnValue, localVarHTTPResponse, newErr + } + var v ErrorGeneric + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.model = v + return localVarReturnValue, localVarHTTPResponse, newErr + } + + err = a.client.decode(&localVarReturnValue, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr := &GenericOpenAPIError{ + body: localVarBody, + error: err.Error(), + } + return localVarReturnValue, localVarHTTPResponse, newErr + } + + return localVarReturnValue, localVarHTTPResponse, nil +} + type FrontendAPIApiUpdateLoginFlowRequest struct { ctx context.Context ApiService FrontendAPI diff --git a/internal/httpclient/model_create_fedcm_flow_response.go b/internal/httpclient/model_create_fedcm_flow_response.go new file mode 100644 index 000000000000..fdca32672c63 --- /dev/null +++ b/internal/httpclient/model_create_fedcm_flow_response.go @@ -0,0 +1,150 @@ +/* + * Ory Identities API + * + * This is the API specification for Ory Identities with features such as registration, login, recovery, account verification, profile settings, password reset, identity management, session management, email and sms delivery, and more. + * + * API version: + * Contact: office@ory.sh + */ + +// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT. + +package client + +import ( + "encoding/json" +) + +// CreateFedcmFlowResponse Contains a list of all available FedCM providers. +type CreateFedcmFlowResponse struct { + CsrfToken *string `json:"csrf_token,omitempty"` + Providers []Provider `json:"providers,omitempty"` +} + +// NewCreateFedcmFlowResponse instantiates a new CreateFedcmFlowResponse object +// This constructor will assign default values to properties that have it defined, +// and makes sure properties required by API are set, but the set of arguments +// will change when the set of required properties is changed +func NewCreateFedcmFlowResponse() *CreateFedcmFlowResponse { + this := CreateFedcmFlowResponse{} + return &this +} + +// NewCreateFedcmFlowResponseWithDefaults instantiates a new CreateFedcmFlowResponse object +// This constructor will only assign default values to properties that have it defined, +// but it doesn't guarantee that properties required by API are set +func NewCreateFedcmFlowResponseWithDefaults() *CreateFedcmFlowResponse { + this := CreateFedcmFlowResponse{} + return &this +} + +// GetCsrfToken returns the CsrfToken field value if set, zero value otherwise. +func (o *CreateFedcmFlowResponse) GetCsrfToken() string { + if o == nil || o.CsrfToken == nil { + var ret string + return ret + } + return *o.CsrfToken +} + +// GetCsrfTokenOk returns a tuple with the CsrfToken field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *CreateFedcmFlowResponse) GetCsrfTokenOk() (*string, bool) { + if o == nil || o.CsrfToken == nil { + return nil, false + } + return o.CsrfToken, true +} + +// HasCsrfToken returns a boolean if a field has been set. +func (o *CreateFedcmFlowResponse) HasCsrfToken() bool { + if o != nil && o.CsrfToken != nil { + return true + } + + return false +} + +// SetCsrfToken gets a reference to the given string and assigns it to the CsrfToken field. +func (o *CreateFedcmFlowResponse) SetCsrfToken(v string) { + o.CsrfToken = &v +} + +// GetProviders returns the Providers field value if set, zero value otherwise. +func (o *CreateFedcmFlowResponse) GetProviders() []Provider { + if o == nil || o.Providers == nil { + var ret []Provider + return ret + } + return o.Providers +} + +// GetProvidersOk returns a tuple with the Providers field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *CreateFedcmFlowResponse) GetProvidersOk() ([]Provider, bool) { + if o == nil || o.Providers == nil { + return nil, false + } + return o.Providers, true +} + +// HasProviders returns a boolean if a field has been set. +func (o *CreateFedcmFlowResponse) HasProviders() bool { + if o != nil && o.Providers != nil { + return true + } + + return false +} + +// SetProviders gets a reference to the given []Provider and assigns it to the Providers field. +func (o *CreateFedcmFlowResponse) SetProviders(v []Provider) { + o.Providers = v +} + +func (o CreateFedcmFlowResponse) MarshalJSON() ([]byte, error) { + toSerialize := map[string]interface{}{} + if o.CsrfToken != nil { + toSerialize["csrf_token"] = o.CsrfToken + } + if o.Providers != nil { + toSerialize["providers"] = o.Providers + } + return json.Marshal(toSerialize) +} + +type NullableCreateFedcmFlowResponse struct { + value *CreateFedcmFlowResponse + isSet bool +} + +func (v NullableCreateFedcmFlowResponse) Get() *CreateFedcmFlowResponse { + return v.value +} + +func (v *NullableCreateFedcmFlowResponse) Set(val *CreateFedcmFlowResponse) { + v.value = val + v.isSet = true +} + +func (v NullableCreateFedcmFlowResponse) IsSet() bool { + return v.isSet +} + +func (v *NullableCreateFedcmFlowResponse) Unset() { + v.value = nil + v.isSet = false +} + +func NewNullableCreateFedcmFlowResponse(val *CreateFedcmFlowResponse) *NullableCreateFedcmFlowResponse { + return &NullableCreateFedcmFlowResponse{value: val, isSet: true} +} + +func (v NullableCreateFedcmFlowResponse) MarshalJSON() ([]byte, error) { + return json.Marshal(v.value) +} + +func (v *NullableCreateFedcmFlowResponse) UnmarshalJSON(src []byte) error { + v.isSet = true + return json.Unmarshal(src, &v.value) +} diff --git a/internal/httpclient/model_provider.go b/internal/httpclient/model_provider.go new file mode 100644 index 000000000000..2c9a79590e0e --- /dev/null +++ b/internal/httpclient/model_provider.go @@ -0,0 +1,337 @@ +/* + * Ory Identities API + * + * This is the API specification for Ory Identities with features such as registration, login, recovery, account verification, profile settings, password reset, identity management, session management, email and sms delivery, and more. + * + * API version: + * Contact: office@ory.sh + */ + +// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT. + +package client + +import ( + "encoding/json" +) + +// Provider struct for Provider +type Provider struct { + // The RP's client identifier, issued by the IdP. + ClientId *string `json:"client_id,omitempty"` + // A full path of the IdP config file. + ConfigUrl *string `json:"config_url,omitempty"` + // By specifying one of domain_hints values provided by the accounts endpoints, the FedCM dialog selectively shows the specified account. + DomainHint *string `json:"domain_hint,omitempty"` + // Array of strings that specifies the user information (\"name\", \" email\", \"picture\") that RP needs IdP to share with them. Note: Field API is supported by Chrome 132 and later. + Fields []string `json:"fields,omitempty"` + // By specifying one of login_hints values provided by the accounts endpoints, the FedCM dialog selectively shows the specified account. + LoginHint *string `json:"login_hint,omitempty"` + // A random string to ensure the response is issued for this specific request. Prevents replay attacks. + Nonce *string `json:"nonce,omitempty"` + // Custom object that allows to specify additional key-value parameters: scope: A string value containing additional permissions that RP needs to request, for example \" drive.readonly calendar.readonly\" nonce: A random string to ensure the response is issued for this specific request. Prevents replay attacks. Other custom key-value parameters. Note: parameters is supported from Chrome 132. + Parameters *map[string]string `json:"parameters,omitempty"` +} + +// NewProvider instantiates a new Provider object +// This constructor will assign default values to properties that have it defined, +// and makes sure properties required by API are set, but the set of arguments +// will change when the set of required properties is changed +func NewProvider() *Provider { + this := Provider{} + return &this +} + +// NewProviderWithDefaults instantiates a new Provider object +// This constructor will only assign default values to properties that have it defined, +// but it doesn't guarantee that properties required by API are set +func NewProviderWithDefaults() *Provider { + this := Provider{} + return &this +} + +// GetClientId returns the ClientId field value if set, zero value otherwise. +func (o *Provider) GetClientId() string { + if o == nil || o.ClientId == nil { + var ret string + return ret + } + return *o.ClientId +} + +// GetClientIdOk returns a tuple with the ClientId field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *Provider) GetClientIdOk() (*string, bool) { + if o == nil || o.ClientId == nil { + return nil, false + } + return o.ClientId, true +} + +// HasClientId returns a boolean if a field has been set. +func (o *Provider) HasClientId() bool { + if o != nil && o.ClientId != nil { + return true + } + + return false +} + +// SetClientId gets a reference to the given string and assigns it to the ClientId field. +func (o *Provider) SetClientId(v string) { + o.ClientId = &v +} + +// GetConfigUrl returns the ConfigUrl field value if set, zero value otherwise. +func (o *Provider) GetConfigUrl() string { + if o == nil || o.ConfigUrl == nil { + var ret string + return ret + } + return *o.ConfigUrl +} + +// GetConfigUrlOk returns a tuple with the ConfigUrl field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *Provider) GetConfigUrlOk() (*string, bool) { + if o == nil || o.ConfigUrl == nil { + return nil, false + } + return o.ConfigUrl, true +} + +// HasConfigUrl returns a boolean if a field has been set. +func (o *Provider) HasConfigUrl() bool { + if o != nil && o.ConfigUrl != nil { + return true + } + + return false +} + +// SetConfigUrl gets a reference to the given string and assigns it to the ConfigUrl field. +func (o *Provider) SetConfigUrl(v string) { + o.ConfigUrl = &v +} + +// GetDomainHint returns the DomainHint field value if set, zero value otherwise. +func (o *Provider) GetDomainHint() string { + if o == nil || o.DomainHint == nil { + var ret string + return ret + } + return *o.DomainHint +} + +// GetDomainHintOk returns a tuple with the DomainHint field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *Provider) GetDomainHintOk() (*string, bool) { + if o == nil || o.DomainHint == nil { + return nil, false + } + return o.DomainHint, true +} + +// HasDomainHint returns a boolean if a field has been set. +func (o *Provider) HasDomainHint() bool { + if o != nil && o.DomainHint != nil { + return true + } + + return false +} + +// SetDomainHint gets a reference to the given string and assigns it to the DomainHint field. +func (o *Provider) SetDomainHint(v string) { + o.DomainHint = &v +} + +// GetFields returns the Fields field value if set, zero value otherwise. +func (o *Provider) GetFields() []string { + if o == nil || o.Fields == nil { + var ret []string + return ret + } + return o.Fields +} + +// GetFieldsOk returns a tuple with the Fields field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *Provider) GetFieldsOk() ([]string, bool) { + if o == nil || o.Fields == nil { + return nil, false + } + return o.Fields, true +} + +// HasFields returns a boolean if a field has been set. +func (o *Provider) HasFields() bool { + if o != nil && o.Fields != nil { + return true + } + + return false +} + +// SetFields gets a reference to the given []string and assigns it to the Fields field. +func (o *Provider) SetFields(v []string) { + o.Fields = v +} + +// GetLoginHint returns the LoginHint field value if set, zero value otherwise. +func (o *Provider) GetLoginHint() string { + if o == nil || o.LoginHint == nil { + var ret string + return ret + } + return *o.LoginHint +} + +// GetLoginHintOk returns a tuple with the LoginHint field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *Provider) GetLoginHintOk() (*string, bool) { + if o == nil || o.LoginHint == nil { + return nil, false + } + return o.LoginHint, true +} + +// HasLoginHint returns a boolean if a field has been set. +func (o *Provider) HasLoginHint() bool { + if o != nil && o.LoginHint != nil { + return true + } + + return false +} + +// SetLoginHint gets a reference to the given string and assigns it to the LoginHint field. +func (o *Provider) SetLoginHint(v string) { + o.LoginHint = &v +} + +// GetNonce returns the Nonce field value if set, zero value otherwise. +func (o *Provider) GetNonce() string { + if o == nil || o.Nonce == nil { + var ret string + return ret + } + return *o.Nonce +} + +// GetNonceOk returns a tuple with the Nonce field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *Provider) GetNonceOk() (*string, bool) { + if o == nil || o.Nonce == nil { + return nil, false + } + return o.Nonce, true +} + +// HasNonce returns a boolean if a field has been set. +func (o *Provider) HasNonce() bool { + if o != nil && o.Nonce != nil { + return true + } + + return false +} + +// SetNonce gets a reference to the given string and assigns it to the Nonce field. +func (o *Provider) SetNonce(v string) { + o.Nonce = &v +} + +// GetParameters returns the Parameters field value if set, zero value otherwise. +func (o *Provider) GetParameters() map[string]string { + if o == nil || o.Parameters == nil { + var ret map[string]string + return ret + } + return *o.Parameters +} + +// GetParametersOk returns a tuple with the Parameters field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *Provider) GetParametersOk() (*map[string]string, bool) { + if o == nil || o.Parameters == nil { + return nil, false + } + return o.Parameters, true +} + +// HasParameters returns a boolean if a field has been set. +func (o *Provider) HasParameters() bool { + if o != nil && o.Parameters != nil { + return true + } + + return false +} + +// SetParameters gets a reference to the given map[string]string and assigns it to the Parameters field. +func (o *Provider) SetParameters(v map[string]string) { + o.Parameters = &v +} + +func (o Provider) MarshalJSON() ([]byte, error) { + toSerialize := map[string]interface{}{} + if o.ClientId != nil { + toSerialize["client_id"] = o.ClientId + } + if o.ConfigUrl != nil { + toSerialize["config_url"] = o.ConfigUrl + } + if o.DomainHint != nil { + toSerialize["domain_hint"] = o.DomainHint + } + if o.Fields != nil { + toSerialize["fields"] = o.Fields + } + if o.LoginHint != nil { + toSerialize["login_hint"] = o.LoginHint + } + if o.Nonce != nil { + toSerialize["nonce"] = o.Nonce + } + if o.Parameters != nil { + toSerialize["parameters"] = o.Parameters + } + return json.Marshal(toSerialize) +} + +type NullableProvider struct { + value *Provider + isSet bool +} + +func (v NullableProvider) Get() *Provider { + return v.value +} + +func (v *NullableProvider) Set(val *Provider) { + v.value = val + v.isSet = true +} + +func (v NullableProvider) IsSet() bool { + return v.isSet +} + +func (v *NullableProvider) Unset() { + v.value = nil + v.isSet = false +} + +func NewNullableProvider(val *Provider) *NullableProvider { + return &NullableProvider{value: val, isSet: true} +} + +func (v NullableProvider) MarshalJSON() ([]byte, error) { + return json.Marshal(v.value) +} + +func (v *NullableProvider) UnmarshalJSON(src []byte) error { + v.isSet = true + return json.Unmarshal(src, &v.value) +} diff --git a/internal/httpclient/model_update_fedcm_flow_body.go b/internal/httpclient/model_update_fedcm_flow_body.go new file mode 100644 index 000000000000..2d630d8ece53 --- /dev/null +++ b/internal/httpclient/model_update_fedcm_flow_body.go @@ -0,0 +1,175 @@ +/* + * Ory Identities API + * + * This is the API specification for Ory Identities with features such as registration, login, recovery, account verification, profile settings, password reset, identity management, session management, email and sms delivery, and more. + * + * API version: + * Contact: office@ory.sh + */ + +// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT. + +package client + +import ( + "encoding/json" +) + +// UpdateFedcmFlowBody struct for UpdateFedcmFlowBody +type UpdateFedcmFlowBody struct { + // CSRFToken is the anti-CSRF token. + CsrfToken string `json:"csrf_token"` + // Nonce is the nonce that was used in the `navigator.credentials.get` call. If specified, it must match the `nonce` claim in the token. + Nonce *string `json:"nonce,omitempty"` + // Token contains the result of `navigator.credentials.get`. + Token string `json:"token"` +} + +// NewUpdateFedcmFlowBody instantiates a new UpdateFedcmFlowBody object +// This constructor will assign default values to properties that have it defined, +// and makes sure properties required by API are set, but the set of arguments +// will change when the set of required properties is changed +func NewUpdateFedcmFlowBody(csrfToken string, token string) *UpdateFedcmFlowBody { + this := UpdateFedcmFlowBody{} + this.CsrfToken = csrfToken + this.Token = token + return &this +} + +// NewUpdateFedcmFlowBodyWithDefaults instantiates a new UpdateFedcmFlowBody object +// This constructor will only assign default values to properties that have it defined, +// but it doesn't guarantee that properties required by API are set +func NewUpdateFedcmFlowBodyWithDefaults() *UpdateFedcmFlowBody { + this := UpdateFedcmFlowBody{} + return &this +} + +// GetCsrfToken returns the CsrfToken field value +func (o *UpdateFedcmFlowBody) GetCsrfToken() string { + if o == nil { + var ret string + return ret + } + + return o.CsrfToken +} + +// GetCsrfTokenOk returns a tuple with the CsrfToken field value +// and a boolean to check if the value has been set. +func (o *UpdateFedcmFlowBody) GetCsrfTokenOk() (*string, bool) { + if o == nil { + return nil, false + } + return &o.CsrfToken, true +} + +// SetCsrfToken sets field value +func (o *UpdateFedcmFlowBody) SetCsrfToken(v string) { + o.CsrfToken = v +} + +// GetNonce returns the Nonce field value if set, zero value otherwise. +func (o *UpdateFedcmFlowBody) GetNonce() string { + if o == nil || o.Nonce == nil { + var ret string + return ret + } + return *o.Nonce +} + +// GetNonceOk returns a tuple with the Nonce field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *UpdateFedcmFlowBody) GetNonceOk() (*string, bool) { + if o == nil || o.Nonce == nil { + return nil, false + } + return o.Nonce, true +} + +// HasNonce returns a boolean if a field has been set. +func (o *UpdateFedcmFlowBody) HasNonce() bool { + if o != nil && o.Nonce != nil { + return true + } + + return false +} + +// SetNonce gets a reference to the given string and assigns it to the Nonce field. +func (o *UpdateFedcmFlowBody) SetNonce(v string) { + o.Nonce = &v +} + +// GetToken returns the Token field value +func (o *UpdateFedcmFlowBody) GetToken() string { + if o == nil { + var ret string + return ret + } + + return o.Token +} + +// GetTokenOk returns a tuple with the Token field value +// and a boolean to check if the value has been set. +func (o *UpdateFedcmFlowBody) GetTokenOk() (*string, bool) { + if o == nil { + return nil, false + } + return &o.Token, true +} + +// SetToken sets field value +func (o *UpdateFedcmFlowBody) SetToken(v string) { + o.Token = v +} + +func (o UpdateFedcmFlowBody) MarshalJSON() ([]byte, error) { + toSerialize := map[string]interface{}{} + if true { + toSerialize["csrf_token"] = o.CsrfToken + } + if o.Nonce != nil { + toSerialize["nonce"] = o.Nonce + } + if true { + toSerialize["token"] = o.Token + } + return json.Marshal(toSerialize) +} + +type NullableUpdateFedcmFlowBody struct { + value *UpdateFedcmFlowBody + isSet bool +} + +func (v NullableUpdateFedcmFlowBody) Get() *UpdateFedcmFlowBody { + return v.value +} + +func (v *NullableUpdateFedcmFlowBody) Set(val *UpdateFedcmFlowBody) { + v.value = val + v.isSet = true +} + +func (v NullableUpdateFedcmFlowBody) IsSet() bool { + return v.isSet +} + +func (v *NullableUpdateFedcmFlowBody) Unset() { + v.value = nil + v.isSet = false +} + +func NewNullableUpdateFedcmFlowBody(val *UpdateFedcmFlowBody) *NullableUpdateFedcmFlowBody { + return &NullableUpdateFedcmFlowBody{value: val, isSet: true} +} + +func (v NullableUpdateFedcmFlowBody) MarshalJSON() ([]byte, error) { + return json.Marshal(v.value) +} + +func (v *NullableUpdateFedcmFlowBody) UnmarshalJSON(src []byte) error { + v.isSet = true + return json.Unmarshal(src, &v.value) +} diff --git a/selfservice/strategy/oidc/fedcm/definitions.go b/selfservice/strategy/oidc/fedcm/definitions.go new file mode 100644 index 000000000000..c8665b3e614d --- /dev/null +++ b/selfservice/strategy/oidc/fedcm/definitions.go @@ -0,0 +1,127 @@ +// Copyright © 2025 Ory Corp +// SPDX-License-Identifier: Apache-2.0 + +package fedcm + +type Provider struct { + // A full path of the IdP config file. + ConfigURL string `json:"config_url"` + + // The RP's client identifier, issued by the IdP. + ClientID string `json:"client_id"` + + // A random string to ensure the response is issued for this specific request. + // Prevents replay attacks. + Nonce string `json:"nonce"` + + // By specifying one of login_hints values provided by the accounts endpoints, + // the FedCM dialog selectively shows the specified account. + LoginHint string `json:"login_hint,omitempty"` + + // By specifying one of domain_hints values provided by the accounts endpoints, + // the FedCM dialog selectively shows the specified account. + DomainHint string `json:"domain_hint,omitempty"` + + // Array of strings that specifies the user information ("name", " email", + // "picture") that RP needs IdP to share with them. + // + // Note: Field API is supported by Chrome 132 and later. + Fields []string `json:"fields,omitempty"` + + // Custom object that allows to specify additional key-value parameters: + // - scope: A string value containing additional permissions that RP needs to + // request, for example " drive.readonly calendar.readonly" + // - nonce: A random string to ensure the response is issued for this specific + // request. Prevents replay attacks. + // + // Other custom key-value parameters. + // + // Note: parameters is supported from Chrome 132. + Parameters map[string]string `json:"parameters,omitempty"` +} + +// CreateFedcmFlowResponse +// +// Contains a list of all available FedCM providers. +// +// swagger:model createFedcmFlowResponse +type CreateFedcmFlowResponse struct { + Providers []Provider `json:"providers"` + CSRFToken string `json:"csrf_token"` +} + +// swagger:route GET /self-service/fed-cm/parameters frontend createFedcmFlow +// +// # Get FedCM Parameters +// +// This endpoint returns a list of all available FedCM providers. It is only supported on the Ory Network. +// +// Consumes: +// - application/json +// +// Produces: +// - application/json +// +// Schemes: http, https +// +// Responses: +// 200: createFedcmFlowResponse +// 400: errorGeneric +// default: errorGeneric + +type UpdateFedcmFlowBody struct { + // Token contains the result of `navigator.credentials.get`. + // + // required: true + Token string `json:"token"` + + // Nonce is the nonce that was used in the `navigator.credentials.get` call. If + // specified, it must match the `nonce` claim in the token. + // + // required: false + Nonce string `json:"nonce"` + + // CSRFToken is the anti-CSRF token. + // + // required: true + CSRFToken string `json:"csrf_token"` +} + +// swagger:parameters updateFedcmFlow +// +//nolint:deadcode,unused +//lint:ignore U1000 Used to generate Swagger and OpenAPI definitions +type updateFedcmFlow struct { + // in: body + // required: true + Body UpdateFedcmFlowBody +} + +// swagger:route POST /self-service/fed-cm/token frontend updateFedcmFlow +// +// # Submit a FedCM token +// +// Use this endpoint to submit a token from a FedCM provider through +// `navigator.credentials.get` and log the user in. The parameters from +// `navigator.credentials.get` must have come from `GET +// /self-service/fed-cm/parameters`. +// +// Consumes: +// - application/json +// - application/x-www-form-urlencoded +// +// Produces: +// - application/json +// +// Schemes: http, https +// +// Header: +// - Set-Cookie +// +// Responses: +// 200: successfulNativeLogin +// 303: emptyResponse +// 400: loginFlow +// 410: errorGeneric +// 422: errorBrowserLocationChangeRequired +// default: errorGeneric diff --git a/selfservice/strategy/oidc/provider_apple.go b/selfservice/strategy/oidc/provider_apple.go index 7706eda9d9af..bc5523b22bbd 100644 --- a/selfservice/strategy/oidc/provider_apple.go +++ b/selfservice/strategy/oidc/provider_apple.go @@ -156,13 +156,13 @@ func (a *ProviderApple) DecodeQuery(query url.Values, claims *Claims) { var _ IDTokenVerifier = new(ProviderApple) -const issuerUrlApple = "https://appleid.apple.com" +const issuerURLApple = "https://appleid.apple.com" func (a *ProviderApple) Verify(ctx context.Context, rawIDToken string) (*Claims, error) { keySet := oidc.NewRemoteKeySet(ctx, a.JWKSUrl) - ctx = oidc.ClientContext(ctx, a.reg.HTTPClient(ctx).HTTPClient) - return verifyToken(ctx, keySet, a.config, rawIDToken, issuerUrlApple) + + return verifyToken(ctx, keySet, a.config, rawIDToken, issuerURLApple) } var _ NonceValidationSkipper = new(ProviderApple) diff --git a/selfservice/strategy/oidc/provider_config.go b/selfservice/strategy/oidc/provider_config.go index 92b16fdf5f42..c9de47e3d799 100644 --- a/selfservice/strategy/oidc/provider_config.go +++ b/selfservice/strategy/oidc/provider_config.go @@ -128,6 +128,14 @@ type Configuration struct { // Instead of /self-service/methods/oidc/callback/, you must use /self-service/methods/oidc/callback // (Note the missing path segment and no trailing slash). PKCE string `json:"pkce"` + + // FedCMConfigURL is the URL to the FedCM IdP configuration file. + // This is only effective in the Ory Network. + FedCMConfigURL string `json:"fedcm_config_url"` + + // NetIDTokenOriginHeader contains the orgin header to be used when exchanging a + // NetID FedCM token for an ID token. + NetIDTokenOriginHeader string `json:"net_id_token_origin_header"` } func (p Configuration) Redir(public *url.URL) string { @@ -178,6 +186,7 @@ var supportedProviders = map[string]func(config *Configuration, reg Dependencies "lark": NewProviderLark, "x": NewProviderX, "jackson": NewProviderJackson, + "fedcm-test": NewProviderTestFedcm, } func (c ConfigurationCollection) Provider(id string, reg Dependencies) (Provider, error) { diff --git a/selfservice/strategy/oidc/provider_google.go b/selfservice/strategy/oidc/provider_google.go index 4e009b318380..b1f758bd726b 100644 --- a/selfservice/strategy/oidc/provider_google.go +++ b/selfservice/strategy/oidc/provider_google.go @@ -78,6 +78,7 @@ const issuerUrlGoogle = "https://accounts.google.com" func (p *ProviderGoogle) Verify(ctx context.Context, rawIDToken string) (*Claims, error) { keySet := gooidc.NewRemoteKeySet(ctx, p.JWKSUrl) ctx = gooidc.ClientContext(ctx, p.reg.HTTPClient(ctx).HTTPClient) + return verifyToken(ctx, keySet, p.config, rawIDToken, issuerUrlGoogle) } diff --git a/selfservice/strategy/oidc/provider_netid.go b/selfservice/strategy/oidc/provider_netid.go index d936bf1b361c..9e4a79aba581 100644 --- a/selfservice/strategy/oidc/provider_netid.go +++ b/selfservice/strategy/oidc/provider_netid.go @@ -9,17 +9,16 @@ import ( "fmt" "net/url" "slices" + "strings" - gooidc "github.com/coreos/go-oidc/v3/oidc" - + "github.com/coreos/go-oidc/v3/oidc" "github.com/hashicorp/go-retryablehttp" "github.com/pkg/errors" "golang.org/x/oauth2" - "github.com/ory/x/urlx" - "github.com/ory/herodot" "github.com/ory/x/httpx" + "github.com/ory/x/urlx" ) const ( @@ -38,8 +37,8 @@ func NewProviderNetID( reg Dependencies, ) Provider { config.IssuerURL = fmt.Sprintf("%s://%s/", defaultBrokerScheme, defaultBrokerHost) - if !slices.Contains(config.Scope, gooidc.ScopeOpenID) { - config.Scope = append(config.Scope, gooidc.ScopeOpenID) + if !slices.Contains(config.Scope, oidc.ScopeOpenID) { + config.Scope = append(config.Scope, oidc.ScopeOpenID) } return &ProviderNetID{ @@ -118,6 +117,58 @@ func (n *ProviderNetID) Claims(ctx context.Context, exchange *oauth2.Token, _ ur return &userinfo, nil } +func (n *ProviderNetID) Verify(ctx context.Context, rawIDToken string) (*Claims, error) { + provider, err := n.provider(ctx) + if err != nil { + return nil, err + } + + req, err := retryablehttp.NewRequestWithContext(ctx, "POST", urlx.AppendPaths(n.brokerURL(), "/token").String(), strings.NewReader(url.Values{ + "grant_type": {"netid_fedcm"}, + "fedcm_token": {rawIDToken}, + }.Encode())) + if err != nil { + return nil, err + } + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + req.Header.Set("Origin", n.config.NetIDTokenOriginHeader) + res, err := n.reg.HTTPClient(ctx).Do(req) + if err != nil { + return nil, err + } + + token := struct { + IDToken string `json:"id_token"` + }{} + + if err := json.NewDecoder(res.Body).Decode(&token); err != nil { + return nil, err + } + + idToken, err := provider.VerifierContext( + n.withHTTPClientContext(ctx), + &oidc.Config{ClientID: n.config.ClientID}, + ).Verify(ctx, token.IDToken) + if err != nil { + return nil, err + } + + var ( + claims Claims + rawClaims map[string]any + ) + + if err = idToken.Claims(&claims); err != nil { + return nil, err + } + if err = idToken.Claims(&rawClaims); err != nil { + return nil, err + } + claims.RawClaims = rawClaims + + return &claims, nil +} + func (n *ProviderNetID) brokerURL() *url.URL { return &url.URL{Scheme: defaultBrokerScheme, Host: defaultBrokerHost} } diff --git a/selfservice/strategy/oidc/provider_netid_test.go b/selfservice/strategy/oidc/provider_netid_test.go new file mode 100644 index 000000000000..759bc663acbe --- /dev/null +++ b/selfservice/strategy/oidc/provider_netid_test.go @@ -0,0 +1,29 @@ +// Copyright © 2023 Ory Corp +// SPDX-License-Identifier: Apache-2.0 + +package oidc_test + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ory/kratos/internal" + "github.com/ory/kratos/selfservice/strategy/oidc" +) + +func TestNetidProvider(t *testing.T) { + t.Skip("can't test this automatically, because the token is only valid for a short time") + _, reg := internal.NewVeryFastRegistryWithoutDB(t) + + p := oidc.NewProviderNetID(&oidc.Configuration{ + ClientID: "9b56b26a-e93d-4fce-8f16-951a9858f23e", + }, reg) + + rawToken := `...` + + claims, err := p.(oidc.IDTokenVerifier).Verify(context.Background(), rawToken) + require.NoError(t, err) + require.NotNil(t, claims) +} diff --git a/selfservice/strategy/oidc/provider_test_fedcm.go b/selfservice/strategy/oidc/provider_test_fedcm.go new file mode 100644 index 000000000000..5ea002faa74b --- /dev/null +++ b/selfservice/strategy/oidc/provider_test_fedcm.go @@ -0,0 +1,49 @@ +// Copyright © 2023 Ory Corp +// SPDX-License-Identifier: Apache-2.0 + +package oidc + +import ( + "context" + + "github.com/golang-jwt/jwt/v5" +) + +// ProviderTestFedcm is a mock provider to test FedCM. +type ProviderTestFedcm struct { + *ProviderGenericOIDC +} + +var _ OAuth2Provider = (*ProviderTestFedcm)(nil) + +func NewProviderTestFedcm( + config *Configuration, + reg Dependencies, +) Provider { + return &ProviderTestFedcm{ + ProviderGenericOIDC: &ProviderGenericOIDC{ + config: config, + reg: reg, + }, + } +} + +func (g *ProviderTestFedcm) Verify(_ context.Context, rawIDToken string) (claims *Claims, err error) { + rawClaims := &struct { + Claims + jwt.MapClaims + }{} + _, err = jwt.ParseWithClaims(rawIDToken, rawClaims, func(token *jwt.Token) (interface{}, error) { + return []byte(`xxxxxxx`), nil + }, jwt.WithoutClaimsValidation()) + if err != nil { + return nil, err + } + rawClaims.Issuer = "https://example.com/fedcm" + + if err = rawClaims.Claims.Validate(); err != nil { + return nil, err + } + + return &rawClaims.Claims, nil +} diff --git a/selfservice/strategy/oidc/provider_test_fedcm_test.go b/selfservice/strategy/oidc/provider_test_fedcm_test.go new file mode 100644 index 000000000000..715441d29dff --- /dev/null +++ b/selfservice/strategy/oidc/provider_test_fedcm_test.go @@ -0,0 +1,26 @@ +// Copyright © 2025 Ory Corp +// SPDX-License-Identifier: Apache-2.0 + +package oidc_test + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ory/kratos/internal" + "github.com/ory/kratos/selfservice/strategy/oidc" +) + +func TestFedcmTestProvider(t *testing.T) { + _, reg := internal.NewVeryFastRegistryWithoutDB(t) + + p := oidc.NewProviderTestFedcm(&oidc.Configuration{}, reg) + + rawToken := `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1NWVlMjgxNC02ZTQ4LTRmZTktYWIzNS1mM2QxYzczM2I3ZTciLCJub25jZSI6ImVkOWM0ZDcyMDZkMDc1YTg4NjY0ZmE3YjMwY2Q5ZGE2NGU4ZTkwMjY5MGJhZmI2YjNmMmY2OWU5YzU1ZGUyNTcwOTFlYTk3ZTFiZTFiYjdiNDZmMjJjYzY0ZSIsImV4cCI6MTczNzU1ODM4MTk3MSwiaWF0IjoxNzM3NDcxOTgxOTcxLCJlbWFpbCI6InhweGN3dnU1YjRuemZvdGZAZXhhbXBsZS5jb20iLCJuYW1lIjoiVXNlciBOYW1lIiwicGljdHVyZSI6Imh0dHBzOi8vYXBpLmRpY2ViZWFyLmNvbS83LngvYm90dHRzL3BuZz9zZWVkPSUyNDJiJTI0MTAlMjR5WEs3eWozNEg4SkhCNm8zOG1sc2xlYzl1WkozZ2F2UGlDaFdaeFFIbnk3VkFKRlouS3RGZSJ9.GnSP_x8J_yS5wrTwtB6B-BydYYljrpVjQjS2vZ5D8Hg` + + claims, err := p.(oidc.IDTokenVerifier).Verify(context.Background(), rawToken) + require.NoError(t, err) + require.NotNil(t, claims) +} diff --git a/selfservice/strategy/oidc/strategy.go b/selfservice/strategy/oidc/strategy.go index f04f06d35899..d22e6fe000c0 100644 --- a/selfservice/strategy/oidc/strategy.go +++ b/selfservice/strategy/oidc/strategy.go @@ -402,22 +402,22 @@ func (s *Strategy) HandleCallback(w http.ResponseWriter, r *http.Request, ps htt req, state, cntnr, err := s.ValidateCallback(w, r, ps) if err != nil { if req != nil { - s.forwardError(ctx, w, r, req, s.handleError(ctx, w, r, req, state.ProviderId, nil, err)) + s.forwardError(ctx, w, r, req, s.HandleError(ctx, w, r, req, state.ProviderId, nil, err)) } else { - s.d.SelfServiceErrorManager().Forward(ctx, w, r, s.handleError(ctx, w, r, nil, "", nil, err)) + s.d.SelfServiceErrorManager().Forward(ctx, w, r, s.HandleError(ctx, w, r, nil, "", nil, err)) } return } if authenticated, err := s.alreadyAuthenticated(ctx, w, r, req); err != nil { - s.forwardError(ctx, w, r, req, s.handleError(ctx, w, r, req, state.ProviderId, nil, err)) + s.forwardError(ctx, w, r, req, s.HandleError(ctx, w, r, req, state.ProviderId, nil, err)) } else if authenticated { return } - provider, err := s.provider(ctx, state.ProviderId) + provider, err := s.Provider(ctx, state.ProviderId) if err != nil { - s.forwardError(ctx, w, r, req, s.handleError(ctx, w, r, req, state.ProviderId, nil, err)) + s.forwardError(ctx, w, r, req, s.HandleError(ctx, w, r, req, state.ProviderId, nil, err)) return } @@ -427,37 +427,37 @@ func (s *Strategy) HandleCallback(w http.ResponseWriter, r *http.Request, ps htt case OAuth2Provider: token, err := s.exchangeCode(ctx, p, code, PKCEVerifier(state)) if err != nil { - s.forwardError(ctx, w, r, req, s.handleError(ctx, w, r, req, state.ProviderId, nil, err)) + s.forwardError(ctx, w, r, req, s.HandleError(ctx, w, r, req, state.ProviderId, nil, err)) return } et, err = s.encryptOAuth2Tokens(ctx, token) if err != nil { - s.forwardError(ctx, w, r, req, s.handleError(ctx, w, r, req, state.ProviderId, nil, err)) + s.forwardError(ctx, w, r, req, s.HandleError(ctx, w, r, req, state.ProviderId, nil, err)) return } claims, err = p.Claims(ctx, token, r.URL.Query()) if err != nil { - s.forwardError(ctx, w, r, req, s.handleError(ctx, w, r, req, state.ProviderId, nil, err)) + s.forwardError(ctx, w, r, req, s.HandleError(ctx, w, r, req, state.ProviderId, nil, err)) return } case OAuth1Provider: token, err := p.ExchangeToken(ctx, r) if err != nil { - s.forwardError(ctx, w, r, req, s.handleError(ctx, w, r, req, state.ProviderId, nil, err)) + s.forwardError(ctx, w, r, req, s.HandleError(ctx, w, r, req, state.ProviderId, nil, err)) return } claims, err = p.Claims(ctx, token) if err != nil { - s.forwardError(ctx, w, r, req, s.handleError(ctx, w, r, req, state.ProviderId, nil, err)) + s.forwardError(ctx, w, r, req, s.HandleError(ctx, w, r, req, state.ProviderId, nil, err)) return } } if err = claims.Validate(); err != nil { - s.forwardError(ctx, w, r, req, s.handleError(ctx, w, r, req, state.ProviderId, nil, err)) + s.forwardError(ctx, w, r, req, s.HandleError(ctx, w, r, req, state.ProviderId, nil, err)) return } @@ -467,7 +467,7 @@ func (s *Strategy) HandleCallback(w http.ResponseWriter, r *http.Request, ps htt case *login.Flow: a.Active = s.ID() a.TransientPayload = cntnr.TransientPayload - if ff, err := s.processLogin(ctx, w, r, a, et, claims, provider, cntnr); err != nil { + if ff, err := s.ProcessLogin(ctx, w, r, a, et, claims, provider, cntnr); err != nil { if errors.Is(err, flow.ErrCompletedByStrategy) { return } @@ -494,16 +494,16 @@ func (s *Strategy) HandleCallback(w http.ResponseWriter, r *http.Request, ps htt a.TransientPayload = cntnr.TransientPayload sess, err := s.d.SessionManager().FetchFromRequest(ctx, r) if err != nil { - s.forwardError(ctx, w, r, a, s.handleError(ctx, w, r, a, state.ProviderId, nil, err)) + s.forwardError(ctx, w, r, a, s.HandleError(ctx, w, r, a, state.ProviderId, nil, err)) return } if err := s.linkProvider(ctx, w, r, &settings.UpdateContext{Session: sess, Flow: a}, et, claims, provider); err != nil { - s.forwardError(ctx, w, r, a, s.handleError(ctx, w, r, a, state.ProviderId, nil, err)) + s.forwardError(ctx, w, r, a, s.HandleError(ctx, w, r, a, state.ProviderId, nil, err)) return } return default: - s.forwardError(ctx, w, r, req, s.handleError(ctx, w, r, req, state.ProviderId, nil, errors.WithStack(x.PseudoPanic. + s.forwardError(ctx, w, r, req, s.HandleError(ctx, w, r, req, state.ProviderId, nil, errors.WithStack(x.PseudoPanic. WithDetailf("cause", "Unexpected type in OpenID Connect flow: %T", a)))) return } @@ -555,7 +555,7 @@ func (s *Strategy) Config(ctx context.Context) (*ConfigurationCollection, error) return &c, nil } -func (s *Strategy) provider(ctx context.Context, id string) (Provider, error) { +func (s *Strategy) Provider(ctx context.Context, id string) (Provider, error) { if c, err := s.Config(ctx); err != nil { return nil, err } else if provider, err := c.Provider(id, s.d); err != nil { @@ -582,7 +582,7 @@ func (s *Strategy) forwardError(ctx context.Context, w http.ResponseWriter, r *h } } -func (s *Strategy) handleError(ctx context.Context, w http.ResponseWriter, r *http.Request, f flow.Flow, usedProviderID string, traits []byte, err error) error { +func (s *Strategy) HandleError(ctx context.Context, w http.ResponseWriter, r *http.Request, f flow.Flow, usedProviderID string, traits []byte, err error) error { switch rf := f.(type) { case *login.Flow: return err @@ -664,7 +664,7 @@ func (s *Strategy) handleError(ctx context.Context, w http.ResponseWriter, r *ht func (s *Strategy) populateAccountLinkingUI(ctx context.Context, lf *login.Flow, usedProviderID string, duplicateIdentifier string, availableCredentials []string, availableProviders []string) { newLoginURL := s.d.Config().SelfServiceFlowLoginUI(ctx).String() usedProviderLabel := usedProviderID - provider, _ := s.provider(ctx, usedProviderID) + provider, _ := s.Provider(ctx, usedProviderID) if provider != nil && provider.Config() != nil { usedProviderLabel = provider.Config().Label if usedProviderLabel == "" { @@ -742,7 +742,7 @@ func (s *Strategy) CompletedAuthenticationMethod(ctx context.Context) session.Au } } -func (s *Strategy) processIDToken(r *http.Request, provider Provider, idToken, idTokenNonce string) (*Claims, error) { +func (s *Strategy) ProcessIDToken(r *http.Request, provider Provider, idToken, idTokenNonce string) (*Claims, error) { verifier, ok := provider.(IDTokenVerifier) if !ok { return nil, errors.WithStack(herodot.ErrInternalServerError.WithReasonf("The provider %s does not support id_token verification", provider.Config().Provider)) diff --git a/selfservice/strategy/oidc/strategy_login.go b/selfservice/strategy/oidc/strategy_login.go index ffe04c7c3e75..3f8d716d74a0 100644 --- a/selfservice/strategy/oidc/strategy_login.go +++ b/selfservice/strategy/oidc/strategy_login.go @@ -98,7 +98,7 @@ type UpdateLoginFlowWithOidcMethod struct { TransientPayload json.RawMessage `json:"transient_payload,omitempty" form:"transient_payload"` } -func (s *Strategy) processLogin(ctx context.Context, w http.ResponseWriter, r *http.Request, loginFlow *login.Flow, token *identity.CredentialsOIDCEncryptedTokens, claims *Claims, provider Provider, container *AuthCodeContainer) (_ *registration.Flow, err error) { +func (s *Strategy) ProcessLogin(ctx context.Context, w http.ResponseWriter, r *http.Request, loginFlow *login.Flow, token *identity.CredentialsOIDCEncryptedTokens, claims *Claims, provider Provider, container *AuthCodeContainer) (_ *registration.Flow, err error) { ctx, span := s.d.Tracer(ctx).Tracer().Start(ctx, "selfservice.strategy.oidc.Strategy.processLogin") defer otelx.End(span, &err) @@ -133,12 +133,12 @@ func (s *Strategy) processLogin(ctx context.Context, w http.ResponseWriter, r *h registrationFlow, err := s.d.RegistrationHandler().NewRegistrationFlow(w, r, loginFlow.Type, opts...) if err != nil { - return nil, s.handleError(ctx, w, r, loginFlow, provider.Config().ID, nil, err) + return nil, s.HandleError(ctx, w, r, loginFlow, provider.Config().ID, nil, err) } err = s.d.SessionTokenExchangePersister().MoveToNewFlow(ctx, loginFlow.ID, registrationFlow.ID) if err != nil { - return nil, s.handleError(ctx, w, r, loginFlow, provider.Config().ID, nil, err) + return nil, s.HandleError(ctx, w, r, loginFlow, provider.Config().ID, nil, err) } registrationFlow.OrganizationID = loginFlow.OrganizationID @@ -157,12 +157,12 @@ func (s *Strategy) processLogin(ctx context.Context, w http.ResponseWriter, r *h return nil, nil } - return nil, s.handleError(ctx, w, r, loginFlow, provider.Config().ID, nil, err) + return nil, s.HandleError(ctx, w, r, loginFlow, provider.Config().ID, nil, err) } var oidcCredentials identity.CredentialsOIDC if err := json.NewDecoder(bytes.NewBuffer(c.Config)).Decode(&oidcCredentials); err != nil { - return nil, s.handleError(ctx, w, r, loginFlow, provider.Config().ID, nil, errors.WithStack(herodot.ErrInternalServerError.WithReason("The password credentials could not be decoded properly").WithDebug(err.Error()))) + return nil, s.HandleError(ctx, w, r, loginFlow, provider.Config().ID, nil, errors.WithStack(herodot.ErrInternalServerError.WithReason("The password credentials could not be decoded properly").WithDebug(err.Error()))) } sess := session.NewInactiveSession() @@ -171,13 +171,13 @@ func (s *Strategy) processLogin(ctx context.Context, w http.ResponseWriter, r *h for _, c := range oidcCredentials.Providers { if c.Subject == claims.Subject && c.Provider == provider.Config().ID { if err = s.d.LoginHookExecutor().PostLoginHook(w, r, node.OpenIDConnectGroup, loginFlow, i, sess, provider.Config().ID); err != nil { - return nil, s.handleError(ctx, w, r, loginFlow, provider.Config().ID, nil, err) + return nil, s.HandleError(ctx, w, r, loginFlow, provider.Config().ID, nil, err) } return nil, nil } } - return nil, s.handleError(ctx, w, r, loginFlow, provider.Config().ID, nil, errors.WithStack(herodot.ErrInternalServerError.WithReason("Unable to find matching OpenID Connect Credentials.").WithDebugf(`Unable to find credentials that match the given provider "%s" and subject "%s".`, provider.Config().ID, claims.Subject))) + return nil, s.HandleError(ctx, w, r, loginFlow, provider.Config().ID, nil, errors.WithStack(herodot.ErrInternalServerError.WithReason("Unable to find matching OpenID Connect Credentials.").WithDebugf(`Unable to find credentials that match the given Provider "%s" and subject "%s".`, provider.Config().ID, claims.Subject))) } func (s *Strategy) Login(w http.ResponseWriter, r *http.Request, f *login.Flow, _ *session.Session) (i *identity.Identity, err error) { @@ -191,7 +191,7 @@ func (s *Strategy) Login(w http.ResponseWriter, r *http.Request, f *login.Flow, var p UpdateLoginFlowWithOidcMethod if err := s.newLinkDecoder(ctx, &p, r); err != nil { - return nil, s.handleError(ctx, w, r, f, "", nil, err) + return nil, s.HandleError(ctx, w, r, f, "", nil, err) } f.IDToken = p.IDToken @@ -216,43 +216,43 @@ func (s *Strategy) Login(w http.ResponseWriter, r *http.Request, f *login.Flow, } if err := flow.MethodEnabledAndAllowed(ctx, f.GetFlowName(), s.SettingsStrategyID(), s.SettingsStrategyID(), s.d); err != nil { - return nil, s.handleError(ctx, w, r, f, pid, nil, s.handleMethodNotAllowedError(err)) + return nil, s.HandleError(ctx, w, r, f, pid, nil, s.handleMethodNotAllowedError(err)) } - provider, err := s.provider(ctx, pid) + provider, err := s.Provider(ctx, pid) if err != nil { - return nil, s.handleError(ctx, w, r, f, pid, nil, err) + return nil, s.HandleError(ctx, w, r, f, pid, nil, err) } req, err := s.validateFlow(ctx, r, f.ID) if err != nil { - return nil, s.handleError(ctx, w, r, f, pid, nil, err) + return nil, s.HandleError(ctx, w, r, f, pid, nil, err) } if authenticated, err := s.alreadyAuthenticated(ctx, w, r, req); err != nil { - return nil, s.handleError(ctx, w, r, f, pid, nil, err) + return nil, s.HandleError(ctx, w, r, f, pid, nil, err) } else if authenticated { return i, nil } if p.IDToken != "" { - claims, err := s.processIDToken(r, provider, p.IDToken, p.IDTokenNonce) + claims, err := s.ProcessIDToken(r, provider, p.IDToken, p.IDTokenNonce) if err != nil { - return nil, s.handleError(ctx, w, r, f, pid, nil, err) + return nil, s.HandleError(ctx, w, r, f, pid, nil, err) } - _, err = s.processLogin(ctx, w, r, f, nil, claims, provider, &AuthCodeContainer{ + _, err = s.ProcessLogin(ctx, w, r, f, nil, claims, provider, &AuthCodeContainer{ FlowID: f.ID.String(), Traits: p.Traits, }) if err != nil { - return nil, s.handleError(ctx, w, r, f, pid, nil, err) + return nil, s.HandleError(ctx, w, r, f, pid, nil, err) } return nil, errors.WithStack(flow.ErrCompletedByStrategy) } state, pkce, err := s.GenerateState(ctx, provider, f.ID) if err != nil { - return nil, s.handleError(ctx, w, r, f, pid, nil, err) + return nil, s.HandleError(ctx, w, r, f, pid, nil, err) } if err := s.d.ContinuityManager().Pause(ctx, w, r, sessionName, continuity.WithPayload(&AuthCodeContainer{ @@ -262,12 +262,12 @@ func (s *Strategy) Login(w http.ResponseWriter, r *http.Request, f *login.Flow, TransientPayload: f.TransientPayload, }), continuity.WithLifespan(time.Minute*30)); err != nil { - return nil, s.handleError(ctx, w, r, f, pid, nil, err) + return nil, s.HandleError(ctx, w, r, f, pid, nil, err) } f.Active = s.ID() if err = s.d.LoginFlowPersister().UpdateLoginFlow(ctx, f); err != nil { - return nil, s.handleError(ctx, w, r, f, pid, nil, errors.WithStack(herodot.ErrInternalServerError.WithReason("Could not update flow").WithDebug(err.Error()))) + return nil, s.HandleError(ctx, w, r, f, pid, nil, errors.WithStack(herodot.ErrInternalServerError.WithReason("Could not update flow").WithDebug(err.Error()))) } var up map[string]string @@ -277,7 +277,7 @@ func (s *Strategy) Login(w http.ResponseWriter, r *http.Request, f *login.Flow, codeURL, err := getAuthRedirectURL(ctx, provider, f, state, up, pkce) if err != nil { - return nil, s.handleError(ctx, w, r, f, pid, nil, err) + return nil, s.HandleError(ctx, w, r, f, pid, nil, err) } if x.IsJSONRequest(r) { diff --git a/selfservice/strategy/oidc/strategy_registration.go b/selfservice/strategy/oidc/strategy_registration.go index ccb6287cdfb2..9a06bfedd138 100644 --- a/selfservice/strategy/oidc/strategy_registration.go +++ b/selfservice/strategy/oidc/strategy_registration.go @@ -156,7 +156,7 @@ func (s *Strategy) Register(w http.ResponseWriter, r *http.Request, f *registrat var p UpdateRegistrationFlowWithOidcMethod if err := s.newLinkDecoder(ctx, &p, r); err != nil { - return s.handleError(ctx, w, r, f, "", nil, err) + return s.HandleError(ctx, w, r, f, "", nil, err) } pid := p.Provider // this can come from both url query and post body @@ -181,29 +181,29 @@ func (s *Strategy) Register(w http.ResponseWriter, r *http.Request, f *registrat } if err := flow.MethodEnabledAndAllowed(ctx, f.GetFlowName(), s.SettingsStrategyID(), s.SettingsStrategyID(), s.d); err != nil { - return s.handleError(ctx, w, r, f, pid, nil, s.handleMethodNotAllowedError(err)) + return s.HandleError(ctx, w, r, f, pid, nil, s.handleMethodNotAllowedError(err)) } - provider, err := s.provider(ctx, pid) + provider, err := s.Provider(ctx, pid) if err != nil { - return s.handleError(ctx, w, r, f, pid, nil, err) + return s.HandleError(ctx, w, r, f, pid, nil, err) } req, err := s.validateFlow(ctx, r, f.ID) if err != nil { - return s.handleError(ctx, w, r, f, pid, nil, err) + return s.HandleError(ctx, w, r, f, pid, nil, err) } if authenticated, err := s.alreadyAuthenticated(ctx, w, r, req); err != nil { - return s.handleError(ctx, w, r, f, pid, nil, err) + return s.HandleError(ctx, w, r, f, pid, nil, err) } else if authenticated { return errors.WithStack(registration.ErrAlreadyLoggedIn) } if p.IDToken != "" { - claims, err := s.processIDToken(r, provider, p.IDToken, p.IDTokenNonce) + claims, err := s.ProcessIDToken(r, provider, p.IDToken, p.IDTokenNonce) if err != nil { - return s.handleError(ctx, w, r, f, pid, nil, err) + return s.HandleError(ctx, w, r, f, pid, nil, err) } _, err = s.processRegistration(ctx, w, r, f, nil, claims, provider, &AuthCodeContainer{ FlowID: f.ID.String(), @@ -211,14 +211,14 @@ func (s *Strategy) Register(w http.ResponseWriter, r *http.Request, f *registrat TransientPayload: f.TransientPayload, }) if err != nil { - return s.handleError(ctx, w, r, f, pid, nil, err) + return s.HandleError(ctx, w, r, f, pid, nil, err) } return errors.WithStack(flow.ErrCompletedByStrategy) } state, pkce, err := s.GenerateState(ctx, provider, f.ID) if err != nil { - return s.handleError(ctx, w, r, f, pid, nil, err) + return s.HandleError(ctx, w, r, f, pid, nil, err) } if err := s.d.ContinuityManager().Pause(ctx, w, r, sessionName, continuity.WithPayload(&AuthCodeContainer{ @@ -228,7 +228,7 @@ func (s *Strategy) Register(w http.ResponseWriter, r *http.Request, f *registrat TransientPayload: f.TransientPayload, }), continuity.WithLifespan(time.Minute*30)); err != nil { - return s.handleError(ctx, w, r, f, pid, nil, err) + return s.HandleError(ctx, w, r, f, pid, nil, err) } var up map[string]string @@ -238,7 +238,7 @@ func (s *Strategy) Register(w http.ResponseWriter, r *http.Request, f *registrat codeURL, err := getAuthRedirectURL(ctx, provider, f, state, up, pkce) if err != nil { - return s.handleError(ctx, w, r, f, pid, nil, err) + return s.HandleError(ctx, w, r, f, pid, nil, err) } if x.IsJSONRequest(r) { s.d.Writer().WriteError(w, r, flow.NewBrowserLocationChangeRequiredError(codeURL)) @@ -299,17 +299,17 @@ func (s *Strategy) processRegistration(ctx context.Context, w http.ResponseWrite // not need additional consent/login. // This is kinda hacky but the only way to ensure seamless login/registration flows when using OIDC. - s.d.Logger().WithRequest(r).WithField("provider", provider.Config().ID). + s.d.Logger().WithRequest(r).WithField("Provider", provider.Config().ID). WithField("subject", claims.Subject). Debug("Received successful OpenID Connect callback but user is already registered. Re-initializing login flow now.") lf, err := s.registrationToLogin(ctx, w, r, rf) if err != nil { - return nil, s.handleError(ctx, w, r, rf, provider.Config().ID, nil, err) + return nil, s.HandleError(ctx, w, r, rf, provider.Config().ID, nil, err) } - if _, err := s.processLogin(ctx, w, r, lf, token, claims, provider, container); err != nil { - return lf, s.handleError(ctx, w, r, rf, provider.Config().ID, nil, err) + if _, err := s.ProcessLogin(ctx, w, r, lf, token, claims, provider, container); err != nil { + return lf, s.HandleError(ctx, w, r, rf, provider.Config().ID, nil, err) } return nil, nil @@ -318,17 +318,17 @@ func (s *Strategy) processRegistration(ctx context.Context, w http.ResponseWrite fetch := fetcher.NewFetcher(fetcher.WithClient(s.d.HTTPClient(ctx)), fetcher.WithCache(jsonnetCache, 60*time.Minute)) jsonnetMapperSnippet, err := fetch.FetchContext(ctx, provider.Config().Mapper) if err != nil { - return nil, s.handleError(ctx, w, r, rf, provider.Config().ID, nil, err) + return nil, s.HandleError(ctx, w, r, rf, provider.Config().ID, nil, err) } i, va, err := s.createIdentity(ctx, w, r, rf, claims, provider, container, jsonnetMapperSnippet.Bytes()) if err != nil { - return nil, s.handleError(ctx, w, r, rf, provider.Config().ID, nil, err) + return nil, s.HandleError(ctx, w, r, rf, provider.Config().ID, nil, err) } // Validate the identity itself if err := s.d.IdentityValidator().Validate(ctx, i); err != nil { - return nil, s.handleError(ctx, w, r, rf, provider.Config().ID, i.Traits, err) + return nil, s.HandleError(ctx, w, r, rf, provider.Config().ID, i.Traits, err) } for n := range i.VerifiableAddresses { @@ -345,12 +345,12 @@ func (s *Strategy) processRegistration(ctx context.Context, w http.ResponseWrite creds, err := identity.NewCredentialsOIDC(token, provider.Config().ID, claims.Subject, provider.Config().OrganizationID) if err != nil { - return nil, s.handleError(ctx, w, r, rf, provider.Config().ID, i.Traits, err) + return nil, s.HandleError(ctx, w, r, rf, provider.Config().ID, i.Traits, err) } i.SetCredentials(s.ID(), *creds) if err := s.d.RegistrationExecutor().PostRegistrationHook(w, r, s.ID(), provider.Config().ID, provider.Config().OrganizationID, rf, i); err != nil { - return nil, s.handleError(ctx, w, r, rf, provider.Config().ID, i.Traits, err) + return nil, s.HandleError(ctx, w, r, rf, provider.Config().ID, i.Traits, err) } return nil, nil @@ -359,36 +359,36 @@ func (s *Strategy) processRegistration(ctx context.Context, w http.ResponseWrite func (s *Strategy) createIdentity(ctx context.Context, w http.ResponseWriter, r *http.Request, a *registration.Flow, claims *Claims, provider Provider, container *AuthCodeContainer, jsonnetSnippet []byte) (*identity.Identity, []VerifiedAddress, error) { var jsonClaims bytes.Buffer if err := json.NewEncoder(&jsonClaims).Encode(claims); err != nil { - return nil, nil, s.handleError(ctx, w, r, a, provider.Config().ID, nil, err) + return nil, nil, s.HandleError(ctx, w, r, a, provider.Config().ID, nil, err) } vm, err := s.d.JsonnetVM(ctx) if err != nil { - return nil, nil, s.handleError(ctx, w, r, a, provider.Config().ID, nil, err) + return nil, nil, s.HandleError(ctx, w, r, a, provider.Config().ID, nil, err) } vm.ExtCode("claims", jsonClaims.String()) evaluated, err := vm.EvaluateAnonymousSnippet(provider.Config().Mapper, string(jsonnetSnippet)) if err != nil { - return nil, nil, s.handleError(ctx, w, r, a, provider.Config().ID, nil, err) + return nil, nil, s.HandleError(ctx, w, r, a, provider.Config().ID, nil, err) } i := identity.NewIdentity(s.d.Config().DefaultIdentityTraitsSchemaID(ctx)) if err := s.setTraits(ctx, w, r, a, provider, container, evaluated, i); err != nil { - return nil, nil, s.handleError(ctx, w, r, a, provider.Config().ID, i.Traits, err) + return nil, nil, s.HandleError(ctx, w, r, a, provider.Config().ID, i.Traits, err) } if err := s.setMetadata(evaluated, i, PublicMetadata); err != nil { - return nil, nil, s.handleError(ctx, w, r, a, provider.Config().ID, i.Traits, err) + return nil, nil, s.HandleError(ctx, w, r, a, provider.Config().ID, i.Traits, err) } if err := s.setMetadata(evaluated, i, AdminMetadata); err != nil { - return nil, nil, s.handleError(ctx, w, r, a, provider.Config().ID, i.Traits, err) + return nil, nil, s.HandleError(ctx, w, r, a, provider.Config().ID, i.Traits, err) } va, err := s.extractVerifiedAddresses(evaluated) if err != nil { - return nil, nil, s.handleError(ctx, w, r, a, provider.Config().ID, i.Traits, err) + return nil, nil, s.HandleError(ctx, w, r, a, provider.Config().ID, i.Traits, err) } if orgID, err := uuid.FromString(provider.Config().OrganizationID); err == nil { @@ -414,7 +414,7 @@ func (s *Strategy) setTraits(ctx context.Context, w http.ResponseWriter, r *http if container != nil { traits, err := merge(container.Traits, json.RawMessage(jsonTraits.Raw)) if err != nil { - return s.handleError(ctx, w, r, a, provider.Config().ID, nil, err) + return s.HandleError(ctx, w, r, a, provider.Config().ID, nil, err) } i.Traits = traits diff --git a/selfservice/strategy/oidc/strategy_settings.go b/selfservice/strategy/oidc/strategy_settings.go index fa82ab5a1499..cf76e9f2feb3 100644 --- a/selfservice/strategy/oidc/strategy_settings.go +++ b/selfservice/strategy/oidc/strategy_settings.go @@ -359,7 +359,7 @@ func (s *Strategy) initLinkProvider(ctx context.Context, w http.ResponseWriter, return s.handleSettingsError(ctx, w, r, ctxUpdate, p, errors.WithStack(settings.NewFlowNeedsReAuth())) } - provider, err := s.provider(ctx, p.Link) + provider, err := s.Provider(ctx, p.Link) if err != nil { return s.handleSettingsError(ctx, w, r, ctxUpdate, p, err) } diff --git a/selfservice/strategy/oidc/token_verifier.go b/selfservice/strategy/oidc/token_verifier.go index ce9cb8b3d3ee..42b16767a041 100644 --- a/selfservice/strategy/oidc/token_verifier.go +++ b/selfservice/strategy/oidc/token_verifier.go @@ -35,8 +35,19 @@ func verifyToken(ctx context.Context, keySet oidc.KeySet, config *Configuration, return nil, fmt.Errorf("token audience didn't match allowed audiences: %+v %w", tokenAudiences, err) } claims := &Claims{} + var rawClaims map[string]any + + if token == nil { + return nil, fmt.Errorf("token is nil") + } + if err := token.Claims(claims); err != nil { return nil, err } + if err = token.Claims(&rawClaims); err != nil { + return nil, err + } + claims.RawClaims = rawClaims + return claims, nil } diff --git a/spec/api.json b/spec/api.json index 210e6cca8592..84cf2d9ed6ab 100644 --- a/spec/api.json +++ b/spec/api.json @@ -413,6 +413,45 @@ }, "type": "object" }, + "Provider": { + "properties": { + "client_id": { + "description": "The RP's client identifier, issued by the IdP.", + "type": "string" + }, + "config_url": { + "description": "A full path of the IdP config file.", + "type": "string" + }, + "domain_hint": { + "description": "By specifying one of domain_hints values provided by the accounts endpoints,\nthe FedCM dialog selectively shows the specified account.", + "type": "string" + }, + "fields": { + "description": "Array of strings that specifies the user information (\"name\", \" email\",\n\"picture\") that RP needs IdP to share with them.\n\nNote: Field API is supported by Chrome 132 and later.", + "items": { + "type": "string" + }, + "type": "array" + }, + "login_hint": { + "description": "By specifying one of login_hints values provided by the accounts endpoints,\nthe FedCM dialog selectively shows the specified account.", + "type": "string" + }, + "nonce": { + "description": "A random string to ensure the response is issued for this specific request.\nPrevents replay attacks.", + "type": "string" + }, + "parameters": { + "additionalProperties": { + "type": "string" + }, + "description": "Custom object that allows to specify additional key-value parameters:\nscope: A string value containing additional permissions that RP needs to\nrequest, for example \" drive.readonly calendar.readonly\"\nnonce: A random string to ensure the response is issued for this specific\nrequest. Prevents replay attacks.\n\nOther custom key-value parameters.\n\nNote: parameters is supported from Chrome 132.", + "type": "object" + } + }, + "type": "object" + }, "RecoveryAddressType": { "title": "RecoveryAddressType must not exceed 16 characters as that is the limitation in the SQL Schema.", "type": "string" @@ -425,6 +464,27 @@ "format": "uuid4", "type": "string" }, + "UpdateFedcmFlowBody": { + "properties": { + "csrf_token": { + "description": "CSRFToken is the anti-CSRF token.", + "type": "string" + }, + "nonce": { + "description": "Nonce is the nonce that was used in the `navigator.credentials.get` call. If\nspecified, it must match the `nonce` claim in the token.", + "type": "string" + }, + "token": { + "description": "Token contains the result of `navigator.credentials.get`.", + "type": "string" + } + }, + "required": [ + "token", + "csrf_token" + ], + "type": "object" + }, "authenticatorAssuranceLevel": { "description": "The authenticator assurance level can be one of \"aal1\", \"aal2\", or \"aal3\". A higher number means that it is harder\nfor an attacker to compromise the account.\n\nGenerally, \"aal1\" implies that one authentication factor was used while AAL2 implies that two factors (e.g.\npassword + TOTP) have been used.\n\nTo learn more about these levels please head over to: https://www.ory.sh/kratos/docs/concepts/credentials", "enum": [ @@ -676,6 +736,22 @@ "title": "A Message's Type", "type": "string" }, + "createFedcmFlowResponse": { + "description": "Contains a list of all available FedCM providers.", + "properties": { + "csrf_token": { + "type": "string" + }, + "providers": { + "items": { + "$ref": "#/components/schemas/Provider" + }, + "type": "array" + } + }, + "title": "CreateFedcmFlowResponse", + "type": "object" + }, "createIdentityBody": { "description": "Create Identity Body", "properties": { @@ -5485,6 +5561,129 @@ ] } }, + "/self-service/fed-cm/parameters": { + "get": { + "description": "This endpoint returns a list of all available FedCM providers. It is only supported on the Ory Network.", + "operationId": "createFedcmFlow", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/createFedcmFlowResponse" + } + } + }, + "description": "createFedcmFlowResponse" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errorGeneric" + } + } + }, + "description": "errorGeneric" + }, + "default": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errorGeneric" + } + } + }, + "description": "errorGeneric" + } + }, + "summary": "Get FedCM Parameters", + "tags": [ + "frontend" + ] + } + }, + "/self-service/fed-cm/token": { + "post": { + "description": "Use this endpoint to submit a token from a FedCM provider through\n`navigator.credentials.get` and log the user in. The parameters from\n`navigator.credentials.get` must have come from `GET\nself-service/fed-cm/parameters`.", + "operationId": "updateFedcmFlow", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateFedcmFlowBody" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/UpdateFedcmFlowBody" + } + } + }, + "required": true, + "x-originalParamName": "Body" + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/successfulNativeLogin" + } + } + }, + "description": "successfulNativeLogin" + }, + "303": { + "$ref": "#/components/responses/emptyResponse" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/loginFlow" + } + } + }, + "description": "loginFlow" + }, + "410": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errorGeneric" + } + } + }, + "description": "errorGeneric" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errorBrowserLocationChangeRequired" + } + } + }, + "description": "errorBrowserLocationChangeRequired" + }, + "default": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errorGeneric" + } + } + }, + "description": "errorGeneric" + } + }, + "summary": "Submit a FedCM token", + "tags": [ + "frontend" + ] + } + }, "/self-service/login": { "post": { "description": "Use this endpoint to complete a login flow. This endpoint\nbehaves differently for API and browser flows.\n\nAPI flows expect `application/json` to be sent in the body and responds with\nHTTP 200 and a application/json body with the session token on success;\nHTTP 410 if the original flow expired with the appropriate error messages set and optionally a `use_flow_id` parameter in the body;\nHTTP 400 on form validation errors.\n\nBrowser flows expect a Content-Type of `application/x-www-form-urlencoded` or `application/json` to be sent in the body and respond with\na HTTP 303 redirect to the post/after login URL or the `return_to` value if it was set and if the login succeeded;\na HTTP 303 redirect to the login UI URL with the flow ID containing the validation errors otherwise.\n\nBrowser flows with an accept header of `application/json` will not redirect but instead respond with\nHTTP 200 and a application/json body with the signed in identity and a `Set-Cookie` header on success;\nHTTP 303 redirect to a fresh login flow if the original flow expired with the appropriate error messages set;\nHTTP 400 on form validation errors.\n\nIf this endpoint is called with `Accept: application/json` in the header, the response contains the flow without a redirect. In the\ncase of an error, the `error.id` of the JSON response body can be one of:\n\n`session_already_available`: The user is already signed in.\n`security_csrf_violation`: Unable to fetch the flow because a CSRF violation occurred.\n`security_identity_mismatch`: The requested `?return_to` address is not allowed to be used. Adjust this in the configuration!\n`browser_location_change_required`: Usually sent when an AJAX request indicates that the browser needs to open a specific URL.\nMost likely used in Social Sign In flows.\n\nMore information can be found at [Ory Kratos User Login](https://www.ory.sh/docs/kratos/self-service/flows/user-login) and [User Registration Documentation](https://www.ory.sh/docs/kratos/self-service/flows/user-registration).", diff --git a/spec/swagger.json b/spec/swagger.json index f2f4f05ab25b..dbed6c5be265 100755 --- a/spec/swagger.json +++ b/spec/swagger.json @@ -1477,6 +1477,112 @@ } } }, + "/self-service/fed-cm/parameters": { + "get": { + "description": "This endpoint returns a list of all available FedCM providers. It is only supported on the Ory Network.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "schemes": [ + "http", + "https" + ], + "tags": [ + "frontend" + ], + "summary": "Get FedCM Parameters", + "operationId": "createFedcmFlow", + "responses": { + "200": { + "description": "createFedcmFlowResponse", + "schema": { + "$ref": "#/definitions/createFedcmFlowResponse" + } + }, + "400": { + "description": "errorGeneric", + "schema": { + "$ref": "#/definitions/errorGeneric" + } + }, + "default": { + "description": "errorGeneric", + "schema": { + "$ref": "#/definitions/errorGeneric" + } + } + } + } + }, + "/self-service/fed-cm/token": { + "post": { + "description": "Use this endpoint to submit a token from a FedCM provider through\n`navigator.credentials.get` and log the user in. The parameters from\n`navigator.credentials.get` must have come from `GET\nself-service/fed-cm/parameters`.", + "consumes": [ + "application/json", + "application/x-www-form-urlencoded" + ], + "produces": [ + "application/json" + ], + "schemes": [ + "http", + "https" + ], + "tags": [ + "frontend" + ], + "summary": "Submit a FedCM token", + "operationId": "updateFedcmFlow", + "parameters": [ + { + "name": "Body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/UpdateFedcmFlowBody" + } + } + ], + "responses": { + "200": { + "description": "successfulNativeLogin", + "schema": { + "$ref": "#/definitions/successfulNativeLogin" + } + }, + "303": { + "$ref": "#/responses/emptyResponse" + }, + "400": { + "description": "loginFlow", + "schema": { + "$ref": "#/definitions/loginFlow" + } + }, + "410": { + "description": "errorGeneric", + "schema": { + "$ref": "#/definitions/errorGeneric" + } + }, + "422": { + "description": "errorBrowserLocationChangeRequired", + "schema": { + "$ref": "#/definitions/errorBrowserLocationChangeRequired" + } + }, + "default": { + "description": "errorGeneric", + "schema": { + "$ref": "#/definitions/errorGeneric" + } + } + } + } + }, "/self-service/login": { "post": { "description": "Use this endpoint to complete a login flow. This endpoint\nbehaves differently for API and browser flows.\n\nAPI flows expect `application/json` to be sent in the body and responds with\nHTTP 200 and a application/json body with the session token on success;\nHTTP 410 if the original flow expired with the appropriate error messages set and optionally a `use_flow_id` parameter in the body;\nHTTP 400 on form validation errors.\n\nBrowser flows expect a Content-Type of `application/x-www-form-urlencoded` or `application/json` to be sent in the body and respond with\na HTTP 303 redirect to the post/after login URL or the `return_to` value if it was set and if the login succeeded;\na HTTP 303 redirect to the login UI URL with the flow ID containing the validation errors otherwise.\n\nBrowser flows with an accept header of `application/json` will not redirect but instead respond with\nHTTP 200 and a application/json body with the signed in identity and a `Set-Cookie` header on success;\nHTTP 303 redirect to a fresh login flow if the original flow expired with the appropriate error messages set;\nHTTP 400 on form validation errors.\n\nIf this endpoint is called with `Accept: application/json` in the header, the response contains the flow without a redirect. In the\ncase of an error, the `error.id` of the JSON response body can be one of:\n\n`session_already_available`: The user is already signed in.\n`security_csrf_violation`: Unable to fetch the flow because a CSRF violation occurred.\n`security_identity_mismatch`: The requested `?return_to` address is not allowed to be used. Adjust this in the configuration!\n`browser_location_change_required`: Usually sent when an AJAX request indicates that the browser needs to open a specific URL.\nMost likely used in Social Sign In flows.\n\nMore information can be found at [Ory Kratos User Login](https://www.ory.sh/docs/kratos/self-service/flows/user-login) and [User Registration Documentation](https://www.ory.sh/docs/kratos/self-service/flows/user-registration).", @@ -3610,11 +3716,71 @@ } } }, + "Provider": { + "type": "object", + "properties": { + "client_id": { + "description": "The RP's client identifier, issued by the IdP.", + "type": "string" + }, + "config_url": { + "description": "A full path of the IdP config file.", + "type": "string" + }, + "domain_hint": { + "description": "By specifying one of domain_hints values provided by the accounts endpoints,\nthe FedCM dialog selectively shows the specified account.", + "type": "string" + }, + "fields": { + "description": "Array of strings that specifies the user information (\"name\", \" email\",\n\"picture\") that RP needs IdP to share with them.\n\nNote: Field API is supported by Chrome 132 and later.", + "type": "array", + "items": { + "type": "string" + } + }, + "login_hint": { + "description": "By specifying one of login_hints values provided by the accounts endpoints,\nthe FedCM dialog selectively shows the specified account.", + "type": "string" + }, + "nonce": { + "description": "A random string to ensure the response is issued for this specific request.\nPrevents replay attacks.", + "type": "string" + }, + "parameters": { + "description": "Custom object that allows to specify additional key-value parameters:\nscope: A string value containing additional permissions that RP needs to\nrequest, for example \" drive.readonly calendar.readonly\"\nnonce: A random string to ensure the response is issued for this specific\nrequest. Prevents replay attacks.\n\nOther custom key-value parameters.\n\nNote: parameters is supported from Chrome 132.", + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + }, "RecoveryAddressType": { "type": "string", "title": "RecoveryAddressType must not exceed 16 characters as that is the limitation in the SQL Schema." }, "UUID": {"type": "string", "format": "uuid4"}, + "UpdateFedcmFlowBody": { + "type": "object", + "required": [ + "token", + "csrf_token" + ], + "properties": { + "csrf_token": { + "description": "CSRFToken is the anti-CSRF token.", + "type": "string" + }, + "nonce": { + "description": "Nonce is the nonce that was used in the `navigator.credentials.get` call. If\nspecified, it must match the `nonce` claim in the token.", + "type": "string" + }, + "token": { + "description": "Token contains the result of `navigator.credentials.get`.", + "type": "string" + } + } + }, "authenticatorAssuranceLevel": { "description": "The authenticator assurance level can be one of \"aal1\", \"aal2\", or \"aal3\". A higher number means that it is harder\nfor an attacker to compromise the account.\n\nGenerally, \"aal1\" implies that one authentication factor was used while AAL2 implies that two factors (e.g.\npassword + TOTP) have been used.\n\nTo learn more about these levels please head over to: https://www.ory.sh/kratos/docs/concepts/credentials", "type": "string", @@ -3825,6 +3991,22 @@ "format": "int64", "title": "A Message's Type" }, + "createFedcmFlowResponse": { + "description": "Contains a list of all available FedCM providers.", + "type": "object", + "title": "CreateFedcmFlowResponse", + "properties": { + "csrf_token": { + "type": "string" + }, + "providers": { + "type": "array", + "items": { + "$ref": "#/definitions/Provider" + } + } + } + }, "createIdentityBody": { "description": "Create Identity Body", "type": "object", diff --git a/x/router.go b/x/router.go index 6f4cb3609069..06c224c0a37f 100644 --- a/x/router.go +++ b/x/router.go @@ -105,3 +105,8 @@ func (r *RouterAdmin) Handler(method, publicPath string, handler http.Handler) { func (r *RouterAdmin) Lookup(method, publicPath string) { r.Router.Lookup(method, path.Join(AdminPrefix, publicPath)) } + +type HandlerRegistrar interface { + RegisterPublicRoutes(public *RouterPublic) + RegisterAdminRoutes(admin *RouterAdmin) +}