mirror of
				https://github.com/KevinMidboe/immich.git
				synced 2025-10-29 17:40:28 +00:00 
			
		
		
		
	refactor(web,server): use feature flags for oauth (#3928)
* refactor: oauth to use feature flags * chore: open api * chore: e2e test for authorize endpoint
This commit is contained in:
		
							
								
								
									
										104
									
								
								cli/src/api/open-api/api.ts
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										104
									
								
								cli/src/api/open-api/api.ts
									
									
									
										generated
									
									
									
								
							| @@ -1861,6 +1861,19 @@ export const ModelType = { | |||||||
| export type ModelType = typeof ModelType[keyof typeof ModelType]; | export type ModelType = typeof ModelType[keyof typeof ModelType]; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | /** | ||||||
|  |  *  | ||||||
|  |  * @export | ||||||
|  |  * @interface OAuthAuthorizeResponseDto | ||||||
|  |  */ | ||||||
|  | export interface OAuthAuthorizeResponseDto { | ||||||
|  |     /** | ||||||
|  |      *  | ||||||
|  |      * @type {string} | ||||||
|  |      * @memberof OAuthAuthorizeResponseDto | ||||||
|  |      */ | ||||||
|  |     'url': string; | ||||||
|  | } | ||||||
| /** | /** | ||||||
|  *  |  *  | ||||||
|  * @export |  * @export | ||||||
| @@ -8890,6 +8903,41 @@ export class JobApi extends BaseAPI { | |||||||
|  */ |  */ | ||||||
| export const OAuthApiAxiosParamCreator = function (configuration?: Configuration) { | export const OAuthApiAxiosParamCreator = function (configuration?: Configuration) { | ||||||
|     return { |     return { | ||||||
|  |         /** | ||||||
|  |          *  | ||||||
|  |          * @param {OAuthConfigDto} oAuthConfigDto  | ||||||
|  |          * @param {*} [options] Override http request option. | ||||||
|  |          * @throws {RequiredError} | ||||||
|  |          */ | ||||||
|  |         authorizeOAuth: async (oAuthConfigDto: OAuthConfigDto, options: AxiosRequestConfig = {}): Promise<RequestArgs> => { | ||||||
|  |             // verify required parameter 'oAuthConfigDto' is not null or undefined
 | ||||||
|  |             assertParamExists('authorizeOAuth', 'oAuthConfigDto', oAuthConfigDto) | ||||||
|  |             const localVarPath = `/oauth/authorize`; | ||||||
|  |             // use dummy base URL string because the URL constructor only accepts absolute URLs.
 | ||||||
|  |             const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); | ||||||
|  |             let baseOptions; | ||||||
|  |             if (configuration) { | ||||||
|  |                 baseOptions = configuration.baseOptions; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; | ||||||
|  |             const localVarHeaderParameter = {} as any; | ||||||
|  |             const localVarQueryParameter = {} as any; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |      | ||||||
|  |             localVarHeaderParameter['Content-Type'] = 'application/json'; | ||||||
|  | 
 | ||||||
|  |             setSearchParams(localVarUrlObj, localVarQueryParameter); | ||||||
|  |             let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; | ||||||
|  |             localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; | ||||||
|  |             localVarRequestOptions.data = serializeDataIfNeeded(oAuthConfigDto, localVarRequestOptions, configuration) | ||||||
|  | 
 | ||||||
|  |             return { | ||||||
|  |                 url: toPathString(localVarUrlObj), | ||||||
|  |                 options: localVarRequestOptions, | ||||||
|  |             }; | ||||||
|  |         }, | ||||||
|         /** |         /** | ||||||
|          *  |          *  | ||||||
|          * @param {OAuthCallbackDto} oAuthCallbackDto  |          * @param {OAuthCallbackDto} oAuthCallbackDto  | ||||||
| @@ -8926,9 +8974,10 @@ export const OAuthApiAxiosParamCreator = function (configuration?: Configuration | |||||||
|             }; |             }; | ||||||
|         }, |         }, | ||||||
|         /** |         /** | ||||||
|          *  |          * @deprecated use feature flags and /oauth/authorize | ||||||
|          * @param {OAuthConfigDto} oAuthConfigDto  |          * @param {OAuthConfigDto} oAuthConfigDto  | ||||||
|          * @param {*} [options] Override http request option. |          * @param {*} [options] Override http request option. | ||||||
|  |          * @deprecated | ||||||
|          * @throws {RequiredError} |          * @throws {RequiredError} | ||||||
|          */ |          */ | ||||||
|         generateConfig: async (oAuthConfigDto: OAuthConfigDto, options: AxiosRequestConfig = {}): Promise<RequestArgs> => { |         generateConfig: async (oAuthConfigDto: OAuthConfigDto, options: AxiosRequestConfig = {}): Promise<RequestArgs> => { | ||||||
| @@ -9081,6 +9130,16 @@ export const OAuthApiAxiosParamCreator = function (configuration?: Configuration | |||||||
| export const OAuthApiFp = function(configuration?: Configuration) { | export const OAuthApiFp = function(configuration?: Configuration) { | ||||||
|     const localVarAxiosParamCreator = OAuthApiAxiosParamCreator(configuration) |     const localVarAxiosParamCreator = OAuthApiAxiosParamCreator(configuration) | ||||||
|     return { |     return { | ||||||
|  |         /** | ||||||
|  |          *  | ||||||
|  |          * @param {OAuthConfigDto} oAuthConfigDto  | ||||||
|  |          * @param {*} [options] Override http request option. | ||||||
|  |          * @throws {RequiredError} | ||||||
|  |          */ | ||||||
|  |         async authorizeOAuth(oAuthConfigDto: OAuthConfigDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<OAuthAuthorizeResponseDto>> { | ||||||
|  |             const localVarAxiosArgs = await localVarAxiosParamCreator.authorizeOAuth(oAuthConfigDto, options); | ||||||
|  |             return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); | ||||||
|  |         }, | ||||||
|         /** |         /** | ||||||
|          *  |          *  | ||||||
|          * @param {OAuthCallbackDto} oAuthCallbackDto  |          * @param {OAuthCallbackDto} oAuthCallbackDto  | ||||||
| @@ -9092,9 +9151,10 @@ export const OAuthApiFp = function(configuration?: Configuration) { | |||||||
|             return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); |             return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); | ||||||
|         }, |         }, | ||||||
|         /** |         /** | ||||||
|          *  |          * @deprecated use feature flags and /oauth/authorize | ||||||
|          * @param {OAuthConfigDto} oAuthConfigDto  |          * @param {OAuthConfigDto} oAuthConfigDto  | ||||||
|          * @param {*} [options] Override http request option. |          * @param {*} [options] Override http request option. | ||||||
|  |          * @deprecated | ||||||
|          * @throws {RequiredError} |          * @throws {RequiredError} | ||||||
|          */ |          */ | ||||||
|         async generateConfig(oAuthConfigDto: OAuthConfigDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<OAuthConfigResponseDto>> { |         async generateConfig(oAuthConfigDto: OAuthConfigDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<OAuthConfigResponseDto>> { | ||||||
| @@ -9139,6 +9199,15 @@ export const OAuthApiFp = function(configuration?: Configuration) { | |||||||
| export const OAuthApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) { | export const OAuthApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) { | ||||||
|     const localVarFp = OAuthApiFp(configuration) |     const localVarFp = OAuthApiFp(configuration) | ||||||
|     return { |     return { | ||||||
|  |         /** | ||||||
|  |          *  | ||||||
|  |          * @param {OAuthApiAuthorizeOAuthRequest} requestParameters Request parameters. | ||||||
|  |          * @param {*} [options] Override http request option. | ||||||
|  |          * @throws {RequiredError} | ||||||
|  |          */ | ||||||
|  |         authorizeOAuth(requestParameters: OAuthApiAuthorizeOAuthRequest, options?: AxiosRequestConfig): AxiosPromise<OAuthAuthorizeResponseDto> { | ||||||
|  |             return localVarFp.authorizeOAuth(requestParameters.oAuthConfigDto, options).then((request) => request(axios, basePath)); | ||||||
|  |         }, | ||||||
|         /** |         /** | ||||||
|          *  |          *  | ||||||
|          * @param {OAuthApiCallbackRequest} requestParameters Request parameters. |          * @param {OAuthApiCallbackRequest} requestParameters Request parameters. | ||||||
| @@ -9149,9 +9218,10 @@ export const OAuthApiFactory = function (configuration?: Configuration, basePath | |||||||
|             return localVarFp.callback(requestParameters.oAuthCallbackDto, options).then((request) => request(axios, basePath)); |             return localVarFp.callback(requestParameters.oAuthCallbackDto, options).then((request) => request(axios, basePath)); | ||||||
|         }, |         }, | ||||||
|         /** |         /** | ||||||
|          *  |          * @deprecated use feature flags and /oauth/authorize | ||||||
|          * @param {OAuthApiGenerateConfigRequest} requestParameters Request parameters. |          * @param {OAuthApiGenerateConfigRequest} requestParameters Request parameters. | ||||||
|          * @param {*} [options] Override http request option. |          * @param {*} [options] Override http request option. | ||||||
|  |          * @deprecated | ||||||
|          * @throws {RequiredError} |          * @throws {RequiredError} | ||||||
|          */ |          */ | ||||||
|         generateConfig(requestParameters: OAuthApiGenerateConfigRequest, options?: AxiosRequestConfig): AxiosPromise<OAuthConfigResponseDto> { |         generateConfig(requestParameters: OAuthApiGenerateConfigRequest, options?: AxiosRequestConfig): AxiosPromise<OAuthConfigResponseDto> { | ||||||
| @@ -9185,6 +9255,20 @@ export const OAuthApiFactory = function (configuration?: Configuration, basePath | |||||||
|     }; |     }; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | /** | ||||||
|  |  * Request parameters for authorizeOAuth operation in OAuthApi. | ||||||
|  |  * @export | ||||||
|  |  * @interface OAuthApiAuthorizeOAuthRequest | ||||||
|  |  */ | ||||||
|  | export interface OAuthApiAuthorizeOAuthRequest { | ||||||
|  |     /** | ||||||
|  |      *  | ||||||
|  |      * @type {OAuthConfigDto} | ||||||
|  |      * @memberof OAuthApiAuthorizeOAuth | ||||||
|  |      */ | ||||||
|  |     readonly oAuthConfigDto: OAuthConfigDto | ||||||
|  | } | ||||||
|  | 
 | ||||||
| /** | /** | ||||||
|  * Request parameters for callback operation in OAuthApi. |  * Request parameters for callback operation in OAuthApi. | ||||||
|  * @export |  * @export | ||||||
| @@ -9234,6 +9318,17 @@ export interface OAuthApiLinkRequest { | |||||||
|  * @extends {BaseAPI} |  * @extends {BaseAPI} | ||||||
|  */ |  */ | ||||||
| export class OAuthApi extends BaseAPI { | export class OAuthApi extends BaseAPI { | ||||||
|  |     /** | ||||||
|  |      *  | ||||||
|  |      * @param {OAuthApiAuthorizeOAuthRequest} requestParameters Request parameters. | ||||||
|  |      * @param {*} [options] Override http request option. | ||||||
|  |      * @throws {RequiredError} | ||||||
|  |      * @memberof OAuthApi | ||||||
|  |      */ | ||||||
|  |     public authorizeOAuth(requestParameters: OAuthApiAuthorizeOAuthRequest, options?: AxiosRequestConfig) { | ||||||
|  |         return OAuthApiFp(this.configuration).authorizeOAuth(requestParameters.oAuthConfigDto, options).then((request) => request(this.axios, this.basePath)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      *  |      *  | ||||||
|      * @param {OAuthApiCallbackRequest} requestParameters Request parameters. |      * @param {OAuthApiCallbackRequest} requestParameters Request parameters. | ||||||
| @@ -9246,9 +9341,10 @@ export class OAuthApi extends BaseAPI { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      *  |      * @deprecated use feature flags and /oauth/authorize | ||||||
|      * @param {OAuthApiGenerateConfigRequest} requestParameters Request parameters. |      * @param {OAuthApiGenerateConfigRequest} requestParameters Request parameters. | ||||||
|      * @param {*} [options] Override http request option. |      * @param {*} [options] Override http request option. | ||||||
|  |      * @deprecated | ||||||
|      * @throws {RequiredError} |      * @throws {RequiredError} | ||||||
|      * @memberof OAuthApi |      * @memberof OAuthApi | ||||||
|      */ |      */ | ||||||
|   | |||||||
							
								
								
									
										3
									
								
								mobile/openapi/.openapi-generator/FILES
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										3
									
								
								mobile/openapi/.openapi-generator/FILES
									
									
									
										generated
									
									
									
								
							| @@ -73,6 +73,7 @@ doc/MemoryLaneResponseDto.md | |||||||
| doc/MergePersonDto.md | doc/MergePersonDto.md | ||||||
| doc/ModelType.md | doc/ModelType.md | ||||||
| doc/OAuthApi.md | doc/OAuthApi.md | ||||||
|  | doc/OAuthAuthorizeResponseDto.md | ||||||
| doc/OAuthCallbackDto.md | doc/OAuthCallbackDto.md | ||||||
| doc/OAuthConfigDto.md | doc/OAuthConfigDto.md | ||||||
| doc/OAuthConfigResponseDto.md | doc/OAuthConfigResponseDto.md | ||||||
| @@ -225,6 +226,7 @@ lib/model/map_marker_response_dto.dart | |||||||
| lib/model/memory_lane_response_dto.dart | lib/model/memory_lane_response_dto.dart | ||||||
| lib/model/merge_person_dto.dart | lib/model/merge_person_dto.dart | ||||||
| lib/model/model_type.dart | lib/model/model_type.dart | ||||||
|  | lib/model/o_auth_authorize_response_dto.dart | ||||||
| lib/model/o_auth_callback_dto.dart | lib/model/o_auth_callback_dto.dart | ||||||
| lib/model/o_auth_config_dto.dart | lib/model/o_auth_config_dto.dart | ||||||
| lib/model/o_auth_config_response_dto.dart | lib/model/o_auth_config_response_dto.dart | ||||||
| @@ -352,6 +354,7 @@ test/memory_lane_response_dto_test.dart | |||||||
| test/merge_person_dto_test.dart | test/merge_person_dto_test.dart | ||||||
| test/model_type_test.dart | test/model_type_test.dart | ||||||
| test/o_auth_api_test.dart | test/o_auth_api_test.dart | ||||||
|  | test/o_auth_authorize_response_dto_test.dart | ||||||
| test/o_auth_callback_dto_test.dart | test/o_auth_callback_dto_test.dart | ||||||
| test/o_auth_config_dto_test.dart | test/o_auth_config_dto_test.dart | ||||||
| test/o_auth_config_response_dto_test.dart | test/o_auth_config_response_dto_test.dart | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								mobile/openapi/README.md
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								mobile/openapi/README.md
									
									
									
										generated
									
									
									
								
							| @@ -124,6 +124,7 @@ Class | Method | HTTP request | Description | |||||||
| *AuthenticationApi* | [**validateAccessToken**](doc//AuthenticationApi.md#validateaccesstoken) | **POST** /auth/validateToken |  | *AuthenticationApi* | [**validateAccessToken**](doc//AuthenticationApi.md#validateaccesstoken) | **POST** /auth/validateToken |  | ||||||
| *JobApi* | [**getAllJobsStatus**](doc//JobApi.md#getalljobsstatus) | **GET** /jobs |  | *JobApi* | [**getAllJobsStatus**](doc//JobApi.md#getalljobsstatus) | **GET** /jobs |  | ||||||
| *JobApi* | [**sendJobCommand**](doc//JobApi.md#sendjobcommand) | **PUT** /jobs/{id} |  | *JobApi* | [**sendJobCommand**](doc//JobApi.md#sendjobcommand) | **PUT** /jobs/{id} |  | ||||||
|  | *OAuthApi* | [**authorizeOAuth**](doc//OAuthApi.md#authorizeoauth) | **POST** /oauth/authorize |  | ||||||
| *OAuthApi* | [**callback**](doc//OAuthApi.md#callback) | **POST** /oauth/callback |  | *OAuthApi* | [**callback**](doc//OAuthApi.md#callback) | **POST** /oauth/callback |  | ||||||
| *OAuthApi* | [**generateConfig**](doc//OAuthApi.md#generateconfig) | **POST** /oauth/config |  | *OAuthApi* | [**generateConfig**](doc//OAuthApi.md#generateconfig) | **POST** /oauth/config |  | ||||||
| *OAuthApi* | [**link**](doc//OAuthApi.md#link) | **POST** /oauth/link |  | *OAuthApi* | [**link**](doc//OAuthApi.md#link) | **POST** /oauth/link |  | ||||||
| @@ -244,6 +245,7 @@ Class | Method | HTTP request | Description | |||||||
|  - [MemoryLaneResponseDto](doc//MemoryLaneResponseDto.md) |  - [MemoryLaneResponseDto](doc//MemoryLaneResponseDto.md) | ||||||
|  - [MergePersonDto](doc//MergePersonDto.md) |  - [MergePersonDto](doc//MergePersonDto.md) | ||||||
|  - [ModelType](doc//ModelType.md) |  - [ModelType](doc//ModelType.md) | ||||||
|  |  - [OAuthAuthorizeResponseDto](doc//OAuthAuthorizeResponseDto.md) | ||||||
|  - [OAuthCallbackDto](doc//OAuthCallbackDto.md) |  - [OAuthCallbackDto](doc//OAuthCallbackDto.md) | ||||||
|  - [OAuthConfigDto](doc//OAuthConfigDto.md) |  - [OAuthConfigDto](doc//OAuthConfigDto.md) | ||||||
|  - [OAuthConfigResponseDto](doc//OAuthConfigResponseDto.md) |  - [OAuthConfigResponseDto](doc//OAuthConfigResponseDto.md) | ||||||
|   | |||||||
							
								
								
									
										44
									
								
								mobile/openapi/doc/OAuthApi.md
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										44
									
								
								mobile/openapi/doc/OAuthApi.md
									
									
									
										generated
									
									
									
								
							| @@ -9,6 +9,7 @@ All URIs are relative to */api* | |||||||
| 
 | 
 | ||||||
| Method | HTTP request | Description | Method | HTTP request | Description | ||||||
| ------------- | ------------- | ------------- | ------------- | ------------- | ------------- | ||||||
|  | [**authorizeOAuth**](OAuthApi.md#authorizeoauth) | **POST** /oauth/authorize |  | ||||||
| [**callback**](OAuthApi.md#callback) | **POST** /oauth/callback |  | [**callback**](OAuthApi.md#callback) | **POST** /oauth/callback |  | ||||||
| [**generateConfig**](OAuthApi.md#generateconfig) | **POST** /oauth/config |  | [**generateConfig**](OAuthApi.md#generateconfig) | **POST** /oauth/config |  | ||||||
| [**link**](OAuthApi.md#link) | **POST** /oauth/link |  | [**link**](OAuthApi.md#link) | **POST** /oauth/link |  | ||||||
| @@ -16,6 +17,47 @@ Method | HTTP request | Description | |||||||
| [**unlink**](OAuthApi.md#unlink) | **POST** /oauth/unlink |  | [**unlink**](OAuthApi.md#unlink) | **POST** /oauth/unlink |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | # **authorizeOAuth** | ||||||
|  | > OAuthAuthorizeResponseDto authorizeOAuth(oAuthConfigDto) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | ### Example | ||||||
|  | ```dart | ||||||
|  | import 'package:openapi/api.dart'; | ||||||
|  | 
 | ||||||
|  | final api_instance = OAuthApi(); | ||||||
|  | final oAuthConfigDto = OAuthConfigDto(); // OAuthConfigDto |  | ||||||
|  | 
 | ||||||
|  | try { | ||||||
|  |     final result = api_instance.authorizeOAuth(oAuthConfigDto); | ||||||
|  |     print(result); | ||||||
|  | } catch (e) { | ||||||
|  |     print('Exception when calling OAuthApi->authorizeOAuth: $e\n'); | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ### Parameters | ||||||
|  | 
 | ||||||
|  | Name | Type | Description  | Notes | ||||||
|  | ------------- | ------------- | ------------- | ------------- | ||||||
|  |  **oAuthConfigDto** | [**OAuthConfigDto**](OAuthConfigDto.md)|  |  | ||||||
|  | 
 | ||||||
|  | ### Return type | ||||||
|  | 
 | ||||||
|  | [**OAuthAuthorizeResponseDto**](OAuthAuthorizeResponseDto.md) | ||||||
|  | 
 | ||||||
|  | ### Authorization | ||||||
|  | 
 | ||||||
|  | No authorization required | ||||||
|  | 
 | ||||||
|  | ### HTTP request headers | ||||||
|  | 
 | ||||||
|  |  - **Content-Type**: application/json | ||||||
|  |  - **Accept**: application/json | ||||||
|  | 
 | ||||||
|  | [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) | ||||||
|  | 
 | ||||||
| # **callback** | # **callback** | ||||||
| > LoginResponseDto callback(oAuthCallbackDto) | > LoginResponseDto callback(oAuthCallbackDto) | ||||||
| 
 | 
 | ||||||
| @@ -62,6 +104,8 @@ No authorization required | |||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @deprecated use feature flags and /oauth/authorize | ||||||
|  | 
 | ||||||
| ### Example | ### Example | ||||||
| ```dart | ```dart | ||||||
| import 'package:openapi/api.dart'; | import 'package:openapi/api.dart'; | ||||||
|   | |||||||
							
								
								
									
										15
									
								
								mobile/openapi/doc/OAuthAuthorizeResponseDto.md
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								mobile/openapi/doc/OAuthAuthorizeResponseDto.md
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | |||||||
|  | # openapi.model.OAuthAuthorizeResponseDto | ||||||
|  | 
 | ||||||
|  | ## Load the model package | ||||||
|  | ```dart | ||||||
|  | import 'package:openapi/api.dart'; | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ## Properties | ||||||
|  | Name | Type | Description | Notes | ||||||
|  | ------------ | ------------- | ------------- | ------------- | ||||||
|  | **url** | **String** |  |  | ||||||
|  | 
 | ||||||
|  | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
							
								
								
									
										1
									
								
								mobile/openapi/lib/api.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1
									
								
								mobile/openapi/lib/api.dart
									
									
									
										generated
									
									
									
								
							| @@ -107,6 +107,7 @@ part 'model/map_marker_response_dto.dart'; | |||||||
| part 'model/memory_lane_response_dto.dart'; | part 'model/memory_lane_response_dto.dart'; | ||||||
| part 'model/merge_person_dto.dart'; | part 'model/merge_person_dto.dart'; | ||||||
| part 'model/model_type.dart'; | part 'model/model_type.dart'; | ||||||
|  | part 'model/o_auth_authorize_response_dto.dart'; | ||||||
| part 'model/o_auth_callback_dto.dart'; | part 'model/o_auth_callback_dto.dart'; | ||||||
| part 'model/o_auth_config_dto.dart'; | part 'model/o_auth_config_dto.dart'; | ||||||
| part 'model/o_auth_config_response_dto.dart'; | part 'model/o_auth_config_response_dto.dart'; | ||||||
|   | |||||||
							
								
								
									
										54
									
								
								mobile/openapi/lib/api/o_auth_api.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										54
									
								
								mobile/openapi/lib/api/o_auth_api.dart
									
									
									
										generated
									
									
									
								
							| @@ -16,6 +16,53 @@ class OAuthApi { | |||||||
| 
 | 
 | ||||||
|   final ApiClient apiClient; |   final ApiClient apiClient; | ||||||
| 
 | 
 | ||||||
|  |   /// Performs an HTTP 'POST /oauth/authorize' operation and returns the [Response]. | ||||||
|  |   /// Parameters: | ||||||
|  |   /// | ||||||
|  |   /// * [OAuthConfigDto] oAuthConfigDto (required): | ||||||
|  |   Future<Response> authorizeOAuthWithHttpInfo(OAuthConfigDto oAuthConfigDto,) async { | ||||||
|  |     // ignore: prefer_const_declarations | ||||||
|  |     final path = r'/oauth/authorize'; | ||||||
|  | 
 | ||||||
|  |     // ignore: prefer_final_locals | ||||||
|  |     Object? postBody = oAuthConfigDto; | ||||||
|  | 
 | ||||||
|  |     final queryParams = <QueryParam>[]; | ||||||
|  |     final headerParams = <String, String>{}; | ||||||
|  |     final formParams = <String, String>{}; | ||||||
|  | 
 | ||||||
|  |     const contentTypes = <String>['application/json']; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     return apiClient.invokeAPI( | ||||||
|  |       path, | ||||||
|  |       'POST', | ||||||
|  |       queryParams, | ||||||
|  |       postBody, | ||||||
|  |       headerParams, | ||||||
|  |       formParams, | ||||||
|  |       contentTypes.isEmpty ? null : contentTypes.first, | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /// Parameters: | ||||||
|  |   /// | ||||||
|  |   /// * [OAuthConfigDto] oAuthConfigDto (required): | ||||||
|  |   Future<OAuthAuthorizeResponseDto?> authorizeOAuth(OAuthConfigDto oAuthConfigDto,) async { | ||||||
|  |     final response = await authorizeOAuthWithHttpInfo(oAuthConfigDto,); | ||||||
|  |     if (response.statusCode >= HttpStatus.badRequest) { | ||||||
|  |       throw ApiException(response.statusCode, await _decodeBodyBytes(response)); | ||||||
|  |     } | ||||||
|  |     // When a remote server returns no body with a status of 204, we shall not decode it. | ||||||
|  |     // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" | ||||||
|  |     // FormatException when trying to decode an empty string. | ||||||
|  |     if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { | ||||||
|  |       return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'OAuthAuthorizeResponseDto',) as OAuthAuthorizeResponseDto; | ||||||
|  |      | ||||||
|  |     } | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   /// Performs an HTTP 'POST /oauth/callback' operation and returns the [Response]. |   /// Performs an HTTP 'POST /oauth/callback' operation and returns the [Response]. | ||||||
|   /// Parameters: |   /// Parameters: | ||||||
|   /// |   /// | ||||||
| @@ -63,7 +110,10 @@ class OAuthApi { | |||||||
|     return null; |     return null; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /// Performs an HTTP 'POST /oauth/config' operation and returns the [Response]. |   /// @deprecated use feature flags and /oauth/authorize | ||||||
|  |   /// | ||||||
|  |   /// Note: This method returns the HTTP [Response]. | ||||||
|  |   /// | ||||||
|   /// Parameters: |   /// Parameters: | ||||||
|   /// |   /// | ||||||
|   /// * [OAuthConfigDto] oAuthConfigDto (required): |   /// * [OAuthConfigDto] oAuthConfigDto (required): | ||||||
| @@ -92,6 +142,8 @@ class OAuthApi { | |||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   /// @deprecated use feature flags and /oauth/authorize | ||||||
|  |   /// | ||||||
|   /// Parameters: |   /// Parameters: | ||||||
|   /// |   /// | ||||||
|   /// * [OAuthConfigDto] oAuthConfigDto (required): |   /// * [OAuthConfigDto] oAuthConfigDto (required): | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								mobile/openapi/lib/api_client.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								mobile/openapi/lib/api_client.dart
									
									
									
										generated
									
									
									
								
							| @@ -307,6 +307,8 @@ class ApiClient { | |||||||
|           return MergePersonDto.fromJson(value); |           return MergePersonDto.fromJson(value); | ||||||
|         case 'ModelType': |         case 'ModelType': | ||||||
|           return ModelTypeTypeTransformer().decode(value); |           return ModelTypeTypeTransformer().decode(value); | ||||||
|  |         case 'OAuthAuthorizeResponseDto': | ||||||
|  |           return OAuthAuthorizeResponseDto.fromJson(value); | ||||||
|         case 'OAuthCallbackDto': |         case 'OAuthCallbackDto': | ||||||
|           return OAuthCallbackDto.fromJson(value); |           return OAuthCallbackDto.fromJson(value); | ||||||
|         case 'OAuthConfigDto': |         case 'OAuthConfigDto': | ||||||
|   | |||||||
							
								
								
									
										98
									
								
								mobile/openapi/lib/model/o_auth_authorize_response_dto.dart
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								mobile/openapi/lib/model/o_auth_authorize_response_dto.dart
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,98 @@ | |||||||
|  | // | ||||||
|  | // AUTO-GENERATED FILE, DO NOT MODIFY! | ||||||
|  | // | ||||||
|  | // @dart=2.12 | ||||||
|  | 
 | ||||||
|  | // ignore_for_file: unused_element, unused_import | ||||||
|  | // ignore_for_file: always_put_required_named_parameters_first | ||||||
|  | // ignore_for_file: constant_identifier_names | ||||||
|  | // ignore_for_file: lines_longer_than_80_chars | ||||||
|  | 
 | ||||||
|  | part of openapi.api; | ||||||
|  | 
 | ||||||
|  | class OAuthAuthorizeResponseDto { | ||||||
|  |   /// Returns a new [OAuthAuthorizeResponseDto] instance. | ||||||
|  |   OAuthAuthorizeResponseDto({ | ||||||
|  |     required this.url, | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   String url; | ||||||
|  | 
 | ||||||
|  |   @override | ||||||
|  |   bool operator ==(Object other) => identical(this, other) || other is OAuthAuthorizeResponseDto && | ||||||
|  |      other.url == url; | ||||||
|  | 
 | ||||||
|  |   @override | ||||||
|  |   int get hashCode => | ||||||
|  |     // ignore: unnecessary_parenthesis | ||||||
|  |     (url.hashCode); | ||||||
|  | 
 | ||||||
|  |   @override | ||||||
|  |   String toString() => 'OAuthAuthorizeResponseDto[url=$url]'; | ||||||
|  | 
 | ||||||
|  |   Map<String, dynamic> toJson() { | ||||||
|  |     final json = <String, dynamic>{}; | ||||||
|  |       json[r'url'] = this.url; | ||||||
|  |     return json; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /// Returns a new [OAuthAuthorizeResponseDto] instance and imports its values from | ||||||
|  |   /// [value] if it's a [Map], null otherwise. | ||||||
|  |   // ignore: prefer_constructors_over_static_methods | ||||||
|  |   static OAuthAuthorizeResponseDto? fromJson(dynamic value) { | ||||||
|  |     if (value is Map) { | ||||||
|  |       final json = value.cast<String, dynamic>(); | ||||||
|  | 
 | ||||||
|  |       return OAuthAuthorizeResponseDto( | ||||||
|  |         url: mapValueOfType<String>(json, r'url')!, | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static List<OAuthAuthorizeResponseDto> listFromJson(dynamic json, {bool growable = false,}) { | ||||||
|  |     final result = <OAuthAuthorizeResponseDto>[]; | ||||||
|  |     if (json is List && json.isNotEmpty) { | ||||||
|  |       for (final row in json) { | ||||||
|  |         final value = OAuthAuthorizeResponseDto.fromJson(row); | ||||||
|  |         if (value != null) { | ||||||
|  |           result.add(value); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return result.toList(growable: growable); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static Map<String, OAuthAuthorizeResponseDto> mapFromJson(dynamic json) { | ||||||
|  |     final map = <String, OAuthAuthorizeResponseDto>{}; | ||||||
|  |     if (json is Map && json.isNotEmpty) { | ||||||
|  |       json = json.cast<String, dynamic>(); // ignore: parameter_assignments | ||||||
|  |       for (final entry in json.entries) { | ||||||
|  |         final value = OAuthAuthorizeResponseDto.fromJson(entry.value); | ||||||
|  |         if (value != null) { | ||||||
|  |           map[entry.key] = value; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return map; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   // maps a json object with a list of OAuthAuthorizeResponseDto-objects as value to a dart map | ||||||
|  |   static Map<String, List<OAuthAuthorizeResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) { | ||||||
|  |     final map = <String, List<OAuthAuthorizeResponseDto>>{}; | ||||||
|  |     if (json is Map && json.isNotEmpty) { | ||||||
|  |       // ignore: parameter_assignments | ||||||
|  |       json = json.cast<String, dynamic>(); | ||||||
|  |       for (final entry in json.entries) { | ||||||
|  |         map[entry.key] = OAuthAuthorizeResponseDto.listFromJson(entry.value, growable: growable,); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return map; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /// The list of required keys that must be present in a JSON. | ||||||
|  |   static const requiredKeys = <String>{ | ||||||
|  |     'url', | ||||||
|  |   }; | ||||||
|  | } | ||||||
|  | 
 | ||||||
							
								
								
									
										7
									
								
								mobile/openapi/test/o_auth_api_test.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										7
									
								
								mobile/openapi/test/o_auth_api_test.dart
									
									
									
										generated
									
									
									
								
							| @@ -17,11 +17,18 @@ void main() { | |||||||
|   // final instance = OAuthApi(); |   // final instance = OAuthApi(); | ||||||
| 
 | 
 | ||||||
|   group('tests for OAuthApi', () { |   group('tests for OAuthApi', () { | ||||||
|  |     //Future<OAuthAuthorizeResponseDto> authorizeOAuth(OAuthConfigDto oAuthConfigDto) async | ||||||
|  |     test('test authorizeOAuth', () async { | ||||||
|  |       // TODO | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|     //Future<LoginResponseDto> callback(OAuthCallbackDto oAuthCallbackDto) async |     //Future<LoginResponseDto> callback(OAuthCallbackDto oAuthCallbackDto) async | ||||||
|     test('test callback', () async { |     test('test callback', () async { | ||||||
|       // TODO |       // TODO | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|  |     // @deprecated use feature flags and /oauth/authorize | ||||||
|  |     // | ||||||
|     //Future<OAuthConfigResponseDto> generateConfig(OAuthConfigDto oAuthConfigDto) async |     //Future<OAuthConfigResponseDto> generateConfig(OAuthConfigDto oAuthConfigDto) async | ||||||
|     test('test generateConfig', () async { |     test('test generateConfig', () async { | ||||||
|       // TODO |       // TODO | ||||||
|   | |||||||
							
								
								
									
										27
									
								
								mobile/openapi/test/o_auth_authorize_response_dto_test.dart
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								mobile/openapi/test/o_auth_authorize_response_dto_test.dart
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | |||||||
|  | // | ||||||
|  | // AUTO-GENERATED FILE, DO NOT MODIFY! | ||||||
|  | // | ||||||
|  | // @dart=2.12 | ||||||
|  | 
 | ||||||
|  | // ignore_for_file: unused_element, unused_import | ||||||
|  | // ignore_for_file: always_put_required_named_parameters_first | ||||||
|  | // ignore_for_file: constant_identifier_names | ||||||
|  | // ignore_for_file: lines_longer_than_80_chars | ||||||
|  | 
 | ||||||
|  | import 'package:openapi/api.dart'; | ||||||
|  | import 'package:test/test.dart'; | ||||||
|  | 
 | ||||||
|  | // tests for OAuthAuthorizeResponseDto | ||||||
|  | void main() { | ||||||
|  |   // final instance = OAuthAuthorizeResponseDto(); | ||||||
|  | 
 | ||||||
|  |   group('test OAuthAuthorizeResponseDto', () { | ||||||
|  |     // String url | ||||||
|  |     test('to test the property `url`', () async { | ||||||
|  |       // TODO | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  | } | ||||||
| @@ -2477,6 +2477,37 @@ | |||||||
|         ] |         ] | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|  |     "/oauth/authorize": { | ||||||
|  |       "post": { | ||||||
|  |         "operationId": "authorizeOAuth", | ||||||
|  |         "parameters": [], | ||||||
|  |         "requestBody": { | ||||||
|  |           "content": { | ||||||
|  |             "application/json": { | ||||||
|  |               "schema": { | ||||||
|  |                 "$ref": "#/components/schemas/OAuthConfigDto" | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |           }, | ||||||
|  |           "required": true | ||||||
|  |         }, | ||||||
|  |         "responses": { | ||||||
|  |           "201": { | ||||||
|  |             "content": { | ||||||
|  |               "application/json": { | ||||||
|  |                 "schema": { | ||||||
|  |                   "$ref": "#/components/schemas/OAuthAuthorizeResponseDto" | ||||||
|  |                 } | ||||||
|  |               } | ||||||
|  |             }, | ||||||
|  |             "description": "" | ||||||
|  |           } | ||||||
|  |         }, | ||||||
|  |         "tags": [ | ||||||
|  |           "OAuth" | ||||||
|  |         ] | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|     "/oauth/callback": { |     "/oauth/callback": { | ||||||
|       "post": { |       "post": { | ||||||
|         "operationId": "callback", |         "operationId": "callback", | ||||||
| @@ -2510,6 +2541,8 @@ | |||||||
|     }, |     }, | ||||||
|     "/oauth/config": { |     "/oauth/config": { | ||||||
|       "post": { |       "post": { | ||||||
|  |         "deprecated": true, | ||||||
|  |         "description": "@deprecated use feature flags and /oauth/authorize", | ||||||
|         "operationId": "generateConfig", |         "operationId": "generateConfig", | ||||||
|         "parameters": [], |         "parameters": [], | ||||||
|         "requestBody": { |         "requestBody": { | ||||||
| @@ -6202,6 +6235,17 @@ | |||||||
|         ], |         ], | ||||||
|         "type": "string" |         "type": "string" | ||||||
|       }, |       }, | ||||||
|  |       "OAuthAuthorizeResponseDto": { | ||||||
|  |         "properties": { | ||||||
|  |           "url": { | ||||||
|  |             "type": "string" | ||||||
|  |           } | ||||||
|  |         }, | ||||||
|  |         "required": [ | ||||||
|  |           "url" | ||||||
|  |         ], | ||||||
|  |         "type": "object" | ||||||
|  |       }, | ||||||
|       "OAuthCallbackDto": { |       "OAuthCallbackDto": { | ||||||
|         "properties": { |         "properties": { | ||||||
|           "url": { |           "url": { | ||||||
|   | |||||||
| @@ -1,5 +1,12 @@ | |||||||
| import { SystemConfig, UserEntity } from '@app/infra/entities'; | import { SystemConfig, UserEntity } from '@app/infra/entities'; | ||||||
| import { BadRequestException, Inject, Injectable, Logger, UnauthorizedException } from '@nestjs/common'; | import { | ||||||
|  |   BadRequestException, | ||||||
|  |   Inject, | ||||||
|  |   Injectable, | ||||||
|  |   InternalServerErrorException, | ||||||
|  |   Logger, | ||||||
|  |   UnauthorizedException, | ||||||
|  | } from '@nestjs/common'; | ||||||
| import cookieParser from 'cookie'; | import cookieParser from 'cookie'; | ||||||
| import { IncomingHttpHeaders } from 'http'; | import { IncomingHttpHeaders } from 'http'; | ||||||
| import { DateTime } from 'luxon'; | import { DateTime } from 'luxon'; | ||||||
| @@ -27,6 +34,7 @@ import { | |||||||
|   mapAdminSignupResponse, |   mapAdminSignupResponse, | ||||||
|   mapLoginResponse, |   mapLoginResponse, | ||||||
|   mapUserToken, |   mapUserToken, | ||||||
|  |   OAuthAuthorizeResponseDto, | ||||||
|   OAuthConfigResponseDto, |   OAuthConfigResponseDto, | ||||||
| } from './response-dto'; | } from './response-dto'; | ||||||
| import { IUserTokenRepository } from './user-token.repository'; | import { IUserTokenRepository } from './user-token.repository'; | ||||||
| @@ -201,6 +209,22 @@ export class AuthService { | |||||||
|     return { ...response, buttonText, url, autoLaunch }; |     return { ...response, buttonText, url, autoLaunch }; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   async authorize(dto: OAuthConfigDto): Promise<OAuthAuthorizeResponseDto> { | ||||||
|  |     const config = await this.configCore.getConfig(); | ||||||
|  |     if (!config.oauth.enabled) { | ||||||
|  |       throw new BadRequestException('OAuth is not enabled'); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     const client = await this.getOAuthClient(config); | ||||||
|  |     const url = await client.authorizationUrl({ | ||||||
|  |       redirect_uri: this.normalize(config, dto.redirectUri), | ||||||
|  |       scope: config.oauth.scope, | ||||||
|  |       state: generators.state(), | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     return { url }; | ||||||
|  |   } | ||||||
|  |  | ||||||
|   async callback( |   async callback( | ||||||
|     dto: OAuthCallbackDto, |     dto: OAuthCallbackDto, | ||||||
|     loginDetails: LoginDetails, |     loginDetails: LoginDetails, | ||||||
| @@ -280,8 +304,13 @@ export class AuthService { | |||||||
|     const redirectUri = this.normalize(config, url.split('?')[0]); |     const redirectUri = this.normalize(config, url.split('?')[0]); | ||||||
|     const client = await this.getOAuthClient(config); |     const client = await this.getOAuthClient(config); | ||||||
|     const params = client.callbackParams(url); |     const params = client.callbackParams(url); | ||||||
|  |     try { | ||||||
|       const tokens = await client.callback(redirectUri, params, { state: params.state }); |       const tokens = await client.callback(redirectUri, params, { state: params.state }); | ||||||
|       return client.userinfo<OAuthProfile>(tokens.access_token || ''); |       return client.userinfo<OAuthProfile>(tokens.access_token || ''); | ||||||
|  |     } catch (error: Error | any) { | ||||||
|  |       this.logger.error(`Unable to complete OAuth login: ${error}`, error?.stack); | ||||||
|  |       throw new InternalServerErrorException(`Unable to complete OAuth login: ${error}`, { cause: error }); | ||||||
|  |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   private async getOAuthClient(config: SystemConfig) { |   private async getOAuthClient(config: SystemConfig) { | ||||||
|   | |||||||
| @@ -1,9 +1,7 @@ | |||||||
| import { ApiProperty } from '@nestjs/swagger'; |  | ||||||
| import { IsNotEmpty, IsString } from 'class-validator'; | import { IsNotEmpty, IsString } from 'class-validator'; | ||||||
|  |  | ||||||
| export class OAuthConfigDto { | export class OAuthConfigDto { | ||||||
|   @IsNotEmpty() |   @IsNotEmpty() | ||||||
|   @IsString() |   @IsString() | ||||||
|   @ApiProperty() |  | ||||||
|   redirectUri!: string; |   redirectUri!: string; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -5,3 +5,7 @@ export class OAuthConfigResponseDto { | |||||||
|   buttonText?: string; |   buttonText?: string; | ||||||
|   autoLaunch?: boolean; |   autoLaunch?: boolean; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | export class OAuthAuthorizeResponseDto { | ||||||
|  |   url!: string; | ||||||
|  | } | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ import { | |||||||
|   AuthUserDto, |   AuthUserDto, | ||||||
|   LoginDetails, |   LoginDetails, | ||||||
|   LoginResponseDto, |   LoginResponseDto, | ||||||
|  |   OAuthAuthorizeResponseDto, | ||||||
|   OAuthCallbackDto, |   OAuthCallbackDto, | ||||||
|   OAuthConfigDto, |   OAuthConfigDto, | ||||||
|   OAuthConfigResponseDto, |   OAuthConfigResponseDto, | ||||||
| @@ -31,12 +32,19 @@ export class OAuthController { | |||||||
|     }; |     }; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   /** @deprecated use feature flags and /oauth/authorize */ | ||||||
|   @PublicRoute() |   @PublicRoute() | ||||||
|   @Post('config') |   @Post('config') | ||||||
|   generateConfig(@Body() dto: OAuthConfigDto): Promise<OAuthConfigResponseDto> { |   generateConfig(@Body() dto: OAuthConfigDto): Promise<OAuthConfigResponseDto> { | ||||||
|     return this.service.generateConfig(dto); |     return this.service.generateConfig(dto); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   @PublicRoute() | ||||||
|  |   @Post('authorize') | ||||||
|  |   authorizeOAuth(@Body() dto: OAuthConfigDto): Promise<OAuthAuthorizeResponseDto> { | ||||||
|  |     return this.service.authorize(dto); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   @PublicRoute() |   @PublicRoute() | ||||||
|   @Post('callback') |   @Post('callback') | ||||||
|   async callback( |   async callback( | ||||||
|   | |||||||
							
								
								
									
										42
									
								
								server/test/e2e/oauth.e2e-spec.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								server/test/e2e/oauth.e2e-spec.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | |||||||
|  | import { AppModule, OAuthController } from '@app/immich'; | ||||||
|  | import { INestApplication } from '@nestjs/common'; | ||||||
|  | import { Test, TestingModule } from '@nestjs/testing'; | ||||||
|  | import request from 'supertest'; | ||||||
|  | import { errorStub } from '../fixtures'; | ||||||
|  | import { api, db } from '../test-utils'; | ||||||
|  |  | ||||||
|  | describe(`${OAuthController.name} (e2e)`, () => { | ||||||
|  |   let app: INestApplication; | ||||||
|  |   let server: any; | ||||||
|  |  | ||||||
|  |   beforeAll(async () => { | ||||||
|  |     const moduleFixture: TestingModule = await Test.createTestingModule({ | ||||||
|  |       imports: [AppModule], | ||||||
|  |     }).compile(); | ||||||
|  |  | ||||||
|  |     app = await moduleFixture.createNestApplication().init(); | ||||||
|  |     server = app.getHttpServer(); | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   beforeEach(async () => { | ||||||
|  |     await db.reset(); | ||||||
|  |     await api.adminSignUp(server); | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   afterAll(async () => { | ||||||
|  |     await db.disconnect(); | ||||||
|  |     await app.close(); | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   describe('POST /oauth/authorize', () => { | ||||||
|  |     beforeEach(async () => { | ||||||
|  |       await db.reset(); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     it(`should throw an error if a redirect uri is not provided`, async () => { | ||||||
|  |       const { status, body } = await request(server).post('/oauth/authorize').send({}); | ||||||
|  |       expect(status).toBe(400); | ||||||
|  |       expect(body).toEqual(errorStub.badRequest); | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
							
								
								
									
										104
									
								
								web/src/api/open-api/api.ts
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										104
									
								
								web/src/api/open-api/api.ts
									
									
									
										generated
									
									
									
								
							| @@ -1861,6 +1861,19 @@ export const ModelType = { | |||||||
| export type ModelType = typeof ModelType[keyof typeof ModelType]; | export type ModelType = typeof ModelType[keyof typeof ModelType]; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | /** | ||||||
|  |  *  | ||||||
|  |  * @export | ||||||
|  |  * @interface OAuthAuthorizeResponseDto | ||||||
|  |  */ | ||||||
|  | export interface OAuthAuthorizeResponseDto { | ||||||
|  |     /** | ||||||
|  |      *  | ||||||
|  |      * @type {string} | ||||||
|  |      * @memberof OAuthAuthorizeResponseDto | ||||||
|  |      */ | ||||||
|  |     'url': string; | ||||||
|  | } | ||||||
| /** | /** | ||||||
|  *  |  *  | ||||||
|  * @export |  * @export | ||||||
| @@ -8890,6 +8903,41 @@ export class JobApi extends BaseAPI { | |||||||
|  */ |  */ | ||||||
| export const OAuthApiAxiosParamCreator = function (configuration?: Configuration) { | export const OAuthApiAxiosParamCreator = function (configuration?: Configuration) { | ||||||
|     return { |     return { | ||||||
|  |         /** | ||||||
|  |          *  | ||||||
|  |          * @param {OAuthConfigDto} oAuthConfigDto  | ||||||
|  |          * @param {*} [options] Override http request option. | ||||||
|  |          * @throws {RequiredError} | ||||||
|  |          */ | ||||||
|  |         authorizeOAuth: async (oAuthConfigDto: OAuthConfigDto, options: AxiosRequestConfig = {}): Promise<RequestArgs> => { | ||||||
|  |             // verify required parameter 'oAuthConfigDto' is not null or undefined
 | ||||||
|  |             assertParamExists('authorizeOAuth', 'oAuthConfigDto', oAuthConfigDto) | ||||||
|  |             const localVarPath = `/oauth/authorize`; | ||||||
|  |             // use dummy base URL string because the URL constructor only accepts absolute URLs.
 | ||||||
|  |             const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); | ||||||
|  |             let baseOptions; | ||||||
|  |             if (configuration) { | ||||||
|  |                 baseOptions = configuration.baseOptions; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; | ||||||
|  |             const localVarHeaderParameter = {} as any; | ||||||
|  |             const localVarQueryParameter = {} as any; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |      | ||||||
|  |             localVarHeaderParameter['Content-Type'] = 'application/json'; | ||||||
|  | 
 | ||||||
|  |             setSearchParams(localVarUrlObj, localVarQueryParameter); | ||||||
|  |             let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; | ||||||
|  |             localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; | ||||||
|  |             localVarRequestOptions.data = serializeDataIfNeeded(oAuthConfigDto, localVarRequestOptions, configuration) | ||||||
|  | 
 | ||||||
|  |             return { | ||||||
|  |                 url: toPathString(localVarUrlObj), | ||||||
|  |                 options: localVarRequestOptions, | ||||||
|  |             }; | ||||||
|  |         }, | ||||||
|         /** |         /** | ||||||
|          *  |          *  | ||||||
|          * @param {OAuthCallbackDto} oAuthCallbackDto  |          * @param {OAuthCallbackDto} oAuthCallbackDto  | ||||||
| @@ -8926,9 +8974,10 @@ export const OAuthApiAxiosParamCreator = function (configuration?: Configuration | |||||||
|             }; |             }; | ||||||
|         }, |         }, | ||||||
|         /** |         /** | ||||||
|          *  |          * @deprecated use feature flags and /oauth/authorize | ||||||
|          * @param {OAuthConfigDto} oAuthConfigDto  |          * @param {OAuthConfigDto} oAuthConfigDto  | ||||||
|          * @param {*} [options] Override http request option. |          * @param {*} [options] Override http request option. | ||||||
|  |          * @deprecated | ||||||
|          * @throws {RequiredError} |          * @throws {RequiredError} | ||||||
|          */ |          */ | ||||||
|         generateConfig: async (oAuthConfigDto: OAuthConfigDto, options: AxiosRequestConfig = {}): Promise<RequestArgs> => { |         generateConfig: async (oAuthConfigDto: OAuthConfigDto, options: AxiosRequestConfig = {}): Promise<RequestArgs> => { | ||||||
| @@ -9081,6 +9130,16 @@ export const OAuthApiAxiosParamCreator = function (configuration?: Configuration | |||||||
| export const OAuthApiFp = function(configuration?: Configuration) { | export const OAuthApiFp = function(configuration?: Configuration) { | ||||||
|     const localVarAxiosParamCreator = OAuthApiAxiosParamCreator(configuration) |     const localVarAxiosParamCreator = OAuthApiAxiosParamCreator(configuration) | ||||||
|     return { |     return { | ||||||
|  |         /** | ||||||
|  |          *  | ||||||
|  |          * @param {OAuthConfigDto} oAuthConfigDto  | ||||||
|  |          * @param {*} [options] Override http request option. | ||||||
|  |          * @throws {RequiredError} | ||||||
|  |          */ | ||||||
|  |         async authorizeOAuth(oAuthConfigDto: OAuthConfigDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<OAuthAuthorizeResponseDto>> { | ||||||
|  |             const localVarAxiosArgs = await localVarAxiosParamCreator.authorizeOAuth(oAuthConfigDto, options); | ||||||
|  |             return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); | ||||||
|  |         }, | ||||||
|         /** |         /** | ||||||
|          *  |          *  | ||||||
|          * @param {OAuthCallbackDto} oAuthCallbackDto  |          * @param {OAuthCallbackDto} oAuthCallbackDto  | ||||||
| @@ -9092,9 +9151,10 @@ export const OAuthApiFp = function(configuration?: Configuration) { | |||||||
|             return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); |             return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); | ||||||
|         }, |         }, | ||||||
|         /** |         /** | ||||||
|          *  |          * @deprecated use feature flags and /oauth/authorize | ||||||
|          * @param {OAuthConfigDto} oAuthConfigDto  |          * @param {OAuthConfigDto} oAuthConfigDto  | ||||||
|          * @param {*} [options] Override http request option. |          * @param {*} [options] Override http request option. | ||||||
|  |          * @deprecated | ||||||
|          * @throws {RequiredError} |          * @throws {RequiredError} | ||||||
|          */ |          */ | ||||||
|         async generateConfig(oAuthConfigDto: OAuthConfigDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<OAuthConfigResponseDto>> { |         async generateConfig(oAuthConfigDto: OAuthConfigDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<OAuthConfigResponseDto>> { | ||||||
| @@ -9139,6 +9199,15 @@ export const OAuthApiFp = function(configuration?: Configuration) { | |||||||
| export const OAuthApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) { | export const OAuthApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) { | ||||||
|     const localVarFp = OAuthApiFp(configuration) |     const localVarFp = OAuthApiFp(configuration) | ||||||
|     return { |     return { | ||||||
|  |         /** | ||||||
|  |          *  | ||||||
|  |          * @param {OAuthApiAuthorizeOAuthRequest} requestParameters Request parameters. | ||||||
|  |          * @param {*} [options] Override http request option. | ||||||
|  |          * @throws {RequiredError} | ||||||
|  |          */ | ||||||
|  |         authorizeOAuth(requestParameters: OAuthApiAuthorizeOAuthRequest, options?: AxiosRequestConfig): AxiosPromise<OAuthAuthorizeResponseDto> { | ||||||
|  |             return localVarFp.authorizeOAuth(requestParameters.oAuthConfigDto, options).then((request) => request(axios, basePath)); | ||||||
|  |         }, | ||||||
|         /** |         /** | ||||||
|          *  |          *  | ||||||
|          * @param {OAuthApiCallbackRequest} requestParameters Request parameters. |          * @param {OAuthApiCallbackRequest} requestParameters Request parameters. | ||||||
| @@ -9149,9 +9218,10 @@ export const OAuthApiFactory = function (configuration?: Configuration, basePath | |||||||
|             return localVarFp.callback(requestParameters.oAuthCallbackDto, options).then((request) => request(axios, basePath)); |             return localVarFp.callback(requestParameters.oAuthCallbackDto, options).then((request) => request(axios, basePath)); | ||||||
|         }, |         }, | ||||||
|         /** |         /** | ||||||
|          *  |          * @deprecated use feature flags and /oauth/authorize | ||||||
|          * @param {OAuthApiGenerateConfigRequest} requestParameters Request parameters. |          * @param {OAuthApiGenerateConfigRequest} requestParameters Request parameters. | ||||||
|          * @param {*} [options] Override http request option. |          * @param {*} [options] Override http request option. | ||||||
|  |          * @deprecated | ||||||
|          * @throws {RequiredError} |          * @throws {RequiredError} | ||||||
|          */ |          */ | ||||||
|         generateConfig(requestParameters: OAuthApiGenerateConfigRequest, options?: AxiosRequestConfig): AxiosPromise<OAuthConfigResponseDto> { |         generateConfig(requestParameters: OAuthApiGenerateConfigRequest, options?: AxiosRequestConfig): AxiosPromise<OAuthConfigResponseDto> { | ||||||
| @@ -9185,6 +9255,20 @@ export const OAuthApiFactory = function (configuration?: Configuration, basePath | |||||||
|     }; |     }; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | /** | ||||||
|  |  * Request parameters for authorizeOAuth operation in OAuthApi. | ||||||
|  |  * @export | ||||||
|  |  * @interface OAuthApiAuthorizeOAuthRequest | ||||||
|  |  */ | ||||||
|  | export interface OAuthApiAuthorizeOAuthRequest { | ||||||
|  |     /** | ||||||
|  |      *  | ||||||
|  |      * @type {OAuthConfigDto} | ||||||
|  |      * @memberof OAuthApiAuthorizeOAuth | ||||||
|  |      */ | ||||||
|  |     readonly oAuthConfigDto: OAuthConfigDto | ||||||
|  | } | ||||||
|  | 
 | ||||||
| /** | /** | ||||||
|  * Request parameters for callback operation in OAuthApi. |  * Request parameters for callback operation in OAuthApi. | ||||||
|  * @export |  * @export | ||||||
| @@ -9234,6 +9318,17 @@ export interface OAuthApiLinkRequest { | |||||||
|  * @extends {BaseAPI} |  * @extends {BaseAPI} | ||||||
|  */ |  */ | ||||||
| export class OAuthApi extends BaseAPI { | export class OAuthApi extends BaseAPI { | ||||||
|  |     /** | ||||||
|  |      *  | ||||||
|  |      * @param {OAuthApiAuthorizeOAuthRequest} requestParameters Request parameters. | ||||||
|  |      * @param {*} [options] Override http request option. | ||||||
|  |      * @throws {RequiredError} | ||||||
|  |      * @memberof OAuthApi | ||||||
|  |      */ | ||||||
|  |     public authorizeOAuth(requestParameters: OAuthApiAuthorizeOAuthRequest, options?: AxiosRequestConfig) { | ||||||
|  |         return OAuthApiFp(this.configuration).authorizeOAuth(requestParameters.oAuthConfigDto, options).then((request) => request(this.axios, this.basePath)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      *  |      *  | ||||||
|      * @param {OAuthApiCallbackRequest} requestParameters Request parameters. |      * @param {OAuthApiCallbackRequest} requestParameters Request parameters. | ||||||
| @@ -9246,9 +9341,10 @@ export class OAuthApi extends BaseAPI { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      *  |      * @deprecated use feature flags and /oauth/authorize | ||||||
|      * @param {OAuthApiGenerateConfigRequest} requestParameters Request parameters. |      * @param {OAuthApiGenerateConfigRequest} requestParameters Request parameters. | ||||||
|      * @param {*} [options] Override http request option. |      * @param {*} [options] Override http request option. | ||||||
|  |      * @deprecated | ||||||
|      * @throws {RequiredError} |      * @throws {RequiredError} | ||||||
|      * @memberof OAuthApi |      * @memberof OAuthApi | ||||||
|      */ |      */ | ||||||
|   | |||||||
| @@ -1,3 +1,4 @@ | |||||||
|  | import { goto } from '$app/navigation'; | ||||||
| import type { AxiosError, AxiosPromise } from 'axios'; | import type { AxiosError, AxiosPromise } from 'axios'; | ||||||
| import { | import { | ||||||
|   notificationController, |   notificationController, | ||||||
| @@ -32,9 +33,17 @@ export const oauth = { | |||||||
|     } |     } | ||||||
|     return false; |     return false; | ||||||
|   }, |   }, | ||||||
|  |   authorize: async (location: Location) => { | ||||||
|  |     try { | ||||||
|  |       const redirectUri = location.href.split('?')[0]; | ||||||
|  |       const { data } = await api.oauthApi.authorizeOAuth({ oAuthConfigDto: { redirectUri } }); | ||||||
|  |       goto(data.url); | ||||||
|  |     } catch (error) { | ||||||
|  |       handleError(error, 'Unable to login with OAuth'); | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|   getConfig: (location: Location) => { |   getConfig: (location: Location) => { | ||||||
|     const redirectUri = location.href.split('?')[0]; |     const redirectUri = location.href.split('?')[0]; | ||||||
|     console.log(`OAuth Redirect URI: ${redirectUri}`); |  | ||||||
|     return api.oauthApi.generateConfig({ oAuthConfigDto: { redirectUri } }); |     return api.oauthApi.generateConfig({ oAuthConfigDto: { redirectUri } }); | ||||||
|   }, |   }, | ||||||
|   login: (location: Location) => { |   login: (location: Location) => { | ||||||
|   | |||||||
| @@ -2,8 +2,9 @@ | |||||||
|   import { goto } from '$app/navigation'; |   import { goto } from '$app/navigation'; | ||||||
|   import LoadingSpinner from '$lib/components/shared-components/loading-spinner.svelte'; |   import LoadingSpinner from '$lib/components/shared-components/loading-spinner.svelte'; | ||||||
|   import { AppRoute } from '$lib/constants'; |   import { AppRoute } from '$lib/constants'; | ||||||
|  |   import { featureFlags } from '$lib/stores/feature-flags.store'; | ||||||
|   import { getServerErrorMessage, handleError } from '$lib/utils/handle-error'; |   import { getServerErrorMessage, handleError } from '$lib/utils/handle-error'; | ||||||
|   import { OAuthConfigResponseDto, api, oauth } from '@api'; |   import { api, oauth } from '@api'; | ||||||
|   import { createEventDispatcher, onMount } from 'svelte'; |   import { createEventDispatcher, onMount } from 'svelte'; | ||||||
|   import { fade } from 'svelte/transition'; |   import { fade } from 'svelte/transition'; | ||||||
|   import Button from '../elements/buttons/button.svelte'; |   import Button from '../elements/buttons/button.svelte'; | ||||||
| @@ -11,14 +12,18 @@ | |||||||
|   let errorMessage: string; |   let errorMessage: string; | ||||||
|   let email = ''; |   let email = ''; | ||||||
|   let password = ''; |   let password = ''; | ||||||
|   let oauthError: string; |   let oauthError = ''; | ||||||
|   export let authConfig: OAuthConfigResponseDto; |  | ||||||
|   let loading = false; |   let loading = false; | ||||||
|   let oauthLoading = true; |   let oauthLoading = true; | ||||||
|  |  | ||||||
|   const dispatch = createEventDispatcher(); |   const dispatch = createEventDispatcher(); | ||||||
|  |  | ||||||
|   onMount(async () => { |   onMount(async () => { | ||||||
|  |     if (!$featureFlags.oauth) { | ||||||
|  |       oauthLoading = false; | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     if (oauth.isCallback(window.location)) { |     if (oauth.isCallback(window.location)) { | ||||||
|       try { |       try { | ||||||
|         await oauth.login(window.location); |         await oauth.login(window.location); | ||||||
| @@ -26,25 +31,18 @@ | |||||||
|         return; |         return; | ||||||
|       } catch (e) { |       } catch (e) { | ||||||
|         console.error('Error [login-form] [oauth.callback]', e); |         console.error('Error [login-form] [oauth.callback]', e); | ||||||
|         oauthError = 'Unable to complete OAuth login'; |         oauthError = (await getServerErrorMessage(e)) || 'Unable to complete OAuth login'; | ||||||
|       } finally { |  | ||||||
|         oauthLoading = false; |         oauthLoading = false; | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     try { |     try { | ||||||
|       const { data } = await oauth.getConfig(window.location); |       if ($featureFlags.oauthAutoLaunch && !oauth.isAutoLaunchDisabled(window.location)) { | ||||||
|       authConfig = data; |  | ||||||
|  |  | ||||||
|       const { enabled, url, autoLaunch } = authConfig; |  | ||||||
|  |  | ||||||
|       if (enabled && url && autoLaunch && !oauth.isAutoLaunchDisabled(window.location)) { |  | ||||||
|         await goto(`${AppRoute.AUTH_LOGIN}?autoLaunch=0`, { replaceState: true }); |         await goto(`${AppRoute.AUTH_LOGIN}?autoLaunch=0`, { replaceState: true }); | ||||||
|         await goto(url); |         await oauth.authorize(window.location); | ||||||
|         return; |         return; | ||||||
|       } |       } | ||||||
|     } catch (error) { |     } catch (error) { | ||||||
|       authConfig.passwordLoginEnabled = true; |  | ||||||
|       await handleError(error, 'Unable to connect!'); |       await handleError(error, 'Unable to connect!'); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -76,9 +74,15 @@ | |||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|  |   const handleOAuthLogin = async () => { | ||||||
|  |     oauthLoading = true; | ||||||
|  |     oauthError = ''; | ||||||
|  |     await oauth.authorize(window.location); | ||||||
|  |   }; | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
| {#if authConfig.passwordLoginEnabled} | {#if !oauthLoading && $featureFlags.passwordLogin} | ||||||
|   <form on:submit|preventDefault={login} class="mt-5 flex flex-col gap-5"> |   <form on:submit|preventDefault={login} class="mt-5 flex flex-col gap-5"> | ||||||
|     {#if errorMessage} |     {#if errorMessage} | ||||||
|       <p class="text-red-400" transition:fade> |       <p class="text-red-400" transition:fade> | ||||||
| @@ -113,7 +117,7 @@ | |||||||
|     </div> |     </div> | ||||||
|  |  | ||||||
|     <div class="my-5 flex w-full"> |     <div class="my-5 flex w-full"> | ||||||
|       <Button type="submit" size="lg" fullwidth disabled={loading || oauthLoading}> |       <Button type="submit" size="lg" fullwidth disabled={loading}> | ||||||
|         {#if loading} |         {#if loading} | ||||||
|           <span class="h-6"> |           <span class="h-6"> | ||||||
|             <LoadingSpinner /> |             <LoadingSpinner /> | ||||||
| @@ -126,8 +130,8 @@ | |||||||
|   </form> |   </form> | ||||||
| {/if} | {/if} | ||||||
|  |  | ||||||
| {#if authConfig.enabled} | {#if $featureFlags.oauth} | ||||||
|   {#if authConfig.passwordLoginEnabled} |   {#if $featureFlags.passwordLogin} | ||||||
|     <div class="inline-flex w-full items-center justify-center"> |     <div class="inline-flex w-full items-center justify-center"> | ||||||
|       <hr class="my-4 h-px w-3/4 border-0 bg-gray-200 dark:bg-gray-600" /> |       <hr class="my-4 h-px w-3/4 border-0 bg-gray-200 dark:bg-gray-600" /> | ||||||
|       <span |       <span | ||||||
| @@ -139,28 +143,27 @@ | |||||||
|   {/if} |   {/if} | ||||||
|   <div class="my-5 flex flex-col gap-5"> |   <div class="my-5 flex flex-col gap-5"> | ||||||
|     {#if oauthError} |     {#if oauthError} | ||||||
|       <p class="text-red-400" transition:fade>{oauthError}</p> |       <p class="text-center text-red-400" transition:fade>{oauthError}</p> | ||||||
|     {/if} |     {/if} | ||||||
|     <a href={authConfig.url} class="flex w-full"> |  | ||||||
|     <Button |     <Button | ||||||
|       type="button" |       type="button" | ||||||
|       disabled={loading || oauthLoading} |       disabled={loading || oauthLoading} | ||||||
|       size="lg" |       size="lg" | ||||||
|       fullwidth |       fullwidth | ||||||
|         color={authConfig.passwordLoginEnabled ? 'secondary' : 'primary'} |       color={$featureFlags.passwordLogin ? 'secondary' : 'primary'} | ||||||
|  |       on:click={handleOAuthLogin} | ||||||
|     > |     > | ||||||
|       {#if oauthLoading} |       {#if oauthLoading} | ||||||
|         <span class="h-6"> |         <span class="h-6"> | ||||||
|           <LoadingSpinner /> |           <LoadingSpinner /> | ||||||
|         </span> |         </span> | ||||||
|       {:else} |       {:else} | ||||||
|           {authConfig.buttonText || 'Login with OAuth'} |         {$featureFlags.passwordLogin ? 'Login with OAuth' : 'Login'} | ||||||
|       {/if} |       {/if} | ||||||
|     </Button> |     </Button> | ||||||
|     </a> |  | ||||||
|   </div> |   </div> | ||||||
| {/if} | {/if} | ||||||
|  |  | ||||||
| {#if !authConfig.enabled && !authConfig.passwordLoginEnabled} | {#if !$featureFlags.passwordLogin && !$featureFlags.oauth} | ||||||
|   <p class="p-4 text-center dark:text-immich-dark-fg">Login has been disabled.</p> |   <p class="p-4 text-center dark:text-immich-dark-fg">Login has been disabled.</p> | ||||||
| {/if} | {/if} | ||||||
|   | |||||||
| @@ -5,7 +5,7 @@ | |||||||
|   export let showMessage = $$slots.message; |   export let showMessage = $$slots.message; | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
| <section class="flex min-h-screen w-screen place-content-center place-items-center p-4"> | <section class="min-w-screen flex min-h-screen place-content-center place-items-center p-4"> | ||||||
|   <div |   <div | ||||||
|     class="flex w-full max-w-lg flex-col gap-4 rounded-3xl border bg-white p-8 shadow-sm dark:border-immich-dark-gray dark:bg-immich-dark-gray" |     class="flex w-full max-w-lg flex-col gap-4 rounded-3xl border bg-white p-8 shadow-sm dark:border-immich-dark-gray dark:bg-immich-dark-gray" | ||||||
|   > |   > | ||||||
|   | |||||||
| @@ -1,16 +1,16 @@ | |||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
|   import { goto } from '$app/navigation'; |   import { goto } from '$app/navigation'; | ||||||
|   import { oauth, OAuthConfigResponseDto, UserResponseDto } from '@api'; |   import { featureFlags } from '$lib/stores/feature-flags.store'; | ||||||
|  |   import { oauth, UserResponseDto } from '@api'; | ||||||
|   import { onMount } from 'svelte'; |   import { onMount } from 'svelte'; | ||||||
|   import { fade } from 'svelte/transition'; |   import { fade } from 'svelte/transition'; | ||||||
|   import { handleError } from '../../utils/handle-error'; |   import { handleError } from '../../utils/handle-error'; | ||||||
|  |   import Button from '../elements/buttons/button.svelte'; | ||||||
|   import LoadingSpinner from '../shared-components/loading-spinner.svelte'; |   import LoadingSpinner from '../shared-components/loading-spinner.svelte'; | ||||||
|   import { notificationController, NotificationType } from '../shared-components/notification/notification'; |   import { notificationController, NotificationType } from '../shared-components/notification/notification'; | ||||||
|   import Button from '../elements/buttons/button.svelte'; |  | ||||||
|  |  | ||||||
|   export let user: UserResponseDto; |   export let user: UserResponseDto; | ||||||
|  |  | ||||||
|   let config: OAuthConfigResponseDto = { enabled: false, passwordLoginEnabled: true }; |  | ||||||
|   let loading = true; |   let loading = true; | ||||||
|  |  | ||||||
|   onMount(async () => { |   onMount(async () => { | ||||||
| @@ -32,13 +32,6 @@ | |||||||
|       } |       } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     try { |  | ||||||
|       const { data } = await oauth.getConfig(window.location); |  | ||||||
|       config = data; |  | ||||||
|     } catch (error) { |  | ||||||
|       handleError(error, 'Unable to load OAuth config'); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     loading = false; |     loading = false; | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
| @@ -63,13 +56,11 @@ | |||||||
|         <div class="flex place-content-center place-items-center"> |         <div class="flex place-content-center place-items-center"> | ||||||
|           <LoadingSpinner /> |           <LoadingSpinner /> | ||||||
|         </div> |         </div> | ||||||
|       {:else if config.enabled} |       {:else if $featureFlags.oauth} | ||||||
|         {#if user.oauthId} |         {#if user.oauthId} | ||||||
|           <Button size="sm" on:click={() => handleUnlink()}>Unlink Oauth</Button> |           <Button size="sm" on:click={() => handleUnlink()}>Unlink Oauth</Button> | ||||||
|         {:else} |         {:else} | ||||||
|           <a href={config.url}> |           <Button size="sm" on:click={() => oauth.authorize(window.location)}>Link to OAuth</Button> | ||||||
|             <Button size="sm" on:click={() => handleUnlink()}>Link to OAuth</Button> |  | ||||||
|           </a> |  | ||||||
|         {/if} |         {/if} | ||||||
|       {/if} |       {/if} | ||||||
|     </div> |     </div> | ||||||
|   | |||||||
| @@ -1,5 +1,7 @@ | |||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
|  |   import { browser } from '$app/environment'; | ||||||
|   import { page } from '$app/stores'; |   import { page } from '$app/stores'; | ||||||
|  |   import { featureFlags } from '$lib/stores/feature-flags.store'; | ||||||
|   import { APIKeyResponseDto, AuthDeviceResponseDto, oauth, UserResponseDto } from '@api'; |   import { APIKeyResponseDto, AuthDeviceResponseDto, oauth, UserResponseDto } from '@api'; | ||||||
|   import SettingAccordion from '../admin-page/settings/setting-accordion.svelte'; |   import SettingAccordion from '../admin-page/settings/setting-accordion.svelte'; | ||||||
|   import ChangePasswordSettings from './change-password-settings.svelte'; |   import ChangePasswordSettings from './change-password-settings.svelte'; | ||||||
| @@ -9,7 +11,6 @@ | |||||||
|   import PartnerSettings from './partner-settings.svelte'; |   import PartnerSettings from './partner-settings.svelte'; | ||||||
|   import UserAPIKeyList from './user-api-key-list.svelte'; |   import UserAPIKeyList from './user-api-key-list.svelte'; | ||||||
|   import UserProfileSettings from './user-profile-settings.svelte'; |   import UserProfileSettings from './user-profile-settings.svelte'; | ||||||
|   import { onMount } from 'svelte'; |  | ||||||
|  |  | ||||||
|   export let user: UserResponseDto; |   export let user: UserResponseDto; | ||||||
|  |  | ||||||
| @@ -17,18 +18,10 @@ | |||||||
|   export let devices: AuthDeviceResponseDto[] = []; |   export let devices: AuthDeviceResponseDto[] = []; | ||||||
|   export let partners: UserResponseDto[] = []; |   export let partners: UserResponseDto[] = []; | ||||||
|  |  | ||||||
|   let oauthEnabled = false; |  | ||||||
|   let oauthOpen = false; |   let oauthOpen = false; | ||||||
|  |   if (browser) { | ||||||
|   onMount(async () => { |  | ||||||
|     oauthOpen = oauth.isCallback(window.location); |     oauthOpen = oauth.isCallback(window.location); | ||||||
|     try { |  | ||||||
|       const { data } = await oauth.getConfig(window.location); |  | ||||||
|       oauthEnabled = data.enabled; |  | ||||||
|     } catch { |  | ||||||
|       // noop |  | ||||||
|   } |   } | ||||||
|   }); |  | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
| <SettingAccordion title="Account" subtitle="Manage your account"> | <SettingAccordion title="Account" subtitle="Manage your account"> | ||||||
| @@ -47,7 +40,7 @@ | |||||||
|   <MemoriesSettings {user} /> |   <MemoriesSettings {user} /> | ||||||
| </SettingAccordion> | </SettingAccordion> | ||||||
|  |  | ||||||
| {#if oauthEnabled} | {#if $featureFlags.loaded && $featureFlags.oauth} | ||||||
|   <SettingAccordion |   <SettingAccordion | ||||||
|     title="OAuth" |     title="OAuth" | ||||||
|     subtitle="Manage your OAuth connection" |     subtitle="Manage your OAuth connection" | ||||||
|   | |||||||
| @@ -1,21 +1,22 @@ | |||||||
| import { api, ServerFeaturesDto } from '@api'; | import { api, ServerFeaturesDto } from '@api'; | ||||||
| import { writable } from 'svelte/store'; | import { writable } from 'svelte/store'; | ||||||
|  |  | ||||||
| export type FeatureFlags = ServerFeaturesDto; | export type FeatureFlags = ServerFeaturesDto & { loaded: boolean }; | ||||||
|  |  | ||||||
| export const featureFlags = writable<FeatureFlags>({ | export const featureFlags = writable<FeatureFlags>({ | ||||||
|  |   loaded: false, | ||||||
|   clipEncode: true, |   clipEncode: true, | ||||||
|   facialRecognition: true, |   facialRecognition: true, | ||||||
|   sidecar: true, |   sidecar: true, | ||||||
|   tagImage: true, |   tagImage: true, | ||||||
|   search: true, |   search: true, | ||||||
|   oauth: true, |   oauth: false, | ||||||
|   oauthAutoLaunch: true, |   oauthAutoLaunch: false, | ||||||
|   passwordLogin: true, |   passwordLogin: true, | ||||||
|   configFile: false, |   configFile: false, | ||||||
| }); | }); | ||||||
|  |  | ||||||
| export const loadFeatureFlags = async () => { | export const loadFeatureFlags = async () => { | ||||||
|   const { data } = await api.serverInfoApi.getServerFeatures(); |   const { data } = await api.serverInfoApi.getServerFeatures(); | ||||||
|   featureFlags.update(() => data); |   featureFlags.update(() => ({ ...data, loaded: true })); | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -1,5 +1,4 @@ | |||||||
| import { AppRoute } from '$lib/constants'; | import { AppRoute } from '$lib/constants'; | ||||||
| import type { OAuthConfigResponseDto } from '@api'; |  | ||||||
| import { redirect } from '@sveltejs/kit'; | import { redirect } from '@sveltejs/kit'; | ||||||
| import type { PageServerLoad } from './$types'; | import type { PageServerLoad } from './$types'; | ||||||
|  |  | ||||||
| @@ -10,23 +9,7 @@ export const load = (async ({ locals: { api } }) => { | |||||||
|     throw redirect(302, AppRoute.AUTH_REGISTER); |     throw redirect(302, AppRoute.AUTH_REGISTER); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   let authConfig: OAuthConfigResponseDto = { |  | ||||||
|     passwordLoginEnabled: true, |  | ||||||
|     enabled: false, |  | ||||||
|   }; |  | ||||||
|  |  | ||||||
|   try { |  | ||||||
|     // TODO: Figure out how to get correct redirect URI server-side. |  | ||||||
|     const { data } = await api.oauthApi.generateConfig({ oAuthConfigDto: { redirectUri: '/' } }); |  | ||||||
|     data.url = undefined; |  | ||||||
|  |  | ||||||
|     authConfig = data; |  | ||||||
|   } catch (err) { |  | ||||||
|     console.error('[ERROR] login/+page.server.ts:', err); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   return { |   return { | ||||||
|     authConfig, |  | ||||||
|     meta: { |     meta: { | ||||||
|       title: 'Login', |       title: 'Login', | ||||||
|     }, |     }, | ||||||
|   | |||||||
| @@ -4,20 +4,22 @@ | |||||||
|   import FullscreenContainer from '$lib/components/shared-components/fullscreen-container.svelte'; |   import FullscreenContainer from '$lib/components/shared-components/fullscreen-container.svelte'; | ||||||
|   import { AppRoute } from '$lib/constants'; |   import { AppRoute } from '$lib/constants'; | ||||||
|   import { loginPageMessage } from '$lib/constants'; |   import { loginPageMessage } from '$lib/constants'; | ||||||
|  |   import { featureFlags } from '$lib/stores/feature-flags.store'; | ||||||
|   import type { PageData } from './$types'; |   import type { PageData } from './$types'; | ||||||
|  |  | ||||||
|   export let data: PageData; |   export let data: PageData; | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
| <FullscreenContainer title={data.meta.title} showMessage={!!loginPageMessage}> | {#if $featureFlags.loaded} | ||||||
|  |   <FullscreenContainer title={data.meta.title} showMessage={!!loginPageMessage}> | ||||||
|     <p slot="message"> |     <p slot="message"> | ||||||
|       <!-- eslint-disable-next-line svelte/no-at-html-tags --> |       <!-- eslint-disable-next-line svelte/no-at-html-tags --> | ||||||
|       {@html loginPageMessage} |       {@html loginPageMessage} | ||||||
|     </p> |     </p> | ||||||
|  |  | ||||||
|     <LoginForm |     <LoginForm | ||||||
|     authConfig={data.authConfig} |  | ||||||
|       on:success={() => goto(AppRoute.PHOTOS, { invalidateAll: true })} |       on:success={() => goto(AppRoute.PHOTOS, { invalidateAll: true })} | ||||||
|       on:first-login={() => goto(AppRoute.AUTH_CHANGE_PASSWORD)} |       on:first-login={() => goto(AppRoute.AUTH_CHANGE_PASSWORD)} | ||||||
|     /> |     /> | ||||||
| </FullscreenContainer> |   </FullscreenContainer> | ||||||
|  | {/if} | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user