mirror of
				https://github.com/KevinMidboe/immich.git
				synced 2025-10-29 17:40:28 +00:00 
			
		
		
		
	feat(web,server): link/unlink oauth account (#1154)
* feat(web,server): link/unlink oauth account * chore: linting * fix: broken oauth callback * fix: user core bugs * fix: tests * fix: use user response * chore: update docs * feat: prevent the same oauth account from being linked twice * chore: mock logger
This commit is contained in:
		
							
								
								
									
										2
									
								
								mobile/openapi/README.md
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								mobile/openapi/README.md
									
									
									
										generated
									
									
									
								
							@@ -108,6 +108,8 @@ Class | Method | HTTP request | Description
 | 
			
		||||
*JobApi* | [**sendJobCommand**](doc//JobApi.md#sendjobcommand) | **PUT** /jobs/{jobId} | 
 | 
			
		||||
*OAuthApi* | [**callback**](doc//OAuthApi.md#callback) | **POST** /oauth/callback | 
 | 
			
		||||
*OAuthApi* | [**generateConfig**](doc//OAuthApi.md#generateconfig) | **POST** /oauth/config | 
 | 
			
		||||
*OAuthApi* | [**link**](doc//OAuthApi.md#link) | **POST** /oauth/link | 
 | 
			
		||||
*OAuthApi* | [**unlink**](doc//OAuthApi.md#unlink) | **POST** /oauth/unlink | 
 | 
			
		||||
*ServerInfoApi* | [**getServerInfo**](doc//ServerInfoApi.md#getserverinfo) | **GET** /server-info | 
 | 
			
		||||
*ServerInfoApi* | [**getServerVersion**](doc//ServerInfoApi.md#getserverversion) | **GET** /server-info/version | 
 | 
			
		||||
*ServerInfoApi* | [**getStats**](doc//ServerInfoApi.md#getstats) | **GET** /server-info/stats | 
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										80
									
								
								mobile/openapi/doc/OAuthApi.md
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										80
									
								
								mobile/openapi/doc/OAuthApi.md
									
									
									
										generated
									
									
									
								
							@@ -11,6 +11,8 @@ Method | HTTP request | Description
 | 
			
		||||
------------- | ------------- | -------------
 | 
			
		||||
[**callback**](OAuthApi.md#callback) | **POST** /oauth/callback | 
 | 
			
		||||
[**generateConfig**](OAuthApi.md#generateconfig) | **POST** /oauth/config | 
 | 
			
		||||
[**link**](OAuthApi.md#link) | **POST** /oauth/link | 
 | 
			
		||||
[**unlink**](OAuthApi.md#unlink) | **POST** /oauth/unlink | 
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# **callback**
 | 
			
		||||
@@ -95,3 +97,81 @@ No authorization required
 | 
			
		||||
 | 
			
		||||
[[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)
 | 
			
		||||
 | 
			
		||||
# **link**
 | 
			
		||||
> UserResponseDto link(oAuthCallbackDto)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Example
 | 
			
		||||
```dart
 | 
			
		||||
import 'package:openapi/api.dart';
 | 
			
		||||
 | 
			
		||||
final api_instance = OAuthApi();
 | 
			
		||||
final oAuthCallbackDto = OAuthCallbackDto(); // OAuthCallbackDto | 
 | 
			
		||||
 | 
			
		||||
try {
 | 
			
		||||
    final result = api_instance.link(oAuthCallbackDto);
 | 
			
		||||
    print(result);
 | 
			
		||||
} catch (e) {
 | 
			
		||||
    print('Exception when calling OAuthApi->link: $e\n');
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Parameters
 | 
			
		||||
 | 
			
		||||
Name | Type | Description  | Notes
 | 
			
		||||
------------- | ------------- | ------------- | -------------
 | 
			
		||||
 **oAuthCallbackDto** | [**OAuthCallbackDto**](OAuthCallbackDto.md)|  | 
 | 
			
		||||
 | 
			
		||||
### Return type
 | 
			
		||||
 | 
			
		||||
[**UserResponseDto**](UserResponseDto.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)
 | 
			
		||||
 | 
			
		||||
# **unlink**
 | 
			
		||||
> UserResponseDto unlink()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Example
 | 
			
		||||
```dart
 | 
			
		||||
import 'package:openapi/api.dart';
 | 
			
		||||
 | 
			
		||||
final api_instance = OAuthApi();
 | 
			
		||||
 | 
			
		||||
try {
 | 
			
		||||
    final result = api_instance.unlink();
 | 
			
		||||
    print(result);
 | 
			
		||||
} catch (e) {
 | 
			
		||||
    print('Exception when calling OAuthApi->unlink: $e\n');
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Parameters
 | 
			
		||||
This endpoint does not need any parameter.
 | 
			
		||||
 | 
			
		||||
### Return type
 | 
			
		||||
 | 
			
		||||
[**UserResponseDto**](UserResponseDto.md)
 | 
			
		||||
 | 
			
		||||
### Authorization
 | 
			
		||||
 | 
			
		||||
No authorization required
 | 
			
		||||
 | 
			
		||||
### HTTP request headers
 | 
			
		||||
 | 
			
		||||
 - **Content-Type**: Not defined
 | 
			
		||||
 - **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)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								mobile/openapi/doc/UserResponseDto.md
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1
									
								
								mobile/openapi/doc/UserResponseDto.md
									
									
									
										generated
									
									
									
								
							@@ -17,6 +17,7 @@ Name | Type | Description | Notes
 | 
			
		||||
**shouldChangePassword** | **bool** |  | 
 | 
			
		||||
**isAdmin** | **bool** |  | 
 | 
			
		||||
**deletedAt** | [**DateTime**](DateTime.md) |  | [optional] 
 | 
			
		||||
**oauthId** | **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)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										88
									
								
								mobile/openapi/lib/api/o_auth_api.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										88
									
								
								mobile/openapi/lib/api/o_auth_api.dart
									
									
									
										generated
									
									
									
								
							@@ -109,4 +109,92 @@ class OAuthApi {
 | 
			
		||||
    }
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Performs an HTTP 'POST /oauth/link' operation and returns the [Response].
 | 
			
		||||
  /// Parameters:
 | 
			
		||||
  ///
 | 
			
		||||
  /// * [OAuthCallbackDto] oAuthCallbackDto (required):
 | 
			
		||||
  Future<Response> linkWithHttpInfo(OAuthCallbackDto oAuthCallbackDto,) async {
 | 
			
		||||
    // ignore: prefer_const_declarations
 | 
			
		||||
    final path = r'/oauth/link';
 | 
			
		||||
 | 
			
		||||
    // ignore: prefer_final_locals
 | 
			
		||||
    Object? postBody = oAuthCallbackDto;
 | 
			
		||||
 | 
			
		||||
    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:
 | 
			
		||||
  ///
 | 
			
		||||
  /// * [OAuthCallbackDto] oAuthCallbackDto (required):
 | 
			
		||||
  Future<UserResponseDto?> link(OAuthCallbackDto oAuthCallbackDto,) async {
 | 
			
		||||
    final response = await linkWithHttpInfo(oAuthCallbackDto,);
 | 
			
		||||
    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), 'UserResponseDto',) as UserResponseDto;
 | 
			
		||||
    
 | 
			
		||||
    }
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Performs an HTTP 'POST /oauth/unlink' operation and returns the [Response].
 | 
			
		||||
  Future<Response> unlinkWithHttpInfo() async {
 | 
			
		||||
    // ignore: prefer_const_declarations
 | 
			
		||||
    final path = r'/oauth/unlink';
 | 
			
		||||
 | 
			
		||||
    // ignore: prefer_final_locals
 | 
			
		||||
    Object? postBody;
 | 
			
		||||
 | 
			
		||||
    final queryParams = <QueryParam>[];
 | 
			
		||||
    final headerParams = <String, String>{};
 | 
			
		||||
    final formParams = <String, String>{};
 | 
			
		||||
 | 
			
		||||
    const contentTypes = <String>[];
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    return apiClient.invokeAPI(
 | 
			
		||||
      path,
 | 
			
		||||
      'POST',
 | 
			
		||||
      queryParams,
 | 
			
		||||
      postBody,
 | 
			
		||||
      headerParams,
 | 
			
		||||
      formParams,
 | 
			
		||||
      contentTypes.isEmpty ? null : contentTypes.first,
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Future<UserResponseDto?> unlink() async {
 | 
			
		||||
    final response = await unlinkWithHttpInfo();
 | 
			
		||||
    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), 'UserResponseDto',) as UserResponseDto;
 | 
			
		||||
    
 | 
			
		||||
    }
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										14
									
								
								mobile/openapi/lib/model/user_response_dto.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										14
									
								
								mobile/openapi/lib/model/user_response_dto.dart
									
									
									
										generated
									
									
									
								
							@@ -22,6 +22,7 @@ class UserResponseDto {
 | 
			
		||||
    required this.shouldChangePassword,
 | 
			
		||||
    required this.isAdmin,
 | 
			
		||||
    this.deletedAt,
 | 
			
		||||
    required this.oauthId,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  String id;
 | 
			
		||||
@@ -48,6 +49,8 @@ class UserResponseDto {
 | 
			
		||||
  ///
 | 
			
		||||
  DateTime? deletedAt;
 | 
			
		||||
 | 
			
		||||
  String oauthId;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  bool operator ==(Object other) => identical(this, other) || other is UserResponseDto &&
 | 
			
		||||
     other.id == id &&
 | 
			
		||||
@@ -58,7 +61,8 @@ class UserResponseDto {
 | 
			
		||||
     other.profileImagePath == profileImagePath &&
 | 
			
		||||
     other.shouldChangePassword == shouldChangePassword &&
 | 
			
		||||
     other.isAdmin == isAdmin &&
 | 
			
		||||
     other.deletedAt == deletedAt;
 | 
			
		||||
     other.deletedAt == deletedAt &&
 | 
			
		||||
     other.oauthId == oauthId;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  int get hashCode =>
 | 
			
		||||
@@ -71,10 +75,11 @@ class UserResponseDto {
 | 
			
		||||
    (profileImagePath.hashCode) +
 | 
			
		||||
    (shouldChangePassword.hashCode) +
 | 
			
		||||
    (isAdmin.hashCode) +
 | 
			
		||||
    (deletedAt == null ? 0 : deletedAt!.hashCode);
 | 
			
		||||
    (deletedAt == null ? 0 : deletedAt!.hashCode) +
 | 
			
		||||
    (oauthId.hashCode);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  String toString() => 'UserResponseDto[id=$id, email=$email, firstName=$firstName, lastName=$lastName, createdAt=$createdAt, profileImagePath=$profileImagePath, shouldChangePassword=$shouldChangePassword, isAdmin=$isAdmin, deletedAt=$deletedAt]';
 | 
			
		||||
  String toString() => 'UserResponseDto[id=$id, email=$email, firstName=$firstName, lastName=$lastName, createdAt=$createdAt, profileImagePath=$profileImagePath, shouldChangePassword=$shouldChangePassword, isAdmin=$isAdmin, deletedAt=$deletedAt, oauthId=$oauthId]';
 | 
			
		||||
 | 
			
		||||
  Map<String, dynamic> toJson() {
 | 
			
		||||
    final _json = <String, dynamic>{};
 | 
			
		||||
@@ -91,6 +96,7 @@ class UserResponseDto {
 | 
			
		||||
    } else {
 | 
			
		||||
      _json[r'deletedAt'] = null;
 | 
			
		||||
    }
 | 
			
		||||
      _json[r'oauthId'] = oauthId;
 | 
			
		||||
    return _json;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -122,6 +128,7 @@ class UserResponseDto {
 | 
			
		||||
        shouldChangePassword: mapValueOfType<bool>(json, r'shouldChangePassword')!,
 | 
			
		||||
        isAdmin: mapValueOfType<bool>(json, r'isAdmin')!,
 | 
			
		||||
        deletedAt: mapDateTime(json, r'deletedAt', ''),
 | 
			
		||||
        oauthId: mapValueOfType<String>(json, r'oauthId')!,
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
    return null;
 | 
			
		||||
@@ -179,6 +186,7 @@ class UserResponseDto {
 | 
			
		||||
    'profileImagePath',
 | 
			
		||||
    'shouldChangePassword',
 | 
			
		||||
    'isAdmin',
 | 
			
		||||
    'oauthId',
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										10
									
								
								mobile/openapi/test/o_auth_api_test.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										10
									
								
								mobile/openapi/test/o_auth_api_test.dart
									
									
									
										generated
									
									
									
								
							@@ -27,5 +27,15 @@ void main() {
 | 
			
		||||
      // TODO
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    //Future<UserResponseDto> link(OAuthCallbackDto oAuthCallbackDto) async
 | 
			
		||||
    test('test link', () async {
 | 
			
		||||
      // TODO
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    //Future<UserResponseDto> unlink() async
 | 
			
		||||
    test('test unlink', () async {
 | 
			
		||||
      // TODO
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										5
									
								
								mobile/openapi/test/user_response_dto_test.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										5
									
								
								mobile/openapi/test/user_response_dto_test.dart
									
									
									
										generated
									
									
									
								
							@@ -61,6 +61,11 @@ void main() {
 | 
			
		||||
      // TODO
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // String oauthId
 | 
			
		||||
    test('to test the property `oauthId`', () async {
 | 
			
		||||
      // TODO
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user