👽 Update the OIDC login
This commit is contained in:
parent
eb4d2c2e2f
commit
7f4e489f51
@ -19,6 +19,7 @@ import 'package:island/pods/network.dart';
|
||||
import 'package:island/pods/userinfo.dart';
|
||||
import 'package:island/pods/websocket.dart';
|
||||
import 'package:island/screens/account/me/settings_connections.dart';
|
||||
import 'package:island/screens/auth/oidc.dart';
|
||||
import 'package:island/services/notify.dart';
|
||||
import 'package:island/services/udid.dart';
|
||||
import 'package:island/widgets/alert.dart';
|
||||
@ -174,28 +175,15 @@ class _LoginCheckScreen extends HookConsumerWidget {
|
||||
return null;
|
||||
}, [isBusy]);
|
||||
|
||||
Future<void> performCheckTicket() async {
|
||||
final pwd = passwordController.value.text;
|
||||
if (pwd.isEmpty) return;
|
||||
isBusy.value = true;
|
||||
try {
|
||||
// Pass challenge
|
||||
final client = ref.watch(apiClientProvider);
|
||||
final resp = await client.patch(
|
||||
'/auth/challenge/${challenge!.id}',
|
||||
data: {'factor_id': factor!.id, 'password': pwd},
|
||||
);
|
||||
final result = SnAuthChallenge.fromJson(resp.data);
|
||||
onChallenge(result);
|
||||
if (result.stepRemain > 0) {
|
||||
onNext();
|
||||
return;
|
||||
}
|
||||
|
||||
Future<void> getToken({String? code}) async {
|
||||
// Get token if challenge is completed
|
||||
final client = ref.watch(apiClientProvider);
|
||||
final tokenResp = await client.post(
|
||||
'/auth/token',
|
||||
data: {'grant_type': 'authorization_code', 'code': result.id},
|
||||
data: {
|
||||
'grant_type': 'authorization_code',
|
||||
'code': code ?? challenge!.id,
|
||||
},
|
||||
);
|
||||
final token = tokenResp.data['token'];
|
||||
setToken(ref.watch(sharedPreferencesProvider), token);
|
||||
@ -233,6 +221,40 @@ class _LoginCheckScreen extends HookConsumerWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() {
|
||||
if (challenge != null && challenge?.stepRemain == 0) {
|
||||
Future(() {
|
||||
isBusy.value = true;
|
||||
getToken().catchError((err) {
|
||||
showErrorAlert(err);
|
||||
isBusy.value = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
return null;
|
||||
}, [challenge]);
|
||||
|
||||
Future<void> performCheckTicket() async {
|
||||
final pwd = passwordController.value.text;
|
||||
if (pwd.isEmpty) return;
|
||||
isBusy.value = true;
|
||||
try {
|
||||
// Pass challenge
|
||||
final client = ref.watch(apiClientProvider);
|
||||
final resp = await client.patch(
|
||||
'/auth/challenge/${challenge!.id}',
|
||||
data: {'factor_id': factor!.id, 'password': pwd},
|
||||
);
|
||||
final result = SnAuthChallenge.fromJson(resp.data);
|
||||
onChallenge(result);
|
||||
if (result.stepRemain > 0) {
|
||||
onNext();
|
||||
return;
|
||||
}
|
||||
|
||||
await getToken(code: result.id);
|
||||
} catch (err) {
|
||||
showErrorAlert(err);
|
||||
return;
|
||||
@ -346,6 +368,14 @@ class _LoginPickerScreen extends HookConsumerWidget {
|
||||
return null;
|
||||
}, [isBusy]);
|
||||
|
||||
useEffect(() {
|
||||
if (ticket != null && ticket?.stepRemain == 0) {
|
||||
onPickFactor(factors!.first);
|
||||
onNext();
|
||||
}
|
||||
return null;
|
||||
}, [ticket]);
|
||||
|
||||
final unfocusColor = Theme.of(
|
||||
context,
|
||||
).colorScheme.onSurface.withAlpha((255 * 0.75).round());
|
||||
@ -569,7 +599,6 @@ class _LoginLookupScreen extends HookConsumerWidget {
|
||||
);
|
||||
|
||||
if (context.mounted) showLoadingModal(context);
|
||||
|
||||
final resp = await client.post(
|
||||
'/auth/login/apple/mobile',
|
||||
data: {
|
||||
@ -578,20 +607,18 @@ class _LoginLookupScreen extends HookConsumerWidget {
|
||||
'device_id': await getUdid(),
|
||||
},
|
||||
);
|
||||
final token = resp.data['token'];
|
||||
setToken(ref.watch(sharedPreferencesProvider), token);
|
||||
ref.invalidate(tokenProvider);
|
||||
if (!context.mounted) return;
|
||||
|
||||
// Do post login tasks
|
||||
final userNotifier = ref.read(userInfoProvider.notifier);
|
||||
userNotifier.fetchUser().then((_) {
|
||||
final apiClient = ref.read(apiClientProvider);
|
||||
subscribePushNotification(apiClient);
|
||||
final wsNotifier = ref.read(websocketStateProvider.notifier);
|
||||
wsNotifier.connect();
|
||||
if (context.mounted) Navigator.pop(context, true);
|
||||
});
|
||||
final challenge = SnAuthChallenge.fromJson(resp.data);
|
||||
onChallenge(challenge);
|
||||
final factorResp = await client.get(
|
||||
'/auth/challenge/${challenge.id}/factors',
|
||||
);
|
||||
onFactor(
|
||||
List<SnAuthFactor>.from(
|
||||
factorResp.data.map((ele) => SnAuthFactor.fromJson(ele)),
|
||||
),
|
||||
);
|
||||
onNext();
|
||||
} catch (err) {
|
||||
if (err is SignInWithAppleAuthorizationException) return;
|
||||
showErrorAlert(err);
|
||||
@ -600,6 +627,32 @@ class _LoginLookupScreen extends HookConsumerWidget {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> withOidc(String provider) async {
|
||||
final challengeId = await Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => OidcScreen(provider: provider.toLowerCase()),
|
||||
),
|
||||
);
|
||||
|
||||
final client = ref.watch(apiClientProvider);
|
||||
try {
|
||||
final resp = await client.get('/auth/challenge/$challengeId');
|
||||
final challenge = SnAuthChallenge.fromJson(resp.data);
|
||||
onChallenge(challenge);
|
||||
final factorResp = await client.get(
|
||||
'/auth/challenge/${challenge.id}/factors',
|
||||
);
|
||||
onFactor(
|
||||
List<SnAuthFactor>.from(
|
||||
factorResp.data.map((ele) => SnAuthFactor.fromJson(ele)),
|
||||
),
|
||||
);
|
||||
onNext();
|
||||
} catch (err) {
|
||||
showErrorAlert(err);
|
||||
}
|
||||
}
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
@ -635,6 +688,26 @@ class _LoginLookupScreen extends HookConsumerWidget {
|
||||
Text("loginOr").tr().fontSize(11).opacity(0.85),
|
||||
const Gap(8),
|
||||
Spacer(),
|
||||
IconButton.filledTonal(
|
||||
onPressed: () => withOidc('github'),
|
||||
padding: EdgeInsets.zero,
|
||||
icon: getProviderIcon(
|
||||
"github",
|
||||
size: 16,
|
||||
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
||||
),
|
||||
tooltip: 'GitHub',
|
||||
),
|
||||
IconButton.filledTonal(
|
||||
onPressed: () => withOidc('google'),
|
||||
padding: EdgeInsets.zero,
|
||||
icon: getProviderIcon(
|
||||
"google",
|
||||
size: 16,
|
||||
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
||||
),
|
||||
tooltip: 'Google',
|
||||
),
|
||||
IconButton.filledTonal(
|
||||
onPressed: withApple,
|
||||
padding: EdgeInsets.zero,
|
||||
|
@ -9,6 +9,7 @@ import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:island/pods/config.dart';
|
||||
import 'package:island/pods/network.dart';
|
||||
import 'package:island/services/udid.dart';
|
||||
import 'package:island/widgets/app_scaffold.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
|
||||
@ -27,6 +28,13 @@ class _OidcScreenState extends ConsumerState<OidcScreen> {
|
||||
String? currentUrl;
|
||||
final TextEditingController _urlController = TextEditingController();
|
||||
bool _isLoading = true;
|
||||
late Future<String> _deviceIdFuture;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_deviceIdFuture = getUdid();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
@ -43,7 +51,20 @@ class _OidcScreenState extends ConsumerState<OidcScreen> {
|
||||
appBar: AppBar(
|
||||
title: widget.title != null ? Text(widget.title!) : Text('login').tr(),
|
||||
),
|
||||
body: Column(
|
||||
body: FutureBuilder<String>(
|
||||
future: _deviceIdFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
|
||||
if (snapshot.hasError) {
|
||||
return Center(child: Text('somethingWentWrong').tr());
|
||||
}
|
||||
|
||||
final deviceId = snapshot.data!;
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: InAppWebView(
|
||||
@ -62,6 +83,7 @@ class _OidcScreenState extends ConsumerState<OidcScreen> {
|
||||
headers: {
|
||||
if (token?.token.isNotEmpty ?? false)
|
||||
'Authorization': 'AtField ${token!.token}',
|
||||
'X-Device-Id': deviceId,
|
||||
},
|
||||
),
|
||||
onWebViewCreated: (controller) {
|
||||
@ -81,7 +103,10 @@ class _OidcScreenState extends ConsumerState<OidcScreen> {
|
||||
},
|
||||
);
|
||||
},
|
||||
shouldOverrideUrlLoading: (controller, navigationAction) async {
|
||||
shouldOverrideUrlLoading: (
|
||||
controller,
|
||||
navigationAction,
|
||||
) async {
|
||||
final url = navigationAction.request.url;
|
||||
if (url != null) {
|
||||
setState(() {
|
||||
@ -94,11 +119,11 @@ class _OidcScreenState extends ConsumerState<OidcScreen> {
|
||||
final queryParams = url.queryParameters;
|
||||
|
||||
// Check if we're on the token page
|
||||
if (path.contains('/auth/callback')) {
|
||||
if (path.endsWith('/auth/callback')) {
|
||||
// Extract token from URL
|
||||
final token = queryParams['token'] ?? true;
|
||||
final challenge = queryParams['challenge'];
|
||||
// Return the token and close the webview
|
||||
Navigator.of(context).pop(token);
|
||||
Navigator.of(context).pop(challenge);
|
||||
return NavigationActionPolicy.CANCEL;
|
||||
}
|
||||
}
|
||||
@ -192,6 +217,8 @@ class _OidcScreenState extends ConsumerState<OidcScreen> {
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user