Skip to content

Commit 9812a3c

Browse files
Chris Bobbechrisbobbe
Chris Bobbe
authored andcommitted
[draft] Use "Sign in with Apple".
1 parent ff7419f commit 9812a3c

File tree

3 files changed

+76
-16
lines changed

3 files changed

+76
-16
lines changed

src/api/settings/getServerSettings.js

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,28 @@ export type AuthenticationMethods = {
1414
...
1515
};
1616

17-
export type ExternalAuthenticationMethod = {|
18-
name: string,
17+
type BaseExternalAuthenticationMethod = {|
1918
display_name: string,
2019
display_icon: string | null,
2120
login_url: string,
2221
signup_url: string,
2322
|};
2423

24+
export type AppleExternalAuthenticationMethod = {|
25+
name: 'apple',
26+
apple_kid: string,
27+
...BaseExternalAuthenticationMethod,
28+
|};
29+
30+
export type OtherExternalAuthenticationMethod = {|
31+
name: string, // I'd like this to be "all strings except 'apple'"
32+
...BaseExternalAuthenticationMethod,
33+
|};
34+
35+
export type ExternalAuthenticationMethod =
36+
| AppleExternalAuthenticationMethod
37+
| OtherExternalAuthenticationMethod;
38+
2539
export type ApiResponseServerSettings = {|
2640
...ApiResponseSuccess,
2741
authentication_methods: AuthenticationMethods,

src/common/Icons.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ export const IconPin: IconType = props => <SimpleLineIcons name="pin" {...props}
4949
export const IconPrivate: IconType = props => <Feather name="lock" {...props} />;
5050
export const IconPrivateChat: IconType = props => <Feather name="mail" {...props} />;
5151
export const IconDownArrow: IconType = props => <Feather name="chevron-down" {...props} />;
52+
export const IconApple: IconType = props => <IoniconsIcon name="logo-apple" {...props} />;
5253
export const IconGoogle: IconType = props => <IoniconsIcon name="logo-google" {...props} />;
5354
export const IconGitHub: IconType = props => <Feather name="github" {...props} />;
5455
export const IconWindows: IconType = props => <IoniconsIcon name="logo-windows" {...props} />;

src/start/AuthScreen.js

Lines changed: 59 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,37 @@
11
/* @flow strict-local */
22

33
import React, { PureComponent } from 'react';
4-
import { Linking } from 'react-native';
4+
import { Linking, Platform } from 'react-native';
55
import type { NavigationScreenProp } from 'react-navigation';
6+
import * as AppleAuthentication from 'expo-apple-authentication';
67

78
import type {
89
AuthenticationMethods,
910
Dispatch,
1011
ExternalAuthenticationMethod,
1112
ApiResponseServerSettings,
1213
} from '../types';
13-
import { IconPrivate, IconGoogle, IconGitHub, IconWindows, IconTerminal } from '../common/Icons';
14+
import {
15+
IconApple,
16+
IconPrivate,
17+
IconGoogle,
18+
IconGitHub,
19+
IconWindows,
20+
IconTerminal,
21+
} from '../common/Icons';
1422
import type { IconType } from '../common/Icons';
1523
import { connect } from '../react-redux';
1624
import styles from '../styles';
1725
import { Centerer, Screen, ZulipButton } from '../common';
1826
import { getCurrentRealm } from '../selectors';
1927
import RealmInfo from './RealmInfo';
20-
import { getFullUrl } from '../utils/url';
28+
import { getFullUrl, encodeParamsForUrl } from '../utils/url';
2129
import * as webAuth from './webAuth';
2230
import { loginSuccess, navigateToDev, navigateToPassword } from '../actions';
31+
import IosCompliantAppleAuthButton from './IosCompliantAppleAuthButton';
32+
import openLink from '../utils/openLink';
2333

34+
const KANDRA_APPLE_KID = 'asdfjkl;'; // TODO, of course (likely won't live here)
2435
/**
2536
* Describes a method for authenticating to the server.
2637
*
@@ -99,6 +110,7 @@ const externalMethodIcons = new Map([
99110
['google', IconGoogle],
100111
['github', IconGitHub],
101112
['azuread', IconWindows],
113+
['apple', IconApple],
102114
]);
103115

104116
/** Exported for tests only. */
@@ -229,12 +241,37 @@ class AuthScreen extends PureComponent<Props> {
229241
this.props.dispatch(navigateToPassword(serverSettings.require_email_format_usernames));
230242
};
231243

232-
handleAuth = (method: AuthenticationMethodDetails) => {
244+
handleNativeAppleAuth = async () => {
245+
const state = await webAuth.generateRandomToken();
246+
const credential = await AppleAuthentication.signInAsync({ state });
247+
if (credential.state !== state) {
248+
throw new Error('`state` mismatch');
249+
}
250+
251+
otp = await webAuth.generateOtp();
252+
253+
const params = encodeParamsForUrl({
254+
mobile_flow_otp: otp,
255+
native_flow: true,
256+
id_token: credential.identityToken,
257+
});
258+
259+
openLink(`${this.props.realm}/complete/apple/?${params}`);
260+
};
261+
262+
handleAuth = async (method: AuthenticationMethodDetails) => {
233263
const { action } = method;
264+
const shouldUseNativeAppleFlow =
265+
method.name === 'apple'
266+
&& method.apple_kid === KANDRA_APPLE_KID
267+
&& (await AppleAuthentication.isAvailableAsync());
268+
234269
if (action === 'dev') {
235270
this.handleDevAuth();
236271
} else if (action === 'password') {
237272
this.handlePassword();
273+
} else if (shouldUseNativeAppleFlow) {
274+
this.handleNativeAppleAuth();
238275
} else {
239276
this.beginWebAuth(action.url);
240277
}
@@ -253,16 +290,24 @@ class AuthScreen extends PureComponent<Props> {
253290
{activeAuthentications(
254291
serverSettings.authentication_methods,
255292
serverSettings.external_authentication_methods,
256-
).map(auth => (
257-
<ZulipButton
258-
key={auth.name}
259-
style={styles.halfMarginTop}
260-
secondary
261-
text={`Sign in with ${auth.displayName}`}
262-
Icon={auth.Icon}
263-
onPress={() => this.handleAuth(auth)}
264-
/>
265-
))}
293+
).map(auth =>
294+
auth.name === 'apple' && Platform.OS === 'ios' ? (
295+
<IosCompliantAppleAuthButton
296+
key={auth.name}
297+
style={styles.halfMarginTop}
298+
onPress={() => this.handleAuth(auth)}
299+
/>
300+
) : (
301+
<ZulipButton
302+
key={auth.name}
303+
style={styles.halfMarginTop}
304+
secondary
305+
text={`Sign in with ${auth.displayName}`}
306+
Icon={auth.Icon}
307+
onPress={() => this.handleAuth(auth)}
308+
/>
309+
),
310+
)}
266311
</Centerer>
267312
</Screen>
268313
);

0 commit comments

Comments
 (0)