Skip to content

Commit

Permalink
[draft] Use "Sign in with Apple".
Browse files Browse the repository at this point in the history
  • Loading branch information
Chris Bobbe authored and chrisbobbe committed May 22, 2020
1 parent ff7419f commit 9812a3c
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 16 deletions.
18 changes: 16 additions & 2 deletions src/api/settings/getServerSettings.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,28 @@ export type AuthenticationMethods = {
...
};

export type ExternalAuthenticationMethod = {|
name: string,
type BaseExternalAuthenticationMethod = {|
display_name: string,
display_icon: string | null,
login_url: string,
signup_url: string,
|};

export type AppleExternalAuthenticationMethod = {|
name: 'apple',
apple_kid: string,
...BaseExternalAuthenticationMethod,
|};

export type OtherExternalAuthenticationMethod = {|
name: string, // I'd like this to be "all strings except 'apple'"
...BaseExternalAuthenticationMethod,
|};

export type ExternalAuthenticationMethod =
| AppleExternalAuthenticationMethod
| OtherExternalAuthenticationMethod;

export type ApiResponseServerSettings = {|
...ApiResponseSuccess,
authentication_methods: AuthenticationMethods,
Expand Down
1 change: 1 addition & 0 deletions src/common/Icons.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export const IconPin: IconType = props => <SimpleLineIcons name="pin" {...props}
export const IconPrivate: IconType = props => <Feather name="lock" {...props} />;
export const IconPrivateChat: IconType = props => <Feather name="mail" {...props} />;
export const IconDownArrow: IconType = props => <Feather name="chevron-down" {...props} />;
export const IconApple: IconType = props => <IoniconsIcon name="logo-apple" {...props} />;
export const IconGoogle: IconType = props => <IoniconsIcon name="logo-google" {...props} />;
export const IconGitHub: IconType = props => <Feather name="github" {...props} />;
export const IconWindows: IconType = props => <IoniconsIcon name="logo-windows" {...props} />;
Expand Down
73 changes: 59 additions & 14 deletions src/start/AuthScreen.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,37 @@
/* @flow strict-local */

import React, { PureComponent } from 'react';
import { Linking } from 'react-native';
import { Linking, Platform } from 'react-native';
import type { NavigationScreenProp } from 'react-navigation';
import * as AppleAuthentication from 'expo-apple-authentication';

import type {
AuthenticationMethods,
Dispatch,
ExternalAuthenticationMethod,
ApiResponseServerSettings,
} from '../types';
import { IconPrivate, IconGoogle, IconGitHub, IconWindows, IconTerminal } from '../common/Icons';
import {
IconApple,
IconPrivate,
IconGoogle,
IconGitHub,
IconWindows,
IconTerminal,
} from '../common/Icons';
import type { IconType } from '../common/Icons';
import { connect } from '../react-redux';
import styles from '../styles';
import { Centerer, Screen, ZulipButton } from '../common';
import { getCurrentRealm } from '../selectors';
import RealmInfo from './RealmInfo';
import { getFullUrl } from '../utils/url';
import { getFullUrl, encodeParamsForUrl } from '../utils/url';
import * as webAuth from './webAuth';
import { loginSuccess, navigateToDev, navigateToPassword } from '../actions';
import IosCompliantAppleAuthButton from './IosCompliantAppleAuthButton';
import openLink from '../utils/openLink';

const KANDRA_APPLE_KID = 'asdfjkl;'; // TODO, of course (likely won't live here)
/**
* Describes a method for authenticating to the server.
*
Expand Down Expand Up @@ -99,6 +110,7 @@ const externalMethodIcons = new Map([
['google', IconGoogle],
['github', IconGitHub],
['azuread', IconWindows],
['apple', IconApple],
]);

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

handleAuth = (method: AuthenticationMethodDetails) => {
handleNativeAppleAuth = async () => {
const state = await webAuth.generateRandomToken();
const credential = await AppleAuthentication.signInAsync({ state });
if (credential.state !== state) {
throw new Error('`state` mismatch');
}

otp = await webAuth.generateOtp();

const params = encodeParamsForUrl({
mobile_flow_otp: otp,
native_flow: true,
id_token: credential.identityToken,
});

openLink(`${this.props.realm}/complete/apple/?${params}`);
};

handleAuth = async (method: AuthenticationMethodDetails) => {
const { action } = method;
const shouldUseNativeAppleFlow =
method.name === 'apple'
&& method.apple_kid === KANDRA_APPLE_KID
&& (await AppleAuthentication.isAvailableAsync());

if (action === 'dev') {
this.handleDevAuth();
} else if (action === 'password') {
this.handlePassword();
} else if (shouldUseNativeAppleFlow) {
this.handleNativeAppleAuth();
} else {
this.beginWebAuth(action.url);
}
Expand All @@ -253,16 +290,24 @@ class AuthScreen extends PureComponent<Props> {
{activeAuthentications(
serverSettings.authentication_methods,
serverSettings.external_authentication_methods,
).map(auth => (
<ZulipButton
key={auth.name}
style={styles.halfMarginTop}
secondary
text={`Sign in with ${auth.displayName}`}
Icon={auth.Icon}
onPress={() => this.handleAuth(auth)}
/>
))}
).map(auth =>
auth.name === 'apple' && Platform.OS === 'ios' ? (
<IosCompliantAppleAuthButton
key={auth.name}
style={styles.halfMarginTop}
onPress={() => this.handleAuth(auth)}
/>
) : (
<ZulipButton
key={auth.name}
style={styles.halfMarginTop}
secondary
text={`Sign in with ${auth.displayName}`}
Icon={auth.Icon}
onPress={() => this.handleAuth(auth)}
/>
),
)}
</Centerer>
</Screen>
);
Expand Down

0 comments on commit 9812a3c

Please sign in to comment.