@@ -3,6 +3,7 @@ import 'dart:async';
3
3
import 'package:flutter/foundation.dart' ;
4
4
import 'package:flutter/material.dart' ;
5
5
import 'package:flutter/services.dart' ;
6
+ import 'package:sign_in_with_apple/sign_in_with_apple.dart' ;
6
7
import 'package:url_launcher/url_launcher.dart' ;
7
8
8
9
import '../api/exception.dart' ;
@@ -354,12 +355,10 @@ class _LoginPageState extends State<LoginPage> {
354
355
&& defaultTargetPlatform == TargetPlatform .iOS
355
356
&& e.message != null && e.message! .startsWith ('Error while launching' )) {
356
357
// Ignore; I've seen this on my iPhone even when auth succeeds.
357
- // Specifically, Apple web auth…which on iOS should be replaced by
358
- // Apple native auth; that's #462.
358
+ // Specifically, Apple web auth.
359
359
// Possibly related:
360
360
// https://github.com/flutter/flutter/issues/91660
361
361
// but in that issue, people report authentication not succeeding.
362
- // TODO(#462) remove this?
363
362
return ;
364
363
}
365
364
@@ -428,6 +427,48 @@ class _LoginPageState extends State<LoginPage> {
428
427
}
429
428
}
430
429
430
+ Future <void > _handleNativeAppleAuth () async {
431
+ final state = generateRandomToken ();
432
+ final credential = await SignInWithApple .getAppleIDCredential (
433
+ state: state,
434
+ scopes: [
435
+ AppleIDAuthorizationScopes .fullName,
436
+ AppleIDAuthorizationScopes .email,
437
+ ],
438
+ );
439
+ if (credential.state != state) throw Exception ('`state` mismatch' );
440
+
441
+ __otp = generateOtp ();
442
+
443
+ final url = widget.serverSettings.realmUrl.resolve ('/complete/apple/' )
444
+ .replace (queryParameters: {'mobile_flow_otp' : _otp! , 'native_flow' : 'true' , 'id_token' : credential.identityToken});
445
+
446
+ await ZulipBinding .instance.launchUrl (url, mode: LaunchMode .inAppBrowserView);
447
+ }
448
+
449
+ bool _canUseNativeAppleFlow () {
450
+ // When the platform is iOS, [SignInWithApple.isAvailable] is always true
451
+ // because the minimum OS version is 14.0.
452
+ if (defaultTargetPlatform != TargetPlatform .iOS) return false ;
453
+
454
+ // The native flow for Apple auth assumes that the app and the server
455
+ // are operated by the same organization, so that for a user to
456
+ // entrust private information to either one is the same as entrusting
457
+ // it to the other. Check that this realm is on such a server.
458
+ //
459
+ // (For other realms, we'll simply fall back to the web flow, which
460
+ // handles things appropriately without relying on that assumption.)
461
+ return isAppOwnDomain (widget.serverSettings.realmUrl);
462
+ }
463
+
464
+ Future <void > _handleAuth (ExternalAuthenticationMethod method) async {
465
+ if (method.name == 'apple' && _canUseNativeAppleFlow ()) {
466
+ await _handleNativeAppleAuth ();
467
+ } else {
468
+ await _beginWebAuth (method);
469
+ }
470
+ }
471
+
431
472
@override
432
473
Widget build (BuildContext context) {
433
474
assert (! PerAccountStoreWidget .debugExistsOf (context));
@@ -450,7 +491,7 @@ class _LoginPageState extends State<LoginPage> {
450
491
? Image .network (icon, width: 24 , height: 24 )
451
492
: null ,
452
493
onPressed: ! _inProgress
453
- ? () => _beginWebAuth (method)
494
+ ? () => _handleAuth (method)
454
495
: null ,
455
496
label: Text (
456
497
zulipLocalizations.signInWithFoo (method.displayName)));
0 commit comments