Skip to content
This repository was archived by the owner on Feb 8, 2025. It is now read-only.

Commit

Permalink
Use noncess auth
Browse files Browse the repository at this point in the history
Unfortunately cookies are problematic on RN - facebook/react-native#23185
  • Loading branch information
hbriese committed May 16, 2023
1 parent 59153e2 commit 4bb48ee
Show file tree
Hide file tree
Showing 3 changed files with 37 additions and 42 deletions.
4 changes: 2 additions & 2 deletions api/src/features/auth/auth.middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ export class AuthMiddleware implements NestMiddleware {
try {
const r = await message.validate(signature, this.provider);

// Allow for nonceless authentication if expiration time is set
if (r.nonce === 'expiration') {
// Allow for nonceless authentication if expiration time is set; note that the standard mandates that `nonce` is set
if (r.nonce === 'nonceless') {
if (!r.expirationTime) return 'Nonce may only be omitted if expiration time is set';
} else if (!req.session?.nonce) {
return 'Session lacking nonce; make sure cookies are being included';
Expand Down
73 changes: 34 additions & 39 deletions app/src/gql/api/client/useApiAuth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,66 +4,34 @@ import { onError } from '@apollo/client/link/error';
import { SiweMessage } from 'siwe';
import { tryAcquire, E_ALREADY_LOCKED, Mutex } from 'async-mutex';
import { CONFIG } from '~/util/config';
import { atom, useRecoilState } from 'recoil';
import { getSecureStore, persistAtom } from '~/util/effect/persistAtom';
import { useCallback, useMemo, useRef } from 'react';
import { useApprover } from '@network/useApprover';
import { DateTime } from 'luxon';
import { Approver } from 'lib';
import { persistedAtom } from '~/util/persistedAtom';
import { useAtom } from 'jotai';

const fetchMutex = new Mutex();

const HOST_PATTERN = /^(?:[a-z]+?:\/\/)?([^:/?#]+(:\d+)?)/; // RN lacks URL support )':
// protocol:// (hostname:port)
const API_HOSTNAME = HOST_PATTERN.exec(CONFIG.apiUrl)![1];

const fetchToken = async (approver: Approver): Promise<string> => {
const nonceResp = await fetch(`${CONFIG.apiUrl}/auth/nonce`, { credentials: 'include' });

const message = new SiweMessage({
version: '1',
domain: API_HOSTNAME,
address: approver.address,
nonce: await nonceResp.text(),
expirationTime: DateTime.now().plus({ days: 2 }).toString(),
// Required but unused
uri: 'https://app.zallo.com',
chainId: 0,
});
const token = {
message,
signature: await approver.signMessage(message.prepareMessage()),
};

return JSON.stringify(token);
};
const tokenAtom = persistedAtom<string | null>('ApiToken', null);

const apiTokenState = atom<string | null>({
key: 'apiToken',
default: null,
effects: [
persistAtom({
storage: getSecureStore(),
}),
],
});
const fetchMutex = new Mutex();

export const useApiAuth = () => {
const approver = useApprover();
const [token, setToken] = useRecoilState(apiTokenState);
const [token, setToken] = useAtom(tokenAtom);

const tokenRef = useRef<string | null>(token);

const reset = useCallback(async () => {
// Ensure token is reset exactly once at any given time
try {
await tryAcquire(fetchMutex).runExclusive(async () => {
tokenRef.current = await fetchToken(approver);
tokenRef.current = await getToken(approver);
setToken(tokenRef.current);
});
} catch (e) {
if (e === E_ALREADY_LOCKED) {
await fetchMutex.waitForUnlock();
// Token has been reset by another call
} else {
throw e;
}
Expand Down Expand Up @@ -98,3 +66,30 @@ export const useApiAuth = () => {
return { link, getHeaders };
}, [reset]);
};

const HOST_PATTERN = /^(?:[a-z]+?:\/\/)?([^:/?#]+(:\d+)?)/; // RN lacks URL support )':
// protocol:// (hostname:port)
const API_HOSTNAME = HOST_PATTERN.exec(CONFIG.apiUrl)![1];

async function getToken(approver: Approver): Promise<string> {
// Cookies are problematic on RN - https://github.com/facebook/react-native/issues/23185
// const nonce = await (await fetch(`${CONFIG.apiUrl}/auth/nonce`, { credentials: 'include' })).text();
const nonce = 'nonceless';

const message = new SiweMessage({
version: '1',
domain: API_HOSTNAME,
address: approver.address,
nonce,
expirationTime: DateTime.now().plus({ days: 2 }).toString(),
uri: 'https://app.zallo.com', // Required but unused
chainId: 0,
});

const token = {
message,
signature: await approver.signMessage(message.prepareMessage()),
};

return JSON.stringify(token);
}
2 changes: 1 addition & 1 deletion app/src/gql/api/client/usePromisedApiClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export const usePromisedApiClient = () => {
persistedQueryLink,
new HttpLink({
uri: `${CONFIG.apiUrl}/graphql`,
credentials: 'include',
credentials: 'include', // TODO: try remove; RN cookies are problematic - https://github.com/facebook/react-native/issues/23185
}),
]),
subscriptions: new GraphQLWsLink(
Expand Down

0 comments on commit 4bb48ee

Please sign in to comment.