Skip to content

Commit

Permalink
Merge pull request #127 from MobilityData/100-display-token-in-api-ui
Browse files Browse the repository at this point in the history
feat: 100 display token in api UI
  • Loading branch information
qcdyx authored Oct 12, 2023
2 parents ed598c3 + 5f5cc7b commit 4905697
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 27 deletions.
138 changes: 114 additions & 24 deletions web-app/src/app/screens/Account.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,32 +8,53 @@ import {
Snackbar,
} from '@mui/material';
import { Visibility, VisibilityOff, ContentCopy } from '@mui/icons-material';

import { useSelector } from 'react-redux';
import { selectUserProfile } from '../store/selectors';
interface APIAccountState {
apiKey: string;
showApiKey: boolean;
refreshToken: string;
showRefreshToken: boolean;
accessToken: string;
showAccessToken: boolean;
}

export default function APIAccount(): React.ReactElement {
const [values, setValues] = React.useState<APIAccountState>({
apiKey: 'Your key is hidden',
showApiKey: false,
refreshToken: 'Your refresh token is hidden',
showRefreshToken: false,
accessToken: 'Your access token is hidden',
showAccessToken: false,
});
const user = useSelector(selectUserProfile);
const [openSnackbar, setOpenSnackbar] = React.useState(false);

const handleClickShowApiKey = (): void => {
setValues({
...values,
showApiKey: !values.showApiKey,
apiKey: values.showApiKey
? 'Your key is hidden'
: 'your-api-key-here-12345',
});
const handleClickShowApiKey = (tokenType: 'access' | 'refresh'): void => {
switch (tokenType) {
case 'access':
setValues({
...values,
showAccessToken: !values.showAccessToken,
accessToken: values.showAccessToken
? 'Your access token is hidden'
: user?.accessToken ?? 'Your access token is unavailable',
});
break;
case 'refresh':
setValues({
...values,
showRefreshToken: !values.showRefreshToken,
refreshToken: values.showRefreshToken
? 'Your refresh token is hidden'
: user?.refreshToken ?? 'Your refresh token is unavailable',
});
break;
default:
break;
}
};

const handleCopyToClipboard = (): void => {
const handleCopyToClipboard = (token: string): void => {
navigator.clipboard
.writeText('your-api-key-here-12345')
.writeText(token)
.then(() => {
setOpenSnackbar(true);
})
Expand Down Expand Up @@ -77,36 +98,105 @@ export default function APIAccount(): React.ReactElement {
color='primary'
sx={{ mt: 2, fontWeight: 'bold' }}
>
API Key
Refresh Token
</Typography>
<Typography
width={250}
variant='body1'
sx={{
display: 'inline-block',
mr: 2,
background: '#e6e6e6',
padding: 1,
borderRadius: 2,
wordBreak: 'break-word',
}}
>
{values.apiKey}
{values.refreshToken}
</Typography>
<IconButton
aria-label='Copy API key to clipboard'
aria-label='Copy Refresh Token to clipboard'
edge='end'
onClick={handleCopyToClipboard}
onClick={() => {
handleCopyToClipboard(values.refreshToken);
}}
sx={{ display: 'inline-block', verticalAlign: 'middle' }}
>
<ContentCopy />
</IconButton>
<IconButton
aria-label='toggle API key visibility'
onClick={handleClickShowApiKey}
aria-label='toggle Refresh Token visibility'
onClick={() => {
handleClickShowApiKey('refresh');
}}
edge='end'
sx={{ display: 'inline-block', verticalAlign: 'middle' }}
>
{values.showRefreshToken ? <VisibilityOff /> : <Visibility />}
</IconButton>

<Snackbar
open={openSnackbar}
autoHideDuration={2000}
onClose={() => {
setOpenSnackbar(false);
}}
message='Your Refresh Token is copied to your clipboard.'
anchorOrigin={{ horizontal: 'center', vertical: 'bottom' }}
action={
<React.Fragment>
<Button
color='primary'
size='small'
onClick={() => {
setOpenSnackbar(false);
}}
>
OK
</Button>
</React.Fragment>
}
/>

<Typography
component='h2'
variant='h6'
color='primary'
sx={{ mt: 2, fontWeight: 'bold' }}
>
Access Token
</Typography>
<Typography
variant='body1'
sx={{
display: 'inline-block',
mr: 2,
background: '#e6e6e6',
padding: 1,
borderRadius: 2,
wordBreak: 'break-word',
}}
>
{values.accessToken}
</Typography>
<IconButton
aria-label='Copy Access Token to clipboard'
edge='end'
onClick={() => {
handleCopyToClipboard(values.accessToken);
}}
sx={{ display: 'inline-block', verticalAlign: 'middle' }}
>
<ContentCopy />
</IconButton>
<IconButton
aria-label='toggle Access Token visibility'
onClick={() => {
handleClickShowApiKey('access');
}}
edge='end'
sx={{ display: 'inline-block', verticalAlign: 'middle' }}
>
{values.showApiKey ? <VisibilityOff /> : <Visibility />}
{values.showAccessToken ? <VisibilityOff /> : <Visibility />}
</IconButton>

<Snackbar
Expand All @@ -115,7 +205,7 @@ export default function APIAccount(): React.ReactElement {
onClose={() => {
setOpenSnackbar(false);
}}
message='Your API key is copied to your clipboard.'
message='Your Access Token is copied to your clipboard.'
anchorOrigin={{ horizontal: 'center', vertical: 'bottom' }}
action={
<React.Fragment>
Expand Down
8 changes: 7 additions & 1 deletion web-app/src/app/services/profile-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,22 @@ export const sendEmailVerification = async (): Promise<void> => {
/**
* Return the current user or null if the user is not logged in.
*/
export const getUserFromSession = (): User | null => {
export const getUserFromSession = async (): Promise<User | null> => {
const currentUser = app.auth().currentUser;
if (currentUser === null) {
return null;
}
const idTokenResult = await currentUser.getIdTokenResult(true);
const refreshToken = currentUser.refreshToken;
const accessToken = idTokenResult.token;
// const expiresIn = idTokenResult.expirationTime;
return {
fullname: currentUser?.displayName ?? undefined,
email: currentUser?.email ?? '',
// Organization cannot be retrieved from the current user
organization: undefined,
refreshToken,
accessToken,
};
};

Expand Down
4 changes: 2 additions & 2 deletions web-app/src/app/store/saga/auth-saga.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,11 @@ function* signUpSaga({
}>): Generator {
try {
yield app.auth().createUserWithEmailAndPassword(email, password);
const user = getUserFromSession();
const user = yield call(getUserFromSession);
if (user === null) {
throw new Error('User not found');
}
yield put(signUpSuccess(user));
yield put(signUpSuccess(user as User));
navigateTo(redirectScreen);
} catch (error) {
yield put(signUpFail(getAppError(error)));
Expand Down
2 changes: 2 additions & 0 deletions web-app/src/app/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ export interface User {
fullname?: string;
email: string;
organization?: string;
accessToken?: string;
refreshToken?: string;
}

export const USER_PROFILE = 'userProfile';
Expand Down

0 comments on commit 4905697

Please sign in to comment.