Skip to content

Commit

Permalink
Merge pull request #65 from multiversx/development
Browse files Browse the repository at this point in the history
Update native token verifier (#64) + e2e git action (#63)
  • Loading branch information
CiprianDraghici committed Nov 6, 2023
2 parents 555ae4d + e9169dc commit 7e6c691
Show file tree
Hide file tree
Showing 20 changed files with 171 additions and 155 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/cypress-e2e-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ jobs:
- name: Checkout
uses: actions/checkout@v3
- name: Cypress run
uses: cypress-io/github-action@v4
uses: cypress-io/github-action@v6
with:
start: yarn start
wait-on: "http://localhost:3001"
build: npm run build
start: npm start
4 changes: 2 additions & 2 deletions cypress/e2e/Converters/Converters.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ describe('Converters', () => {
it('should be display the required error message', () => {
cy.get('input').then((selectors) => {
selectors.each((index) => {
cy.get('button[type="submit"]').eq(index).click();
cy.get('button[type="submit"]').eq(index-1).click();
cy.get('[data-cy="error"]')
.eq(index)
.eq(index-1)
.should(AssertionEnum.contain, 'required');
});
});
Expand Down
4 changes: 4 additions & 0 deletions cypress/e2e/NativeAuth/NativeAuth.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,8 @@ describe('Native Auth', () => {
cy.login(walletIDEnum.unguardedWallet1);
cy.contains('Token Valid');
});
afterEach(()=>{
cy.getSelector('navigation-page-unlock').click();
cy.contains('Login')
})
});
5 changes: 3 additions & 2 deletions cypress/e2e/SignMessage/SignMessage.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ describe('Sign Message', () => {
cy.visit('/');
cy.get('a[href="/sign-message"]').click();
});
it.only('should validate the signature', () => {
it('should validate the signature', () => {
signHandler();
cy.get('.styles_code__HFHh6')
cy.getSelector('signaturePayload')
.then((txt) => {
payload = txt.text();
})
Expand All @@ -23,6 +23,7 @@ describe('Sign Message', () => {

it('should return Invalid signature payload', () => {
cy.get('textarea[name="signedMessage"]').type('Invalid#');
cy.contains('button', 'Verify').click();
cy.contains('Invalid signature payload');
});

Expand Down
1 change: 1 addition & 0 deletions cypress/e2e/SignMessage/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export const signHandler = () => {
cy.get('button[type="submit"]').eq(0).click();
cy.get('button[type="submit"]').eq(0).click();
cy.getSelector('keystoreBtn').click();

cy.get('input[type=file]').selectFile('./cypress/assets/testKeystore.json', {
force: true
});
Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@
"@fortawesome/fontawesome-svg-core": "6.1.0",
"@fortawesome/free-solid-svg-icons": "6.1.0",
"@fortawesome/react-fontawesome": "0.2.0",
"@multiversx/sdk-core": "12.2.0",
"@multiversx/sdk-core": "12.12.0",
"@multiversx/sdk-dapp": "2.23.0",
"@multiversx/sdk-native-auth-server": "0.2.6",
"@multiversx/sdk-wallet": "4.2.0",
"@multiversx/sdk-native-auth-server": "1.0.10",
"@uiw/react-textarea-code-editor": "2.1.9",
"axios": "1.2.2",
"bignumber.js": "9.1.0",
Expand Down
1 change: 1 addition & 0 deletions src/localConstants/nativeAuth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const EXPIRY_SECONDS = 7200;
10 changes: 5 additions & 5 deletions src/main.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import { App } from './App.tsx'
import './index.css'
import React from 'react';
import ReactDOM from 'react-dom/client';
import { App } from './App';
import './index.css';

ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)
);
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { NativeAuthServer } from '@multiversx/sdk-native-auth-server';

import type { NativeAuthServerConfig } from '@multiversx/sdk-native-auth-server/lib/src/entities/native.auth.server.config';

export const decodeToken = async (
token: string,
config: Partial<NativeAuthServerConfig>
config: NativeAuthServerConfig,
) => {
try {
const server = new NativeAuthServer(config);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { NativeAuthServerConfig } from '@multiversx/sdk-native-auth-server/

export const validateToken = async (
token: string,
config: Partial<NativeAuthServerConfig>
config: NativeAuthServerConfig,
) => {
try {
const server = new NativeAuthServer(config);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './verifySignature';
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Address, SignableMessage } from '@multiversx/sdk-core';
import { UserVerifier } from '@multiversx/sdk-wallet';

export const verifySignature = (
address: string,
messageString: string,
signature: Uint8Array,
) => {
const bech32Address = Address.fromBech32(address);
const verifier = UserVerifier.fromAddress(bech32Address);

const signableMessage = new SignableMessage({
address: bech32Address,
message: Buffer.from(messageString, 'utf8'),
});

const cryptoMessageBuffer = Buffer.from(
signableMessage.serializeForSigning(),
);
const signatureBuffer = Buffer.from(signature);

try {
return verifier.verify(cryptoMessageBuffer, signatureBuffer);
} catch (error) {
console.error('Error verifying signature:', error);
return false;
}
};
62 changes: 37 additions & 25 deletions src/pages/Authentication/components/Input/hooks/useTokenActions.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import { ChangeEvent, FormEvent, useCallback, useEffect, useMemo } from 'react';
import { fallbackNetworkConfigurations } from '@multiversx/sdk-dapp/constants';
import { useGetIsLoggedIn } from '@multiversx/sdk-dapp/hooks/account/useGetIsLoggedIn';
import { NativeAuthServerConfig } from '@multiversx/sdk-native-auth-server/lib/src/entities/native.auth.server.config';
import { useFormikContext } from 'formik';
import debounce from 'lodash.debounce';
import { useChain } from 'hooks/useChain';
import { useGetNativeAuthToken } from 'hooks/useGetNativeAuthToken';
import { EXPIRY_SECONDS } from 'localConstants/nativeAuth';
import { emptyMetrics } from 'pages/Authentication/constants/metrics.contants';
import { useAuthenticationContext } from 'pages/Authentication/context';
import { fallbackNetworkConfigurations } from '@multiversx/sdk-dapp/constants';
import { useChain } from 'hooks/useChain';
import { MetricType } from 'pages/Authentication/types';
import { decodeToken } from '../components/Textarea/helpers/decodeToken';
import { validateToken } from '../components/Textarea/helpers/validateToken';
import { verifySignature } from '../components/Textarea/helpers/verifySignature';
import { FormValuesType } from '../types';
import { MetricType } from 'pages/Authentication/types';
import debounce from 'lodash.debounce';
import { useGetIsLoggedIn } from '@multiversx/sdk-dapp/hooks/account/useGetIsLoggedIn';
import { useGetNativeAuthToken } from 'hooks/useGetNativeAuthToken';

const TOKEN_REGEX = /\w+[\w.]+\w/g;

Expand All @@ -27,13 +30,20 @@ export const useTokenActions = () => {
const decodeAndValidateToken = useCallback(
async (token: string) => {
try {
const config = {
apiUrl: fallbackNetworkConfigurations[chain].apiAddress
const config: NativeAuthServerConfig = {
apiUrl: fallbackNetworkConfigurations[chain].apiAddress,
acceptedOrigins: [window.location.origin],
maxExpirySeconds: EXPIRY_SECONDS,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
isOriginAccepted: (_origin) => {
return true;
},
verifySignature,
};

const promises = [
decodeToken(token, config),
validateToken(token, config)
validateToken(token, config),
];

setIsValidating(true);
Expand All @@ -42,26 +52,28 @@ export const useTokenActions = () => {

const tokenExpired =
valid.status === 'rejected' &&
valid.reason.message === 'Token expired';
['Token expired', 'maxExpirySeconds'].some((x) =>
valid.reason.message.includes(x),
);

if (tokenExpired) {
setFieldError('token', 'Token Expired');
setFieldError('message', undefined);
return;
}

if (decoded.status === 'rejected') {
setFieldError('token', 'Token Undecodable');
setFieldError(
'message',
'The provided token is not a NativeAuth token.'
'The provided token is not a NativeAuth token.',
);
setMetrics(emptyMetrics);
return;
} else if (decoded.value) {
setMetrics(decoded.value as MetricType);
}

if (tokenExpired) {
setFieldError('token', 'Token Expired');
setFieldError('message', undefined);
return;
}

if (valid.status === 'rejected') {
setFieldError('token', 'Token Invalid');
setFieldError('message', "Signature doesn't match.");
Expand All @@ -79,12 +91,12 @@ export const useTokenActions = () => {
setIsValidating(false);
}
},
[chain, setFieldError, setIsValidating, setMetrics]
[chain, setFieldError, setIsValidating, setMetrics],
);

const debouncedDecodeAndValidateToken = useMemo(
() => debounce(decodeAndValidateToken, 200),
[decodeAndValidateToken]
[decodeAndValidateToken],
);

const handleChange = useCallback(
Expand All @@ -101,7 +113,7 @@ export const useTokenActions = () => {
setFieldError('token', 'Token Undecodable');
setFieldError(
'message',
'The provided token is not a NativeAuth token.'
'The provided token is not a NativeAuth token.',
);
setMetrics(emptyMetrics);
return;
Expand All @@ -114,15 +126,15 @@ export const useTokenActions = () => {
setFieldTouched,
debouncedDecodeAndValidateToken,
setFieldError,
setMetrics
]
setMetrics,
],
);

const handlePreventDefault = useCallback(
(event: FormEvent<HTMLDivElement>) => {
event.preventDefault();
},
[]
[],
);

const handlePaste = useCallback(
Expand All @@ -136,7 +148,7 @@ export const useTokenActions = () => {

handleChange(token);
},
[handleChange, handlePreventDefault]
[handleChange, handlePreventDefault],
);

useEffect(() => {
Expand All @@ -153,6 +165,6 @@ export const useTokenActions = () => {
return {
handleChange,
handlePaste,
handlePreventDefault
handlePreventDefault,
};
};
Loading

0 comments on commit 7e6c691

Please sign in to comment.