useAuth additional validation checks: localStorage ton_proof save > use > remove & selectWallet check

This commit is contained in:
Verticool 2025-03-18 22:42:25 +06:00
parent 46f3b22ec9
commit 1b96d049c7
2 changed files with 270 additions and 90 deletions

View File

@ -232,7 +232,7 @@ export const ViewContentPage = () => {
{content?.data?.downloadable && ( {content?.data?.downloadable && (
<Button <Button
onClick={() => handleDwnldContent()} onClick={() => handleDwnldContent()}
className={'h-[48px] bg-darkred mb-4'} className={'h-[48px] mb-4'}
label={`Скачать контент`} label={`Скачать контент`}
/> />
)} )}

View File

@ -1,34 +1,36 @@
import { useRef } from 'react'; import { useRef, useEffect } from 'react';
import { useTonConnectUI } from '@tonconnect/ui-react'; import { useTonConnectUI } from '@tonconnect/ui-react';
import { useMutation } from 'react-query'; import { useMutation } from 'react-query';
import { request } from '~/shared/libs'; import { request } from '~/shared/libs';
import { useWebApp } from '@vkruglikov/react-telegram-web-app'; import { useWebApp } from '@vkruglikov/react-telegram-web-app';
const sessionStorageKey = 'auth_v1_token'; const sessionStorageKey = 'auth_v1_token';
const tonProofStorageKey = 'stored_ton_proof';
const payloadTTLMS = 1000 * 60 * 20; const payloadTTLMS = 1000 * 60 * 20;
export const useAuth = () => { export const useAuth = () => {
const WebApp = useWebApp(); const WebApp = useWebApp();
// const wallet = useTonWallet();
const [tonConnectUI] = useTonConnectUI(); const [tonConnectUI] = useTonConnectUI();
const interval = useRef<ReturnType<typeof setInterval> | undefined>(); const interval = useRef<ReturnType<typeof setInterval> | undefined>();
const waitForWalletProof = async () => { // Store ton_proof when it becomes available
return new Promise((resolve, reject) => { useEffect(() => {
const timeout = setTimeout(() => reject(new Error('Timeout waiting for proof')), 30000); if (
const checkProof = setInterval(() => { tonConnectUI.wallet?.connectItems?.tonProof &&
const currentWallet = tonConnectUI.wallet; !('error' in tonConnectUI.wallet.connectItems.tonProof) &&
if ( tonConnectUI.wallet.account
currentWallet?.connectItems?.tonProof && ) {
!('error' in currentWallet.connectItems.tonProof) console.log('DEBUG: Storing ton_proof for future use');
) { localStorage.setItem(
clearInterval(checkProof); tonProofStorageKey,
clearTimeout(timeout); JSON.stringify({
resolve(currentWallet.connectItems.tonProof.proof); timestamp: Date.now(),
} account: tonConnectUI.wallet.account,
}, 500); proof: tonConnectUI.wallet.connectItems.tonProof.proof,
}); })
}; );
}
}, [tonConnectUI.wallet?.connectItems?.tonProof, tonConnectUI.wallet?.account]);
const makeAuthRequest = async (params: { const makeAuthRequest = async (params: {
twa_data: string; twa_data: string;
@ -37,26 +39,94 @@ export const useAuth = () => {
ton_proof: any; ton_proof: any;
}; };
}) => { }) => {
const res = await request.post<{ try {
connected_wallet: null | { const res = await request.post<{
version: string; connected_wallet: null | {
address: string; version: string;
ton_balance: string; address: string;
}; ton_balance: string;
auth_v1_token: string; };
}>('/auth.twa', params); auth_v1_token: string;
}>('/auth.twa', params);
if (res?.data?.auth_v1_token) { if (res?.data?.auth_v1_token) {
localStorage.setItem(sessionStorageKey, res.data.auth_v1_token); localStorage.setItem(sessionStorageKey, res.data.auth_v1_token);
} else {
throw new Error('Failed to get auth token'); // If we sent a proof, it was accepted, so keep record of that
if (params.ton_proof) {
console.log('DEBUG: Auth with proof successful');
}
} else {
throw new Error('Failed to get auth token');
}
return res;
} catch (error) {
// If we were using ton_proof and it failed, clear stored proof
if (params.ton_proof) {
console.log('DEBUG: Auth with proof failed, clearing stored proof');
localStorage.removeItem(tonProofStorageKey);
}
throw error;
} }
return res;
}; };
const makeSelectWalletRequest = async (params: { wallet_address: string }) => { const makeSelectWalletRequest = async (params: { wallet_address: string }) => {
const res = await request.post('/auth.selectWallet', params); try {
return res; const res = await request.post('/auth.selectWallet', params);
return res;
} catch (error: any) {
// Check for 404 error (wallet not found or invalid)
if (error.response?.status === 404) {
console.log('DEBUG: Wallet selection failed with 404, disconnecting');
await tonConnectUI.disconnect();
localStorage.removeItem(sessionStorageKey);
localStorage.removeItem(tonProofStorageKey);
}
throw error;
}
};
// Helper to prepare the connection parameters with proof requirements
const prepareConnectParams = async () => {
console.log('DEBUG: Preparing connect parameters');
// Set to loading state first
tonConnectUI.setConnectRequestParameters({ state: 'loading' });
try {
// Get the payload/token from backend
const value = await request.post<{ auth_v1_token: string }>('/auth.twa', {
twa_data: WebApp.initData,
});
if (value?.data?.auth_v1_token) {
console.log('DEBUG: Got token for connect params');
// Set the parameters to ready with tonProof requirement
tonConnectUI.setConnectRequestParameters({
state: 'ready',
value: { tonProof: value.data.auth_v1_token },
});
return true;
} else {
console.log('DEBUG: No token received for connect params');
tonConnectUI.setConnectRequestParameters(null);
return false;
}
} catch (error) {
console.error('DEBUG: Error preparing connect params:', error);
tonConnectUI.setConnectRequestParameters(null);
return false;
}
};
// Helper to check if connection is capable of transactions
const isConnectionValid = () => {
if (!tonConnectUI.connected || !tonConnectUI.wallet) {
return false;
}
return true;
}; };
return useMutation(['auth'], async () => { return useMutation(['auth'], async () => {
@ -64,68 +134,178 @@ export const useAuth = () => {
let authResult; let authResult;
console.log('DEBUG: Starting auth flow'); console.log('DEBUG: Starting auth flow');
// Case 1: Not connected - need to connect and get proof try {
if (!tonConnectUI.connected) { // Case 1: Not connected - need to connect and get proof
console.log('DEBUG: No wallet connection, starting flow'); if (!tonConnectUI.connected) {
localStorage.removeItem(sessionStorageKey); console.log('DEBUG: No wallet connection, starting flow');
localStorage.removeItem(sessionStorageKey);
const refreshPayload = async () => { // Prepare connection parameters (this sets up the proof requirement)
tonConnectUI.setConnectRequestParameters({ state: 'loading' }); const prepared = await prepareConnectParams();
const value = await request.post<{ auth_v1_token: string }>('/auth.twa', {
twa_data: WebApp.initData,
});
if (value?.data?.auth_v1_token) { if (!prepared) {
tonConnectUI.setConnectRequestParameters({ throw new Error('Failed to prepare connection parameters');
state: 'ready',
value: { tonProof: value.data.auth_v1_token },
});
} else {
tonConnectUI.setConnectRequestParameters(null);
} }
};
await refreshPayload(); // Start periodic refresh of the payload
interval.current = setInterval(refreshPayload, payloadTTLMS); interval.current = setInterval(prepareConnectParams, payloadTTLMS);
const tonProof = await waitForWalletProof(); // Open the modal - this will not resolve until connection or cancellation
console.log('DEBUG: Got initial proof', tonProof); await tonConnectUI.openModal();
authResult = await makeAuthRequest({ // Check if connection was successful
twa_data: WebApp.initData, if (!tonConnectUI.connected) {
ton_proof: { console.log('DEBUG: Connection cancelled or failed');
account: tonConnectUI.wallet!.account, throw new Error('Wallet connection cancelled or failed');
ton_proof: tonProof, }
},
}); // Check if we have a proof after connection
} else { if (
// Case 3: Connected without proof - already authenticated tonConnectUI.wallet?.connectItems?.tonProof &&
console.log('DEBUG: Connected without proof, proceeding without it'); !('error' in tonConnectUI.wallet.connectItems.tonProof)
authResult = await makeAuthRequest({ ) {
twa_data: WebApp.initData, console.log('DEBUG: Got proof after connection');
});
try {
// Try auth with the proof
authResult = await makeAuthRequest({
twa_data: WebApp.initData,
ton_proof: {
account: tonConnectUI.wallet.account,
ton_proof: tonConnectUI.wallet.connectItems.tonProof.proof,
},
});
} catch (error) {
// If auth with proof fails, throw the error
console.error('DEBUG: Auth with fresh proof failed:', error);
throw error;
}
} else {
console.log('DEBUG: No proof available after connection');
// If we can't get proof but we're connected, try auth without it
authResult = await makeAuthRequest({
twa_data: WebApp.initData,
});
}
} else {
// Case 2: Already connected - try to use stored proof first
console.log('DEBUG: Already connected');
// Check if we have a valid stored proof
const storedProofData = localStorage.getItem(tonProofStorageKey);
if (storedProofData) {
try {
const proofData = JSON.parse(storedProofData);
// Check if the proof matches current wallet and is not too old
if (tonConnectUI.wallet?.account?.address === proofData.account.address) {
console.log('DEBUG: Using stored proof');
// Try auth with stored proof but ignore errors
try {
authResult = await makeAuthRequest({
twa_data: WebApp.initData,
ton_proof: {
account: proofData.account,
ton_proof: proofData.proof,
},
});
// If successful, remove stored proof as it's been used
localStorage.removeItem(tonProofStorageKey);
} catch (error) {
console.log(
'DEBUG: Auth with stored proof failed, proceeding without it'
);
// Fall back to auth without proof
authResult = await makeAuthRequest({
twa_data: WebApp.initData,
});
}
} else {
console.log('DEBUG: Stored proof address mismatch');
localStorage.removeItem(tonProofStorageKey);
// Auth without proof
authResult = await makeAuthRequest({
twa_data: WebApp.initData,
});
}
} catch (error) {
console.error('DEBUG: Error parsing stored proof:', error);
localStorage.removeItem(tonProofStorageKey);
// Auth without proof
authResult = await makeAuthRequest({
twa_data: WebApp.initData,
});
}
} else {
// No stored proof, check if we have a live proof
if (
tonConnectUI.wallet?.connectItems?.tonProof &&
!('error' in tonConnectUI.wallet.connectItems.tonProof)
) {
console.log('DEBUG: Using live proof from wallet');
try {
// Try auth with the live proof
authResult = await makeAuthRequest({
twa_data: WebApp.initData,
ton_proof: {
account: tonConnectUI.wallet.account,
ton_proof: tonConnectUI.wallet.connectItems.tonProof.proof,
},
});
} catch (error) {
console.log(
'DEBUG: Auth with live proof failed, proceeding without it'
);
// Fall back to auth without proof
authResult = await makeAuthRequest({
twa_data: WebApp.initData,
});
}
} else {
// Connected without proof - already authenticated
console.log('DEBUG: Connected without proof, proceeding without it');
authResult = await makeAuthRequest({
twa_data: WebApp.initData,
});
}
}
}
// Always try to select wallet after auth (this validates the connection)
if (tonConnectUI.wallet?.account?.address) {
console.log('DEBUG: Selecting wallet', tonConnectUI.wallet.account.address);
try {
await makeSelectWalletRequest({
wallet_address: tonConnectUI.wallet.account.address,
});
// Additional validation check
if (!isConnectionValid()) {
console.log('DEBUG: Connection validation failed, disconnecting');
await tonConnectUI.disconnect();
localStorage.removeItem(sessionStorageKey);
localStorage.removeItem(tonProofStorageKey);
throw new Error('Connection validation failed');
}
} catch (error) {
// Errors from makeSelectWalletRequest are already handled
console.error('DEBUG: Wallet selection failed:', error);
throw error;
}
}
return authResult;
} catch (error) {
console.error('DEBUG: Auth flow failed:', error);
throw error;
} finally {
clearInterval(interval.current);
} }
if (tonConnectUI.wallet?.account?.address) {
console.log('DEBUG: Selecting wallet', tonConnectUI.wallet.account.address);
await makeSelectWalletRequest({ wallet_address: tonConnectUI.wallet.account.address });
}
return authResult;
// Commented this part for two reasons:
// 1) When we include ton_proof from the wallet it fails the call for a reason of bad ton_proof
// 2) This call could happen only if the first case happened and it means that the ton_proof is already have been stored once before
// Case 2: Connected with proof - use it
// if (wallet?.connectItems?.tonProof && !("error" in wallet.connectItems.tonProof)) {
// console.log("DEBUG: Using existing proof", wallet.connectItems.tonProof.proof);
// return makeAuthRequest({
// twa_data: WebApp.initData,
// ton_proof: {
// account: wallet.account,
// ton_proof: wallet.connectItems.tonProof.proof,
// },
// });
// }
}); });
}; };