Skip to content

Commit 3ad2e55

Browse files
authored
Merge pull request #1 from pattern-tech/feat/wallet-connect
Feat: wallet connect
2 parents 334e0b2 + 0a724c1 commit 3ad2e55

23 files changed

+3817
-630
lines changed

.devcontainer/Dockerfile

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
FROM mcr.microsoft.com/devcontainers/javascript-node:1-22-bookworm
2+
3+
# [Optional] Uncomment this section to install additional OS packages.
4+
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
5+
# && apt-get -y install --no-install-recommends <your-package-list-here>
6+
7+
# [Optional] Uncomment if you want to install an additional version of node using nvm
8+
# ARG EXTRA_NODE_VERSION=10
9+
# RUN su node -c "source /usr/local/share/nvm/nvm.sh && nvm install ${EXTRA_NODE_VERSION}"
10+
11+
# [Optional] Uncomment if you want to install more global node modules
12+
# RUN su node -c "npm install -g <your-package-list-here>"

.devcontainer/devcontainer.json

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
2+
// README at: https://github.com/devcontainers/templates/tree/main/src/javascript-node-postgres
3+
{
4+
"name": "Node.js & PostgreSQL",
5+
"dockerComposeFile": "docker-compose.yml",
6+
"service": "app",
7+
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}"
8+
9+
// Features to add to the dev container. More info: https://containers.dev/features.
10+
// "features": {},
11+
12+
// Use 'forwardPorts' to make a list of ports inside the container available locally.
13+
// This can be used to network with other containers or with the host.
14+
// "forwardPorts": [3000, 5432],
15+
16+
// Use 'postCreateCommand' to run commands after the container is created.
17+
// "postCreateCommand": "yarn install",
18+
19+
// Configure tool-specific properties.
20+
// "customizations": {},
21+
22+
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
23+
// "remoteUser": "root"
24+
}

.devcontainer/docker-compose.yml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
version: '3.8'
2+
3+
services:
4+
app:
5+
build:
6+
context: .
7+
dockerfile: Dockerfile
8+
9+
volumes:
10+
- ../..:/workspaces:cached
11+
12+
# Overrides default command so things don't shut down after the process ends.
13+
command: sleep infinity
14+
15+
# Runs app on the same network as the database container, allows "forwardPorts" in devcontainer.json function.
16+
network_mode: service:db
17+
18+
# Use "forwardPorts" in **devcontainer.json** to forward an app port locally.
19+
# (Adding the "ports" property to this file will not forward from a Codespace.)
20+
21+
db:
22+
image: postgres:latest
23+
restart: unless-stopped
24+
volumes:
25+
- postgres-data:/var/lib/postgresql/data
26+
environment:
27+
POSTGRES_PASSWORD: postgres
28+
POSTGRES_USER: postgres
29+
POSTGRES_DB: postgres
30+
31+
# Add "forwardPorts": ["5432"] to **devcontainer.json** to forward PostgreSQL locally.
32+
# (Adding the "ports" property to this file will not forward from a Codespace.)
33+
34+
volumes:
35+
postgres-data:

.env.example

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,6 @@ BLOB_READ_WRITE_TOKEN=****
1515

1616
# Instructions to create a database here: https://vercel.com/docs/storage/vercel-postgres/quickstart
1717
POSTGRES_URL=****
18+
19+
NEXT_PUBLIC_PROJECT_ID=
20+
NEXTAUTH_SECRET=

app/(auth)/actions.ts

Lines changed: 0 additions & 84 deletions
This file was deleted.

app/(auth)/auth.config.ts

Lines changed: 18 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,29 @@
1-
import type { NextAuthConfig } from 'next-auth';
1+
import { NextAuthConfig } from "next-auth";
2+
3+
export const nextAuthSecret = process.env.NEXTAUTH_SECRET;
4+
if (!nextAuthSecret) {
5+
throw new Error("NEXTAUTH_SECRET is not set");
6+
}
27

38
export const authConfig = {
4-
pages: {
5-
signIn: '/login',
6-
newUser: '/',
9+
secret: nextAuthSecret,
10+
providers: [],
11+
session: {
12+
strategy: "jwt",
713
},
8-
providers: [
9-
// added later in auth.ts since it requires bcrypt which is only compatible with Node.js
10-
// while this file is also used in non-Node.js environments
11-
],
1214
callbacks: {
13-
authorized({ auth, request: { nextUrl } }) {
14-
const isLoggedIn = !!auth?.user;
15-
const isOnChat = nextUrl.pathname.startsWith('/');
16-
const isOnRegister = nextUrl.pathname.startsWith('/register');
17-
const isOnLogin = nextUrl.pathname.startsWith('/login');
18-
19-
if (isLoggedIn && (isOnLogin || isOnRegister)) {
20-
return Response.redirect(new URL('/', nextUrl as unknown as URL));
21-
}
22-
23-
if (isOnRegister || isOnLogin) {
24-
return true; // Always allow access to register and login pages
25-
}
26-
27-
if (isOnChat) {
28-
if (isLoggedIn) return true;
29-
return false; // Redirect unauthenticated users to login page
15+
session({ session, token }) {
16+
if (!token.sub) {
17+
return session;
3018
}
3119

32-
if (isLoggedIn) {
33-
return Response.redirect(new URL('/', nextUrl as unknown as URL));
20+
const [, chainId, address] = token.sub.split(":");
21+
if (chainId && address) {
22+
session.address = address;
23+
session.chainId = parseInt(chainId, 10);
3424
}
3525

36-
return true;
26+
return session;
3727
},
3828
},
3929
} satisfies NextAuthConfig;

app/(auth)/auth.ts

Lines changed: 70 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,83 @@
1-
import { compare } from 'bcrypt-ts';
2-
import NextAuth, { type User, type Session } from 'next-auth';
3-
import Credentials from 'next-auth/providers/credentials';
1+
import NextAuth from "next-auth";
2+
import credentialsProvider from "next-auth/providers/credentials";
3+
import { getAddressFromMessage, getChainIdFromMessage, type SIWESession } from "@reown/appkit-siwe";
4+
import { createPublicClient, http } from "viem";
45

5-
import { getUser } from '@/lib/db/queries';
6+
import { authConfig } from "./auth.config";
67

7-
import { authConfig } from './auth.config';
8+
declare module "next-auth" {
9+
interface Session extends SIWESession {
10+
address: string;
11+
chainId: number;
12+
}
13+
}
14+
15+
export const nextAuthSecret = process.env.NEXTAUTH_SECRET;
16+
if (!nextAuthSecret) {
17+
throw new Error("NEXTAUTH_SECRET is not set");
18+
}
819

9-
interface ExtendedSession extends Session {
10-
user: User;
20+
export const projectId = process.env.NEXT_PUBLIC_PROJECT_ID;
21+
if (!projectId) {
22+
throw new Error("NEXT_PUBLIC_PROJECT_ID is not set");
1123
}
1224

25+
const providers = [
26+
credentialsProvider({
27+
name: "Ethereum",
28+
credentials: {
29+
message: {
30+
label: "Message",
31+
type: "text",
32+
placeholder: "0x0",
33+
},
34+
signature: {
35+
label: "Signature",
36+
type: "text",
37+
placeholder: "0x0",
38+
},
39+
},
40+
async authorize(credentials) {
41+
try {
42+
if (!credentials?.message) {
43+
throw new Error("SiweMessage is undefined");
44+
}
45+
const { message, signature } = credentials as Record<string, string>;
46+
const address = getAddressFromMessage(message);
47+
const chainId = getChainIdFromMessage(message);
48+
49+
const publicClient = createPublicClient({
50+
transport: http(
51+
`https://rpc.walletconnect.org/v1/?chainId=${chainId}&projectId=${projectId}`
52+
),
53+
});
54+
const isValid = await publicClient.verifyMessage({
55+
message,
56+
address: address as `0x${string}`,
57+
signature: signature as `0x${string}`,
58+
});
59+
// end o view verifyMessage
60+
61+
if (isValid) {
62+
return {
63+
id: `${chainId}:${address}`,
64+
};
65+
}
66+
67+
return null;
68+
} catch (e) {
69+
return null;
70+
}
71+
},
72+
}),
73+
];
74+
1375
export const {
1476
handlers: { GET, POST },
1577
auth,
1678
signIn,
1779
signOut,
1880
} = NextAuth({
1981
...authConfig,
20-
providers: [
21-
Credentials({
22-
credentials: {},
23-
async authorize({ email, password }: any) {
24-
const users = await getUser(email);
25-
if (users.length === 0) return null;
26-
// biome-ignore lint: Forbidden non-null assertion.
27-
const passwordsMatch = await compare(password, users[0].password!);
28-
if (!passwordsMatch) return null;
29-
return users[0] as any;
30-
},
31-
}),
32-
],
33-
callbacks: {
34-
async jwt({ token, user }) {
35-
if (user) {
36-
token.id = user.id;
37-
}
38-
39-
return token;
40-
},
41-
async session({
42-
session,
43-
token,
44-
}: {
45-
session: ExtendedSession;
46-
token: any;
47-
}) {
48-
if (session.user) {
49-
session.user.id = token.id as string;
50-
}
51-
52-
return session;
53-
},
54-
},
82+
providers,
5583
});

0 commit comments

Comments
 (0)