Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add new login method #1957

Merged
merged 4 commits into from
Oct 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 1 addition & 5 deletions pages/api/auth/[...nextauth].ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,6 @@ const createOptions = (req: NextApiRequest) => ({
// e.g. domain, username, password, 2FA token, etc.
credentials: {
token: { label: 'Token', type: 'text' },
rpcUrl: { label: 'rpc url', type: 'text' },
instanceURL: { label: 'Instance url', type: 'text' },
},
async authorize(credentials) {
Expand All @@ -137,10 +136,7 @@ const createOptions = (req: NextApiRequest) => ({
// Initialize instance api url
initialize({ apiURL: credentials.instanceURL });

const data = await AuthLinkAPI.loginWithAccessToken(
credentials.token,
credentials.rpcUrl,
);
const data = await AuthLinkAPI.loginWithAccessToken(credentials.token);

if (!data?.token?.accessToken) throw Error('Failed to authorize user!');

Expand Down
7 changes: 7 additions & 0 deletions src/components/Login/Login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { Accounts } from './render/Accounts';
import CreateAccounts from './render/CreateAccounts/CreateAccounts';
import LoginByEmail from './render/Email/LoginByEmail';
import { Options } from './render/Options';
import LoginByPAT from './render/PAT/LoginByPAT';
import { Profile } from './render/Profile';
import SigninMethod from './render/SignInMethod/SigninMethod';

Expand Down Expand Up @@ -345,6 +346,12 @@ export const Login: React.FC<LoginProps> = props => {
element={<LoginByEmail onNext={checkEmailRegistered} />}
/>

<Route
index={false}
path="/pat"
element={<LoginByPAT onNext={checkEmailRegistered} />}
/>

<Route
index={false}
path="/createAccounts"
Expand Down
38 changes: 38 additions & 0 deletions src/components/Login/render/PAT/LoginByPAT.style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { createStyles, makeStyles, Theme } from '@material-ui/core/styles';

export const useStyles = makeStyles((theme: Theme) =>
createStyles({
root: {
position: 'relative',
maxHeight: 'fit-content',
maxWidth: 508,
background: '#FFFFFF',
borderRadius: 10,
padding: 40,
boxShadow: '0px 2px 10px rgba(0, 0, 0, 0.05)',
display: 'flex',
flexDirection: 'column',
gap: 24,
'& .MuiFormControl-root': {
marginBottom: 0,
},
},
title: {
fontSize: 18,
fontWeight: 'bold',
color: 'black',
textAlign: 'center',
},
subtitle: {
fontSize: 14,
fontWeight: 'normal',
color: 'black',
textAlign: 'center',
},
actionWrapper: {
display: 'flex',
flexDirection: 'row',
gap: 24,
},
}),
);
122 changes: 122 additions & 0 deletions src/components/Login/render/PAT/LoginByPAT.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import { useState } from 'react';
import { useCookies } from 'react-cookie';
import { useNavigate } from 'react-router';

import { signIn } from 'next-auth/react';
import getConfig from 'next/config';
import { useRouter } from 'next/router';

import { Button, TextField, Typography } from '@material-ui/core';

import { useStyles } from './LoginByPAT.style';

import { COOKIE_INSTANCE_URL } from 'components/SelectServer';
import SelectServer from 'src/components/SelectServer';
import { useAlertHook } from 'src/hooks/use-alert.hook';
import { ServerListProps } from 'src/interfaces/server-list';
import i18n from 'src/locale';

type LoginByPATProps = {
onNext: (
successCallback: () => void,
failedCallback: () => void,
email: string,
) => Promise<void>;
};

const LoginByPAT = ({ onNext }: LoginByPATProps) => {
const styles = useStyles();
const router = useRouter();
const { publicRuntimeConfig } = getConfig();
const { showAlert } = useAlertHook();
const [cookies] = useCookies([COOKIE_INSTANCE_URL]);

const [token, setToken] = useState('');
const [error] = useState({
isError: false,
message: '',
});
const [, setDisableSignIn] = useState<boolean>(false);

const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const input = event.target.value;
setToken(input);
};

const navigate = useNavigate();

const handleNext = () => {
signIn('tokenCredentials', {
token,
instanceURL: cookies[COOKIE_INSTANCE_URL],
redirect: false,
callbackUrl: publicRuntimeConfig.appAuthURL,
}).then(response => {
if (response.ok) {
router.reload();
router.push('/');
}

if (response.error) {
showAlert({
message: token
? i18n.t('Login.Alert.Invalid_OTP')
: i18n.t('Login.Alert.Message'),
severity: 'error',
title: i18n.t('Login.Alert.Title'),
});
setDisableSignIn(false);
}
});
};

const handleBack = () => {
navigate('/');
};

const handleSwitchInstance = (
server: ServerListProps,
callback?: () => void,
) => {
callback && callback();
};

return (
<div className={styles.root}>
<div>
<Typography className={styles.title}>
{i18n.t('Login.Email.LoginByEmail.Title')}
</Typography>
<Typography className={styles.subtitle}>
{i18n.t('Login.Email.LoginByEmail.Subtitle')}
</Typography>
</div>
<TextField
fullWidth
id="user-email-input"
label="Token"
variant="outlined"
placeholder={i18n.t('Login.Email.LoginByEmail.Email_Placeholder')}
value={token}
onChange={handleChange}
error={error.isError}
helperText={error.isError ? error.message : ''}
/>
<SelectServer page="login" onSwitchInstance={handleSwitchInstance} />
<div className={styles.actionWrapper}>
<Button variant="outlined" color="primary" onClick={handleBack}>
{i18n.t('Login.Email.LoginByEmail.Back')}
</Button>
<Button
variant="contained"
color="primary"
disabled={!token.length || error.isError}
onClick={handleNext}>
{i18n.t('Login.Email.LoginByEmail.Next')}
</Button>
</div>
</div>
);
};

export default LoginByPAT;
11 changes: 11 additions & 0 deletions src/components/Login/render/SignInMethod/SigninMethod.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ export default function SigninMethod({
const handleSelected = ({ method }: { method: string }) => {
if (method === 'web2') {
navigate('/email');
} else if (method === 'pat') {
navigate('/pat');
} else {
navigate('/options');
}
Expand Down Expand Up @@ -87,6 +89,15 @@ export default function SigninMethod({
disabled={disableSignIn}
tooltip={i18n.t('Sign_In.Email.tooltip')}
/>
<div className={styles.textOr}>or</div>
<CardSign
title={i18n.t('Sign_In.Token.title')}
desc={i18n.t('Sign_In.Token.desc')}
image={<LoginWeb2 />}
onClick={() => handleSelected({ method: 'pat' })}
disabled={disableSignIn}
tooltip={i18n.t('Sign_In.Token.tooltip')}
/>
</div>
</div>
</>
Expand Down
54 changes: 8 additions & 46 deletions src/components/Settings/SharingSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { useSelector } from 'react-redux';

import {
Button,
TextField,
Paper,
Grid,
CircularProgress,
Expand All @@ -17,6 +16,7 @@ import { Modal } from '../atoms/Modal';
import { useStyles } from './Settings.styles';

import { sha256 } from 'js-sha256';
import * as AccessTokenAPI from 'src/lib/api/access-token';
import i18n from 'src/locale';
import { RootState } from 'src/reducers';
import { v4 as uuidv4 } from 'uuid';
Expand All @@ -28,29 +28,21 @@ const SharingSetting = () => {
);
const [tokenValue, setToken] = useState('');
const [modalOpen, setModalOpen] = useState(false);
const [error, setError] = useState({
isError: false,
message: '',
});

const onChangeToken = (event: React.ChangeEvent<HTMLInputElement>) => {
const input = event.target.value;

if (!input.length) {
setError({ isError: false, message: '' });
}
setToken(event.target.value);
};

const onClickAddToken = () => {
const onClickAddToken = async () => {
const token = uuidv4();
const tokenHash = sha256(token);
const first = token.slice(0, 4);
const second = token.slice(-4);
const replacement = 'xxxx-xxxx-xxxx-xxxx-xxxxxxxx';
const disguise = first + replacement + second;
console.log(tokenHash, disguise);
setModalOpen(true);
try {
await AccessTokenAPI.postToken(tokenHash, disguise);
setModalOpen(true);
} catch (error) {
console.error(error);
}

// TODO create access token on blockchain
// await createAccessToken(hash, wallet)
Expand Down Expand Up @@ -81,36 +73,6 @@ const SharingSetting = () => {
Token is {tokenValue}
</Typography>
</Modal>
<Typography className={styles.subtitle}>Input Code Here</Typography>
<div
className={styles.option}
style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
}}>
<>
<TextField
variant="outlined"
fullWidth
label={i18n.t('Setting.List_Menu.Token_Code')}
placeholder={i18n.t('Setting.List_Menu.Token_Code')}
value={tokenValue}
style={{ marginBottom: 'unset' }}
onChange={onChangeToken}
error={error.isError}
helperText={error.isError ? error.message : ''}
/>
<Button
size="small"
variant="contained"
color="primary"
style={{ marginLeft: '24px', marginRight: '17px' }}
onClick={onClickAddToken}>
Add
</Button>
</>
</div>
<div>
<Button
size="medium"
Expand Down
30 changes: 30 additions & 0 deletions src/lib/api/access-token.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import MyriadAPI from './base';

export const postToken = async (
hash: string,
disguise: string,
): Promise<void> => {
try {
await MyriadAPI().request({
url: `/user/personal-access-tokens`,
method: 'POST',
data: {
token: disguise,
hash: hash,
},
});
} catch (e) {
console.error(e);
}
};

export const getToken = async () => {
try {
await MyriadAPI().request({
url: `/user/personal-access-tokens`,
method: 'GET',
});
} catch (e) {
console.error(e);
}
};
2 changes: 0 additions & 2 deletions src/lib/api/auth-link.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,14 +72,12 @@ export const loginWithLink = async (

export const loginWithAccessToken = async (
token: string,
rpcUrl: string,
): Promise<LoginResponseProps> => {
const { data } = await MyriadAPI().request({
url: '/authentication/login/pat',
method: 'POST',
data: {
token,
rpcUrl,
},
headers: {
'Content-Type': 'application/json',
Expand Down