Skip to content

Commit

Permalink
feat(SignIn): add sign in component
Browse files Browse the repository at this point in the history
  • Loading branch information
kponmuth authored and kishorepmr committed May 13, 2022
1 parent 0c88533 commit 6cf57d7
Show file tree
Hide file tree
Showing 5 changed files with 188 additions and 0 deletions.
1 change: 1 addition & 0 deletions .storybook/preview.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export const parameters = {
'Webex Avatar',
'Webex Member Roster',
'Webex Member',
'Sign In',
],
'Messaging',
[
Expand Down
113 changes: 113 additions & 0 deletions src/components/SignIn/SignIn.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import React, {useState} from 'react';
import PropTypes from 'prop-types';
import {Button} from '../generic';
import Spinner from '../generic/Spinner/Spinner';

/**
* Performs OAuth 2.0 Authorization
*
* @param {object} props Data passed to the component
* @param {string} props.authUrl Authorization url
* @param {string} props.clientID Client ID of the app
* @param {string} props.redirectUri Redirect Url registered to capture the code
* @param {string} props.scope Scope required for the app
* @param {Function} props.signInResponse Function called on successfull sign in
* @param {Function} props.getAccessToken Function called to fetch access token from backend server
* @param {object} props.tokenStoragePolicy Store token in cookie, local or session storage
* @param {string} props.authType Authorization server type
* @returns {object} JSX of the component
*/
export default function SignIn({
authUrl,
clientID,
redirectUri,
authType,
scope,
signInResponse,
getAccessToken,
tokenStoragePolicy,
}) {
const [isAuthenticating, setIsAuthenticating] = useState(false);

const openAuthUrl = () => {
const arr = new Uint8Array(4);
const state = window.crypto.getRandomValues(arr);
const fullAuthUrl = `${authUrl}?client_id=${clientID}&response_type=code&redirect_uri=${encodeURI(redirectUri)}${scope !== '' ? `&scope=${encodeURI(scope)}` : ''}&state=${state}`;

const newWindow = window.open(fullAuthUrl, 'targetWindow', 'toolbar=no,location=no,status=no,menubar=no,scrollbars=yes,resizable=yes,width=400,height=700');

setIsAuthenticating(true);

if (authType === 'Custom') {
const timer = setInterval(() => {
if (newWindow.closed) {
clearInterval(timer);
getAccessToken().then((accessToken) => {
if (accessToken) {
if (Object.keys(tokenStoragePolicy).length !== 0) {
const {name, place, ttl} = tokenStoragePolicy;

switch (place) {
case 'cookie': {
const expiry = new Date();

expiry.setSeconds(ttl);
document.cookie = `${name}=${accessToken}; secure; expires=${expiry.toUTCString()}`;
break;
}
case 'session':
sessionStorage.setItem(name, accessToken);
break;
case 'local':
localStorage.setItem(name, accessToken);
break;
default:
break;
}
}
}
signInResponse(clientID, accessToken || Error('No access token was returned'));
})
.catch((err) => {
signInResponse(clientID, Error('Failed to fetch access token: ', err));
});
setIsAuthenticating(false);
}
}, 500);
}
};

return (
<div className="sign-in-wrapper">
{isAuthenticating ? <Spinner /> : (
<Button
type="join"
size={40}
onClick={openAuthUrl}
>
Sign In
</Button>
)}
</div>
);
}

SignIn.propTypes = {
authUrl: PropTypes.string.isRequired,
clientID: PropTypes.string.isRequired,
redirectUri: PropTypes.string.isRequired,
authType: PropTypes.string.isRequired,
scope: PropTypes.string,
signInResponse: PropTypes.func,
getAccessToken: PropTypes.func,
tokenStoragePolicy: PropTypes.shape(
{place: PropTypes.string, name: PropTypes.string, ttl: PropTypes.number},
),
};

SignIn.defaultProps = {
scope: '',
signInResponse: () => {},
getAccessToken: () => {},
tokenStoragePolicy: {},
};
28 changes: 28 additions & 0 deletions src/components/SignIn/SignIn.stories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React from 'react';
import SignIn from './SignIn';

export default {
title: 'Platform/Sign In',
component: SignIn,
};

const Template = (args) => <SignIn {...args} />;

export const WebexAuth = Template.bind({});
WebexAuth.args = {
authUrl: 'https://webexapis.com/v1/authorize',
responseType: 'code',
clientID: process.env.webex_int_client_id,
redirectUri: 'http://example.com/verification',
scope: process.env.webex_int_scope,
authType: 'Custom',
signInResponse: (clientID, accessToken) => `Example: Send access token as props to create space: ${clientID === process.env.webex_int_client_id ? accessToken : ''}`,
getAccessToken: () => fetch('http://example.com/access_token').then((res) => {
if (res?.data?.access_token) {
return res.data.accessToken;
}

throw Error('Failed to fetch access token');
}),
tokenStoragePolicy: {place: 'cookie', name: 'integ_cookie', ttl: 1209600},
};
45 changes: 45 additions & 0 deletions src/components/SignIn/__snapshots__/SignIn.stories.storyshot
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Storyshots Platform/Sign In Webex Auth 1`] = `
Array [
<div
className="sign-in-wrapper"
>
<button
autoFocus={false}
className="wxc wxc-button wxc-button--join"
onClick={[Function]}
style={
Object {
"height": 40,
}
}
tabIndex={0}
type="button"
>
Sign In
</button>

</div>,
<video
autoPlay={true}
height="0"
id="remote-video"
loop={true}
muted={true}
playsInline={true}
src="./video/ongoing-meeting.mp4"
width="0"
/>,
<video
autoPlay={true}
height="0"
id="remote-share"
loop={true}
muted={true}
playsInline={true}
src="./video/ongoing-share.mp4"
width="0"
/>,
]
`;
1 change: 1 addition & 0 deletions src/components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export {default as WebexMessaging} from './WebexMessaging/WebexMessaging';
export {default as WebexRemoteMedia} from './WebexRemoteMedia/WebexRemoteMedia';
export {default as WebexSettings} from './WebexSettings/WebexSettings';
export {default as WebexWaitingForHost} from './WebexWaitingForHost/WebexWaitingForHost';
export {default as SignIn} from './SignIn/SignIn';
export {default as useMeetingDestination} from './hooks/useMeetingDestination';
export {default as withMeeting} from './hoc/withMeeting';
export {default as withAdapter} from './hoc/withAdapter';
Expand Down

0 comments on commit 6cf57d7

Please sign in to comment.