Skip to content

Commit

Permalink
feat: viem/siwe partial migration
Browse files Browse the repository at this point in the history
  • Loading branch information
DanielSinclair committed Oct 3, 2024
1 parent b2b8217 commit 564e9ad
Show file tree
Hide file tree
Showing 13 changed files with 116 additions and 44 deletions.
8 changes: 2 additions & 6 deletions examples/with-next-siwe-iron-session/src/pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
RainbowKitAuthenticationProvider,
AuthenticationStatus,
} from '@rainbow-me/rainbowkit';
import { SiweMessage } from 'siwe';
import { createSiweMessage } from 'viem/siwe';

import { config } from '../wagmi';

Expand Down Expand Up @@ -60,7 +60,7 @@ export default function App({ Component, pageProps }: AppProps) {
},

createMessage: ({ nonce, address, chainId }) => {
return new SiweMessage({
return createSiweMessage({
domain: window.location.host,
address,
statement: 'Sign in with Ethereum to the app.',
Expand All @@ -71,10 +71,6 @@ export default function App({ Component, pageProps }: AppProps) {
});
},

getMessageBody: ({ message }) => {
return message.prepareMessage();
},

verify: async ({ message, signature }) => {
verifyingRef.current = true;

Expand Down
5 changes: 3 additions & 2 deletions examples/with-next-siwe-iron-session/src/pages/api/nonce.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { withIronSessionApiRoute } from 'iron-session/next';
import { NextApiRequest, NextApiResponse } from 'next';
import { generateNonce } from 'siwe';
import { generateSiweNonce } from 'viem/siwe';

import { ironOptions } from '../../../lib/iron';

const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const { method } = req;
switch (method) {
case 'GET':
req.session.nonce = generateNonce();
req.session.nonce = generateSiweNonce();
await req.session.save();
res.setHeader('Content-Type', 'text/plain');
res.send(req.session.nonce);
Expand Down
17 changes: 11 additions & 6 deletions examples/with-next-siwe-iron-session/src/pages/api/verify.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,30 @@
import { withIronSessionApiRoute } from 'iron-session/next';
import { NextApiRequest, NextApiResponse } from 'next';
import { SiweMessage } from 'siwe';
import { parseSiweMessage, type SiweMessage } from 'viem/siwe';

import { ironOptions } from '../../../lib/iron';
import { publicClient } from '../../wagmi';

const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const { method } = req;
switch (method) {
case 'POST':
try {
const { message, signature } = req.body;
const siweMessage = new SiweMessage(message);
const { success, error, data } = await siweMessage.verify({
const siweMessage = parseSiweMessage(message) as SiweMessage;

const success = await publicClient.verifyMessage({
address: siweMessage.address,
message,
signature,
});

if (!success) throw error;
if (!success) throw new Error('Invalid signature.');

if (data.nonce !== req.session.nonce)
if (siweMessage.nonce !== req.session.nonce)
return res.status(422).json({ message: 'Invalid nonce.' });

req.session.siwe = data;
req.session.siwe = siweMessage;
await req.session.save();
res.json({ ok: true });
} catch (_error) {
Expand Down
3 changes: 3 additions & 0 deletions examples/with-next-siwe-iron-session/src/wagmi.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { getDefaultConfig } from '@rainbow-me/rainbowkit';
import { publicActions } from 'viem';
import {
arbitrum,
base,
Expand All @@ -21,3 +22,5 @@ export const config = getDefaultConfig({
],
ssr: true,
});

export const publicClient = config.getClient().extend(publicActions);
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import 'iron-session';
import { SiweMessage } from 'siwe';
import type { SiweMessage } from 'viem/siwe';

declare module 'iron-session' {
interface IronSessionData {
Expand Down
1 change: 0 additions & 1 deletion examples/with-next-siwe-next-auth/src/pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import {
GetSiweMessageOptions,
} from '@rainbow-me/rainbowkit-siwe-next-auth';


import { config } from '../wagmi';

const getSiweMessageOptions: GetSiweMessageOptions = () => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,31 @@ import { NextApiRequest, NextApiResponse } from 'next';
import NextAuth, { NextAuthOptions } from 'next-auth';
import CredentialsProvider from 'next-auth/providers/credentials';
import { getCsrfToken } from 'next-auth/react';
import { SiweMessage } from 'siwe';
import {
type SiweMessage,
parseSiweMessage,
validateSiweMessage,
} from 'viem/siwe';

import { publicClient } from '../../../wagmi';

export function getAuthOptions(req: IncomingMessage): NextAuthOptions {
const providers = [
CredentialsProvider({
async authorize(credentials) {
async authorize(credentials: any) {
try {
const siwe = new SiweMessage(
JSON.parse(credentials?.message || '{}')
);
const siweMessage = parseSiweMessage(
credentials?.message!,
) as SiweMessage;

if (
!validateSiweMessage({
address: credentials?.address,
message: siweMessage,
})
) {
return null;
}

const nextAuthUrl =
process.env.NEXTAUTH_URL ||
Expand All @@ -28,20 +43,29 @@ export function getAuthOptions(req: IncomingMessage): NextAuthOptions {
}

const nextAuthHost = new URL(nextAuthUrl).host;
if (siwe.domain !== nextAuthHost) {
if (siweMessage.domain !== nextAuthHost) {
return null;
}

if (
siwe.nonce !==
siweMessage.nonce !==
(await getCsrfToken({ req: { headers: req.headers } }))
) {
return null;
}

await siwe.verify({ signature: credentials?.signature || '' });
const valid = await publicClient.verifyMessage({
address: credentials?.address,
message: credentials?.message,
signature: credentials?.signature,
});

if (!valid) {
return null;
}

return {
id: siwe.address,
id: siweMessage.address,
};
} catch (e) {
return null;
Expand All @@ -58,6 +82,11 @@ export function getAuthOptions(req: IncomingMessage): NextAuthOptions {
placeholder: '0x0',
type: 'text',
},
address: {
label: 'Address',
placeholder: '0x0',
type: 'text',
},
},
name: 'Ethereum',
}),
Expand Down
5 changes: 4 additions & 1 deletion examples/with-next-siwe-next-auth/src/wagmi.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { getDefaultConfig } from '@rainbow-me/rainbowkit';
import { publicActions } from 'viem';
import {
arbitrum,
base,
Expand All @@ -20,4 +21,6 @@ export const config = getDefaultConfig({
...(process.env.NEXT_PUBLIC_ENABLE_TESTNETS === 'true' ? [sepolia] : []),
],
ssr: true,
});
});

export const publicClient = config.getClient().extend(publicActions);
47 changes: 38 additions & 9 deletions packages/example/src/pages/api/auth/[...nextauth].ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,31 @@ import type { NextApiRequest, NextApiResponse } from 'next';
import NextAuth, { type NextAuthOptions } from 'next-auth';
import CredentialsProvider from 'next-auth/providers/credentials';
import { getCsrfToken } from 'next-auth/react';
import { SiweMessage } from 'siwe';
import {
type SiweMessage,
parseSiweMessage,
validateSiweMessage,
} from 'viem/siwe';

import { publicClient } from '../../../wagmi';

export function getAuthOptions(req: IncomingMessage): NextAuthOptions {
const providers = [
CredentialsProvider({
async authorize(credentials) {
async authorize(credentials: any) {
try {
const siwe = new SiweMessage(
JSON.parse(credentials?.message || '{}'),
);
const siweMessage = parseSiweMessage(
credentials?.message!,
) as SiweMessage;

if (
!validateSiweMessage({
address: credentials?.address,
message: siweMessage,
})
) {
return null;
}

const nextAuthUrl =
process.env.NEXTAUTH_URL ||
Expand All @@ -28,20 +43,29 @@ export function getAuthOptions(req: IncomingMessage): NextAuthOptions {
}

const nextAuthHost = new URL(nextAuthUrl).host;
if (siwe.domain !== nextAuthHost) {
if (siweMessage.domain !== nextAuthHost) {
return null;
}

if (
siwe.nonce !==
siweMessage.nonce !==
(await getCsrfToken({ req: { headers: req.headers } }))
) {
return null;
}

await siwe.verify({ signature: credentials?.signature || '' });
const valid = await publicClient.verifyMessage({
address: credentials?.address,
message: credentials?.message,
signature: credentials?.signature,
});

if (!valid) {
return null;
}

return {
id: siwe.address,
id: siweMessage.address,
};
} catch (e) {
console.error('siwe authorization failed', e);
Expand All @@ -59,6 +83,11 @@ export function getAuthOptions(req: IncomingMessage): NextAuthOptions {
placeholder: '0x0',
type: 'text',
},
address: {
label: 'Address',
placeholder: '0x0',
type: 'text',
},
},
name: 'Ethereum',
}),
Expand Down
3 changes: 3 additions & 0 deletions packages/example/src/wagmi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ import {
zealWallet,
zerionWallet,
} from '@rainbow-me/rainbowkit/wallets';
import { publicActions } from 'viem';
import {
arbitrum,
arbitrumSepolia,
Expand Down Expand Up @@ -242,3 +243,5 @@ export const config = getDefaultConfig({
],
ssr: true,
});

export const publicClient = config.getClient().extend(publicActions);
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ import {
} from '@rainbow-me/rainbowkit';
import { getCsrfToken, signIn, signOut, useSession } from 'next-auth/react';
import React, { type ReactNode, useMemo } from 'react';
import { SiweMessage } from 'siwe';
import type { Address } from 'viem';
import { type SiweMessage, createSiweMessage } from 'viem/siwe';

type UnconfigurableMessageOptions = {
address: string;
address: Address;
chainId: number;
nonce: string;
};
Expand Down Expand Up @@ -36,7 +37,12 @@ export function RainbowKitSiweNextAuthProvider({
() =>
createAuthenticationAdapter({
createMessage: ({ address, chainId, nonce }) => {
const defaultConfigurableOptions: ConfigurableMessageOptions = {
const defaultConfigurableOptions: Required<
Pick<
ConfigurableMessageOptions,
'domain' | 'uri' | 'version' | 'statement'
>
> = {
domain: window.location.host,
statement: 'Sign in with Ethereum to the app.',
uri: window.location.origin,
Expand All @@ -49,7 +55,7 @@ export function RainbowKitSiweNextAuthProvider({
nonce,
};

return new SiweMessage({
return createSiweMessage({
...defaultConfigurableOptions,

// Spread custom SIWE message options provided by the consumer
Expand All @@ -60,8 +66,6 @@ export function RainbowKitSiweNextAuthProvider({
});
},

getMessageBody: ({ message }) => message.prepareMessage(),

getNonce: async () => {
const nonce = await getCsrfToken();
if (!nonce) throw new Error();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import React, {
useMemo,
useState,
} from 'react';
import type { Address } from 'viem';
import { type Config, useAccount, useAccountEffect } from 'wagmi';

export type AuthenticationStatus =
Expand All @@ -17,10 +18,9 @@ export interface AuthenticationAdapter<Message> {
getNonce: () => Promise<string>;
createMessage: (args: {
nonce: string;
address: string;
address: Address;
chainId: number;
}) => Message;
getMessageBody: (args: { message: Message }) => string;
verify: (args: { message: Message; signature: string }) => Promise<boolean>;
signOut: () => Promise<void>;
}
Expand Down
2 changes: 1 addition & 1 deletion packages/rainbowkit/src/components/SignIn/SignIn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export function SignIn({

try {
signature = await signMessageAsync({
message: authAdapter.getMessageBody({ message }),
message,
});
} catch (error) {
if (error instanceof UserRejectedRequestError) {
Expand Down

0 comments on commit 564e9ad

Please sign in to comment.