fix(mobile): use a valid OAuth callback URL (#10832)
* add root resource path '/' to mobile oauth scheme * chore: add oauth-callback path * add root resource path '/' to mobile oauth scheme * chore: add oauth-callback path * fix: make sure there are three forward slash in callback URL --------- Co-authored-by: Jason Rasmussen <jason@rasm.me> Co-authored-by: Alex <alex.tran1502@gmail.com>
This commit is contained in:
@@ -69,7 +69,7 @@
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:scheme="app.immich" />
|
||||
<data android:scheme="app.immich" android:pathPrefix="/oauth-callback"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<!-- Don't delete the meta-data below.
|
||||
@@ -94,4 +94,4 @@
|
||||
<data android:scheme="geo" />
|
||||
</intent>
|
||||
</queries>
|
||||
</manifest>
|
||||
</manifest>
|
||||
|
||||
@@ -29,7 +29,7 @@ class LoginPage extends HookConsumerWidget {
|
||||
);
|
||||
|
||||
return Scaffold(
|
||||
body: const LoginForm(),
|
||||
body: LoginForm(),
|
||||
bottomNavigationBar: SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 16.0),
|
||||
|
||||
@@ -3,7 +3,7 @@ import 'package:logging/logging.dart';
|
||||
import 'package:openapi/api.dart';
|
||||
import 'package:flutter_web_auth/flutter_web_auth.dart';
|
||||
|
||||
// Redirect URL = app.immich://
|
||||
// Redirect URL = app.immich:///oauth-callback
|
||||
|
||||
class OAuthService {
|
||||
final ApiService _apiService;
|
||||
@@ -16,28 +16,40 @@ class OAuthService {
|
||||
) async {
|
||||
// Resolve API server endpoint from user provided serverUrl
|
||||
await _apiService.resolveAndSetEndpoint(serverUrl);
|
||||
final redirectUri = '$callbackUrlScheme:///oauth-callback';
|
||||
log.info(
|
||||
"Starting OAuth flow with redirect URI: $redirectUri",
|
||||
);
|
||||
|
||||
final dto = await _apiService.oAuthApi.startOAuth(
|
||||
OAuthConfigDto(redirectUri: '$callbackUrlScheme:/'),
|
||||
OAuthConfigDto(redirectUri: redirectUri),
|
||||
);
|
||||
return dto?.url;
|
||||
|
||||
final authUrl = dto?.url;
|
||||
log.info('Received Authorization URL: $authUrl');
|
||||
|
||||
return authUrl;
|
||||
}
|
||||
|
||||
Future<LoginResponseDto?> oAuthLogin(String oauthUrl) async {
|
||||
try {
|
||||
var result = await FlutterWebAuth.authenticate(
|
||||
url: oauthUrl,
|
||||
callbackUrlScheme: callbackUrlScheme,
|
||||
);
|
||||
String result = await FlutterWebAuth.authenticate(
|
||||
url: oauthUrl,
|
||||
callbackUrlScheme: callbackUrlScheme,
|
||||
);
|
||||
|
||||
return await _apiService.oAuthApi.finishOAuth(
|
||||
OAuthCallbackDto(
|
||||
url: result,
|
||||
),
|
||||
log.info('Received OAuth callback: $result');
|
||||
|
||||
if (result.startsWith('app.immich:/oauth-callback')) {
|
||||
result = result.replaceAll(
|
||||
'app.immich:/oauth-callback',
|
||||
'app.immich:///oauth-callback',
|
||||
);
|
||||
} catch (e, stack) {
|
||||
log.severe("OAuth login failed", e, stack);
|
||||
return null;
|
||||
}
|
||||
|
||||
return await _apiService.oAuthApi.finishOAuth(
|
||||
OAuthCallbackDto(
|
||||
url: result,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,12 +27,15 @@ import 'package:immich_mobile/widgets/forms/login/login_button.dart';
|
||||
import 'package:immich_mobile/widgets/forms/login/o_auth_login_button.dart';
|
||||
import 'package:immich_mobile/widgets/forms/login/password_input.dart';
|
||||
import 'package:immich_mobile/widgets/forms/login/server_endpoint_input.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:openapi/api.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
|
||||
class LoginForm extends HookConsumerWidget {
|
||||
const LoginForm({super.key});
|
||||
LoginForm({super.key});
|
||||
|
||||
final log = Logger('LoginForm');
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
@@ -229,7 +232,9 @@ class LoginForm extends HookConsumerWidget {
|
||||
.getOAuthServerUrl(sanitizeUrl(serverEndpointController.text));
|
||||
|
||||
isLoading.value = true;
|
||||
} catch (e) {
|
||||
} catch (error, stack) {
|
||||
log.severe('Error getting OAuth server Url: $error', stack);
|
||||
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
msg: "login_form_failed_get_oauth_server_config".tr(),
|
||||
@@ -241,10 +246,19 @@ class LoginForm extends HookConsumerWidget {
|
||||
}
|
||||
|
||||
if (oAuthServerUrl != null) {
|
||||
var loginResponseDto = await oAuthService.oAuthLogin(oAuthServerUrl);
|
||||
try {
|
||||
final loginResponseDto =
|
||||
await oAuthService.oAuthLogin(oAuthServerUrl);
|
||||
|
||||
if (loginResponseDto != null) {
|
||||
var isSuccess = await ref
|
||||
if (loginResponseDto == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
log.info(
|
||||
"Finished OAuth login with response: ${loginResponseDto.userEmail}",
|
||||
);
|
||||
|
||||
final isSuccess = await ref
|
||||
.watch(authenticationProvider.notifier)
|
||||
.setSuccessLoginInfo(
|
||||
accessToken: loginResponseDto.accessToken,
|
||||
@@ -258,17 +272,19 @@ class LoginForm extends HookConsumerWidget {
|
||||
ref.watch(backupProvider.notifier).resumeBackup();
|
||||
}
|
||||
context.replaceRoute(const TabControllerRoute());
|
||||
} else {
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
msg: "login_form_failed_login".tr(),
|
||||
toastType: ToastType.error,
|
||||
gravity: ToastGravity.TOP,
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (error, stack) {
|
||||
log.severe('Error logging in with OAuth: $error', stack);
|
||||
|
||||
isLoading.value = false;
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
msg: error.toString(),
|
||||
toastType: ToastType.error,
|
||||
gravity: ToastGravity.TOP,
|
||||
);
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
} else {
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
|
||||
Reference in New Issue
Block a user