Skip to content

Commit

Permalink
maybe progress?
Browse files Browse the repository at this point in the history
  • Loading branch information
0age committed Dec 5, 2024
1 parent 27cd7e3 commit b090b29
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 62 deletions.
1 change: 1 addition & 0 deletions frontend/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
BASE_URL=http://localhost:3000
4 changes: 4 additions & 0 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { getDefaultConfig, RainbowKitProvider } from '@rainbow-me/rainbowkit';
import { mainnet } from 'viem/chains';
import { WagmiProvider } from 'wagmi';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { http } from 'wagmi';
import '@rainbow-me/rainbowkit/styles.css';
import { useState } from 'react';

Expand All @@ -13,6 +14,9 @@ const config = getDefaultConfig({
appName: 'Smallocator',
projectId: 'YOUR_PROJECT_ID', // Get from WalletConnect Cloud
chains: [mainnet],
transports: {
[mainnet.id]: http(),
},
});

const queryClient = new QueryClient();
Expand Down
112 changes: 66 additions & 46 deletions frontend/src/components/SessionManager.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useAccount, useSignMessage } from 'wagmi';
import { useAccount, useSignMessage, useChainId } from 'wagmi';
import { useState, useEffect } from 'react';

interface SessionManagerProps {
Expand All @@ -7,60 +7,80 @@ interface SessionManagerProps {

export function SessionManager({ onSessionCreated }: SessionManagerProps) {
const { address, isConnected } = useAccount();
const { signMessageAsync } = useSignMessage();
const chainId = useChainId();
const [sessionToken, setSessionToken] = useState<string | null>(null);
const { signMessage } = useSignMessage({
mutation: {
onSuccess: async (signature: string) => {
try {
const response = await fetch('/api/auth/session', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
address,
signature,
}),
});

if (response.ok) {
const data = await response.json();
setSessionToken(data.token);
onSessionCreated();
}
} catch (error) {
console.error('Failed to create session:', error);
}
},
},
});

const createSession = async () => {
if (!address || !chainId) return;

const nonce = crypto.randomUUID();
const baseUrl = process.env.BASE_URL || 'http://localhost:3000';

try {
const signature = await signMessageAsync({
message: [
'Smallocator wants you to sign in with your Ethereum account:',
address,
'',
'Sign in to Smallocator',
'',
`URI: ${baseUrl}`,
`Version: 1`,
`Chain ID: ${chainId}`,
`Nonce: ${nonce}`,
`Issued At: ${new Date().toISOString()}`,
`Expiration Time: ${new Date(Date.now() + 30 * 60 * 1000).toISOString()}`,
].join('\n'),
});

const response = await fetch('/session', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
signature,
payload: {
domain: new URL(baseUrl).host,
address,
uri: baseUrl,
statement: 'Sign in to Smallocator',
version: '1',
chainId,
nonce,
issuedAt: new Date().toISOString(),
expirationTime: new Date(Date.now() + 30 * 60 * 1000).toISOString(),
resources: [],
},
}),
});

if (response.ok) {
const data = await response.json();
setSessionToken(data.token);
onSessionCreated();
} else {
console.error('Failed to create session:', await response.text());
}
} catch (error) {
console.error('Failed to create session:', error);
}
};

useEffect(() => {
if (isConnected && address && !sessionToken) {
const message = `Sign this message to create a session for Smallocator\n\nAddress: ${address}\nNonce: ${Date.now()}`;
signMessage({ message });
if (isConnected && address && !sessionToken && chainId) {
createSession();
}
}, [isConnected, address, sessionToken, signMessage]);
}, [isConnected, address, sessionToken, chainId]);

if (!isConnected) {
return (
<div className="text-center py-4">
<p className="text-gray-600">Connect your wallet to continue</p>
</div>
);
}

if (sessionToken) {
return (
<div className="text-center py-4">
<p className="text-green-600">✓ Session active</p>
<div>
<p>Please connect your wallet to continue.</p>
</div>
);
}

return (
<div className="text-center py-4">
<p className="text-gray-600">Creating session...</p>
</div>
);
return null;
}
32 changes: 21 additions & 11 deletions frontend/vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,26 @@
import { defineConfig } from 'vite';
import { defineConfig, loadEnv } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'path';

// https://vite.dev/config/
export default defineConfig({
plugins: [react()],
server: {
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ''),
export default defineConfig(({ mode }) => {
// Load env from parent directory
const env = loadEnv(mode, path.resolve(__dirname, '..'), '');
const baseUrl = env.BASE_URL || 'http://localhost:3000';

return {
plugins: [react()],
server: {
proxy: {
'/api': {
target: baseUrl,
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ''),
},
},
},
},
})
define: {
'process.env.BASE_URL': JSON.stringify(baseUrl),
},
};
});
15 changes: 10 additions & 5 deletions src/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import { FastifyInstance } from 'fastify';
import { getAddress, verifyMessage } from 'viem/utils';
import { randomUUID } from 'crypto';

// Import the FastifyInstance augmentation
import './database';

export interface SessionPayload {
domain: string;
address: string;
Expand Down Expand Up @@ -38,7 +41,8 @@ export async function validateAndCreateSession(
const nonceIsValid = await verifyNonce(
server,
payload.domain,
payload.nonce
payload.nonce,
payload.chainId
);
if (!nonceIsValid) {
throw new Error('Invalid or expired nonce');
Expand Down Expand Up @@ -85,7 +89,7 @@ export async function validateAndCreateSession(
// Mark nonce as used
await server.db.query(
'INSERT INTO nonces (id, chain_id, nonce) VALUES ($1, $2, $3)',
[randomUUID(), '1', payload.nonce] // Using chain_id 1 for session nonces
[randomUUID(), payload.chainId.toString(), payload.nonce]
);

return session;
Expand Down Expand Up @@ -195,7 +199,7 @@ function isValidPayload(payload: SessionPayload): boolean {
}

// Validate chain ID
if (payload.chainId <= 0) {
if (payload.chainId < 1) {
throw new Error('Invalid chain ID');
}

Expand Down Expand Up @@ -230,12 +234,13 @@ function isValidPayload(payload: SessionPayload): boolean {
export async function verifyNonce(
server: FastifyInstance,
domain: string,
nonce: string
nonce: string,
chainId: number
): Promise<boolean> {
// Check if nonce has been used
const result = await server.db.query(
'SELECT id FROM nonces WHERE chain_id = $1 AND nonce = $2',
['1', nonce] // Using chain_id 1 for session nonces
[chainId.toString(), nonce]
);

return result.rows.length === 0;
Expand Down

0 comments on commit b090b29

Please sign in to comment.