Skip to content

Commit 9458628

Browse files
committed
refined sign arbitrary
1 parent 740bdbf commit 9458628

File tree

3 files changed

+174
-80
lines changed

3 files changed

+174
-80
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import type { NextApiRequest, NextApiResponse } from 'next';
2+
import { randomBytes } from 'crypto';
3+
4+
type ResponseData = {
5+
message: string;
6+
timestamp: string;
7+
nonce: string;
8+
}
9+
10+
export default function handler(
11+
req: NextApiRequest,
12+
res: NextApiResponse<ResponseData | { error: string }>
13+
) {
14+
if (req.method !== 'GET') {
15+
return res.status(405).json({ error: 'Method not allowed' });
16+
}
17+
18+
try {
19+
// Generate current timestamp in ISO format
20+
const timestamp = new Date().toISOString();
21+
22+
// Generate random nonce
23+
const nonce = randomBytes(8).toString('hex');
24+
25+
// Format the authentication message with explicit line breaks
26+
const message = `Please sign this message to complete login authentication.\nTimestamp: ${timestamp}\nNonce: ${nonce}`;
27+
28+
return res.status(200).json({
29+
message,
30+
timestamp,
31+
nonce
32+
});
33+
} catch (error) {
34+
console.error('Error generating auth message:', error);
35+
return res.status(500).json({ error: 'Failed to generate authentication message' });
36+
}
37+
}

templates/chain-template/pages/api/verify-signature.ts

+33-1
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,16 @@ type RequestBody = {
77
signature: string;
88
publicKey: string;
99
signer: string;
10-
prefix?: string;
1110
}
1211

1312
type ResponseData = {
1413
success: boolean;
1514
message: string;
1615
}
1716

17+
18+
const SIGNATURE_EXPIRY_MS = 5 * 60 * 1000;
19+
1820
export default function handler(
1921
req: NextApiRequest,
2022
res: NextApiResponse<ResponseData>
@@ -37,6 +39,29 @@ export default function handler(
3739
}
3840

3941
try {
42+
const messageTimestamp = extractTimestampFromMessage(message);
43+
44+
if (messageTimestamp) {
45+
const timestampDate = new Date(messageTimestamp);
46+
const currentTime = new Date();
47+
48+
if (isNaN(timestampDate.getTime())) {
49+
return res.status(400).json({
50+
success: false,
51+
message: 'Invalid timestamp format in message'
52+
});
53+
}
54+
55+
if (currentTime.getTime() - timestampDate.getTime() > SIGNATURE_EXPIRY_MS) {
56+
return res.status(401).json({
57+
success: false,
58+
message: 'Authentication expired. Please sign in again.'
59+
});
60+
}
61+
} else {
62+
console.warn('No timestamp found in message, skipping timestamp validation');
63+
}
64+
4065
// Convert base64 public key to Uint8Array
4166
const pubKeyBytes = new Uint8Array(Buffer.from(publicKey, 'base64'));
4267
// Convert base64 signature to Uint8Array
@@ -68,4 +93,11 @@ export default function handler(
6893
message: 'Internal server error: ' + error.message
6994
});
7095
}
96+
}
97+
98+
99+
function extractTimestampFromMessage(message: string): string | null {
100+
// "Please sign this message to complete login authentication.\nTimestamp: 2023-04-30T12:34:56.789Z\nNonce: abc123"
101+
const timestampMatch = message.match(/Timestamp:\s*([^\n]+)/);
102+
return timestampMatch ? timestampMatch[1].trim() : null;
71103
}

templates/chain-template/pages/sign-message.tsx

+104-79
Original file line numberDiff line numberDiff line change
@@ -1,85 +1,123 @@
1-
import { useState, ChangeEvent } from 'react';
2-
import { Container, Button, Stack, Text, TextField, useTheme } from '@interchain-ui/react';
1+
import { useState, useEffect } from 'react';
2+
import { Container, Button, Stack, Text, useTheme } from '@interchain-ui/react';
33
import { useChain } from '@interchain-kit/react';
44
import { useChainStore } from '@/contexts';
55
import { useToast } from '@/hooks';
66

77
export default function SignMessage() {
88
const [message, setMessage] = useState('');
9-
const [signature, setSignature] = useState('');
10-
const [isValid, setIsValid] = useState<boolean | null>(null);
11-
const [verifying, setVerifying] = useState(false);
9+
const [loading, setLoading] = useState(false);
10+
const [signingIn, setSigningIn] = useState(false);
1211
const { selectedChain } = useChainStore();
1312
const { address, wallet, chain } = useChain(selectedChain);
1413
const { toast } = useToast();
1514
const { theme } = useTheme();
1615

17-
const handleSign = async () => {
18-
if (!wallet || !address || !chain.chainId) {
16+
useEffect(() => {
17+
// Load the authentication message when component mounts
18+
fetchAuthMessage();
19+
}, []);
20+
21+
const fetchAuthMessage = async () => {
22+
try {
23+
setLoading(true);
24+
const response = await fetch('/api/generate-auth-message');
25+
const data = await response.json();
26+
27+
if (!response.ok) {
28+
throw new Error(data.error || 'Failed to fetch authentication message');
29+
}
30+
31+
setMessage(data.message);
32+
} catch (error) {
33+
console.error('Error fetching auth message:', error);
1934
toast({
2035
title: 'Error',
21-
description: 'Please connect your wallet first',
36+
description: 'Failed to fetch authentication message',
2237
type: 'error'
2338
});
24-
return;
39+
} finally {
40+
setLoading(false);
2541
}
42+
};
2643

27-
try {
28-
setSignature('');
29-
setIsValid(null);
30-
const result = await wallet.signArbitrary(chain.chainId, address, message);
31-
setSignature(result.signature);
32-
} catch (error) {
33-
console.error('Error signing message:', error);
44+
const handleSignAndLogin = async () => {
45+
if (!wallet || !address || !chain.chainId) {
3446
toast({
3547
title: 'Error',
36-
description: 'Failed to sign message: ' + (error instanceof Error ? error.message : String(error)),
48+
description: 'Please connect your wallet first',
3749
type: 'error'
3850
});
51+
return;
3952
}
40-
};
41-
42-
const handleVerify = async () => {
43-
if (!signature || !address || !chain.chainId) return;
4453

4554
try {
46-
setVerifying(true);
55+
setSigningIn(true);
56+
57+
// Sign the message
58+
const result = await wallet.signArbitrary(chain.chainId, address, message);
59+
60+
// Get the public key
4761
const account = await wallet?.getAccount(chain.chainId);
4862
if (!account?.pubkey) {
4963
throw new Error('Failed to get public key');
5064
}
5165

66+
// Submit to API directly
5267
const response = await fetch('/api/verify-signature', {
5368
method: 'POST',
5469
headers: {
5570
'Content-Type': 'application/json',
5671
},
5772
body: JSON.stringify({
5873
message,
59-
signature,
74+
signature: result.signature,
6075
publicKey: Buffer.from(account.pubkey).toString('base64'),
6176
signer: address
6277
}),
6378
});
6479

6580
const data = await response.json();
66-
setIsValid(data.success);
81+
82+
if (!response.ok) {
83+
throw new Error(data.error || 'Login failed');
84+
}
85+
86+
if (!data.success && data.message?.includes('expired')) {
87+
toast({
88+
title: 'Authentication expired',
89+
description: 'Authentication expired, please try again',
90+
type: 'error'
91+
});
92+
handleRefreshMessage();
93+
return;
94+
}
95+
96+
if (data.success) {
97+
toast({
98+
title: 'Success',
99+
description: 'Authentication successful',
100+
type: 'success'
101+
});
102+
// Handle successful login - redirect or update UI state
103+
// You can add navigation or state management here
104+
} else {
105+
throw new Error(data.message || 'Authentication failed');
106+
}
67107
} catch (error) {
68-
console.error('Error verifying signature:', error);
108+
console.error('Error signing in:', error);
69109
toast({
70110
title: 'Error',
71-
description: 'Failed to verify signature',
111+
description: 'Failed to sign in: ' + (error instanceof Error ? error.message : String(error)),
72112
type: 'error'
73113
});
74114
} finally {
75-
setVerifying(false);
115+
setSigningIn(false);
76116
}
77117
};
78118

79-
const handleMessageChange = (e: ChangeEvent<HTMLInputElement>) => {
80-
setMessage(e.target.value);
81-
setSignature('');
82-
setIsValid(null);
119+
const handleRefreshMessage = () => {
120+
fetchAuthMessage();
83121
};
84122

85123
return (
@@ -93,64 +131,51 @@ export default function SignMessage() {
93131
>
94132
<Stack direction="vertical" space="$12">
95133
<Text as="h1" fontSize="$xl" fontWeight="$semibold">
96-
Sign Arbitrary Message
134+
Sign Authentication Message
97135
</Text>
98136

99137
<Stack direction="vertical" space="$8">
100138
<Text as="label" fontSize="$md">
101-
Message to Sign
139+
Authentication Message
102140
</Text>
103-
<TextField
104-
id="message"
105-
placeholder="Enter your message here"
106-
value={message}
107-
onChange={handleMessageChange}
108-
/>
141+
<Container
142+
attributes={{
143+
p: '$16',
144+
backgroundColor: theme === 'light' ? '$gray100' : '$gray900',
145+
borderRadius: '$md'
146+
}}
147+
>
148+
{loading ? (
149+
<Text>Loading authentication message...</Text>
150+
) : (
151+
<Text
152+
fontSize="$sm"
153+
fontFamily="$mono"
154+
whiteSpace="pre-wrap"
155+
attributes={{ whiteSpace: 'pre-line' }} // This ensures line breaks are preserved
156+
>
157+
{message}
158+
</Text>
159+
)}
160+
</Container>
161+
<Button
162+
intent="secondary"
163+
onClick={handleRefreshMessage}
164+
size="sm"
165+
disabled={loading || signingIn}
166+
>
167+
Refresh Message
168+
</Button>
109169
</Stack>
110170

111171
<Button
112-
intent="tertiary"
113-
onClick={handleSign}
114-
disabled={!message || !wallet}
172+
intent="primary"
173+
onClick={handleSignAndLogin}
174+
disabled={!message || !wallet || loading}
175+
isLoading={signingIn}
115176
>
116-
Sign Message
177+
Sign and Login
117178
</Button>
118-
119-
{signature && (
120-
<Stack direction="vertical" space="$8">
121-
<Text fontWeight="$semibold">Signature:</Text>
122-
<Container
123-
attributes={{
124-
p: '$16',
125-
backgroundColor: theme === 'light' ? '$gray100' : '$gray900',
126-
borderRadius: '$md'
127-
}}
128-
>
129-
<Text fontSize="$sm" fontFamily="$mono">
130-
{signature}
131-
</Text>
132-
</Container>
133-
134-
<Button
135-
intent="primary"
136-
onClick={handleVerify}
137-
disabled={verifying}
138-
isLoading={verifying}
139-
>
140-
Verify Signature
141-
</Button>
142-
143-
{isValid !== null && (
144-
<Text
145-
fontSize="$md"
146-
color={isValid ? '$green500' : '$red500'}
147-
fontWeight="$medium"
148-
>
149-
Signature is {isValid ? 'valid' : 'invalid'}
150-
</Text>
151-
)}
152-
</Stack>
153-
)}
154179
</Stack>
155180
</Container>
156181
</>

0 commit comments

Comments
 (0)