Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
FROM mcr.microsoft.com/devcontainers/javascript-node:1-22-bookworm

# [Optional] Uncomment this section to install additional OS packages.
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
# && apt-get -y install --no-install-recommends <your-package-list-here>

# [Optional] Uncomment if you want to install an additional version of node using nvm
# ARG EXTRA_NODE_VERSION=10
# RUN su node -c "source /usr/local/share/nvm/nvm.sh && nvm install ${EXTRA_NODE_VERSION}"

# [Optional] Uncomment if you want to install more global node modules
# RUN su node -c "npm install -g <your-package-list-here>"
24 changes: 24 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/javascript-node-postgres
{
"name": "Node.js & PostgreSQL",
"dockerComposeFile": "docker-compose.yml",
"service": "app",
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}"

// Features to add to the dev container. More info: https://containers.dev/features.
// "features": {},

// Use 'forwardPorts' to make a list of ports inside the container available locally.
// This can be used to network with other containers or with the host.
// "forwardPorts": [3000, 5432],

// Use 'postCreateCommand' to run commands after the container is created.
// "postCreateCommand": "yarn install",

// Configure tool-specific properties.
// "customizations": {},

// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
}
35 changes: 35 additions & 0 deletions .devcontainer/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
version: '3.8'

services:
app:
build:
context: .
dockerfile: Dockerfile

volumes:
- ../..:/workspaces:cached

# Overrides default command so things don't shut down after the process ends.
command: sleep infinity

# Runs app on the same network as the database container, allows "forwardPorts" in devcontainer.json function.
network_mode: service:db

# Use "forwardPorts" in **devcontainer.json** to forward an app port locally.
# (Adding the "ports" property to this file will not forward from a Codespace.)

db:
image: postgres:latest
restart: unless-stopped
volumes:
- postgres-data:/var/lib/postgresql/data
environment:
POSTGRES_PASSWORD: postgres
POSTGRES_USER: postgres
POSTGRES_DB: postgres

# Add "forwardPorts": ["5432"] to **devcontainer.json** to forward PostgreSQL locally.
# (Adding the "ports" property to this file will not forward from a Codespace.)

volumes:
postgres-data:
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,6 @@ BLOB_READ_WRITE_TOKEN=****

# Instructions to create a database here: https://vercel.com/docs/storage/vercel-postgres/quickstart
POSTGRES_URL=****

NEXT_PUBLIC_PROJECT_ID=
NEXTAUTH_SECRET=
84 changes: 0 additions & 84 deletions app/(auth)/actions.ts

This file was deleted.

46 changes: 18 additions & 28 deletions app/(auth)/auth.config.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,29 @@
import type { NextAuthConfig } from 'next-auth';
import { NextAuthConfig } from "next-auth";

export const nextAuthSecret = process.env.NEXTAUTH_SECRET;
if (!nextAuthSecret) {
throw new Error("NEXTAUTH_SECRET is not set");
}

export const authConfig = {
pages: {
signIn: '/login',
newUser: '/',
secret: nextAuthSecret,
providers: [],
session: {
strategy: "jwt",
},
providers: [
// added later in auth.ts since it requires bcrypt which is only compatible with Node.js
// while this file is also used in non-Node.js environments
],
callbacks: {
authorized({ auth, request: { nextUrl } }) {
const isLoggedIn = !!auth?.user;
const isOnChat = nextUrl.pathname.startsWith('/');
const isOnRegister = nextUrl.pathname.startsWith('/register');
const isOnLogin = nextUrl.pathname.startsWith('/login');

if (isLoggedIn && (isOnLogin || isOnRegister)) {
return Response.redirect(new URL('/', nextUrl as unknown as URL));
}

if (isOnRegister || isOnLogin) {
return true; // Always allow access to register and login pages
}

if (isOnChat) {
if (isLoggedIn) return true;
return false; // Redirect unauthenticated users to login page
session({ session, token }) {
if (!token.sub) {
return session;
}

if (isLoggedIn) {
return Response.redirect(new URL('/', nextUrl as unknown as URL));
const [, chainId, address] = token.sub.split(":");
if (chainId && address) {
session.address = address;
session.chainId = parseInt(chainId, 10);
}

return true;
return session;
},
},
} satisfies NextAuthConfig;
112 changes: 70 additions & 42 deletions app/(auth)/auth.ts
Original file line number Diff line number Diff line change
@@ -1,55 +1,83 @@
import { compare } from 'bcrypt-ts';
import NextAuth, { type User, type Session } from 'next-auth';
import Credentials from 'next-auth/providers/credentials';
import NextAuth from "next-auth";
import credentialsProvider from "next-auth/providers/credentials";
import { getAddressFromMessage, getChainIdFromMessage, type SIWESession } from "@reown/appkit-siwe";
import { createPublicClient, http } from "viem";

import { getUser } from '@/lib/db/queries';
import { authConfig } from "./auth.config";

import { authConfig } from './auth.config';
declare module "next-auth" {
interface Session extends SIWESession {
address: string;
chainId: number;
}
}

export const nextAuthSecret = process.env.NEXTAUTH_SECRET;
if (!nextAuthSecret) {
throw new Error("NEXTAUTH_SECRET is not set");
}

interface ExtendedSession extends Session {
user: User;
export const projectId = process.env.NEXT_PUBLIC_PROJECT_ID;
if (!projectId) {
throw new Error("NEXT_PUBLIC_PROJECT_ID is not set");
}

const providers = [
credentialsProvider({
name: "Ethereum",
credentials: {
message: {
label: "Message",
type: "text",
placeholder: "0x0",
},
signature: {
label: "Signature",
type: "text",
placeholder: "0x0",
},
},
async authorize(credentials) {
try {
if (!credentials?.message) {
throw new Error("SiweMessage is undefined");
}
const { message, signature } = credentials as Record<string, string>;
const address = getAddressFromMessage(message);
const chainId = getChainIdFromMessage(message);

const publicClient = createPublicClient({
transport: http(
`https://rpc.walletconnect.org/v1/?chainId=${chainId}&projectId=${projectId}`
),
});
const isValid = await publicClient.verifyMessage({
message,
address: address as `0x${string}`,
signature: signature as `0x${string}`,
});
// end o view verifyMessage

if (isValid) {
return {
id: `${chainId}:${address}`,
};
}

return null;
} catch (e) {
return null;
}
},
}),
];

export const {
handlers: { GET, POST },
auth,
signIn,
signOut,
} = NextAuth({
...authConfig,
providers: [
Credentials({
credentials: {},
async authorize({ email, password }: any) {
const users = await getUser(email);
if (users.length === 0) return null;
// biome-ignore lint: Forbidden non-null assertion.
const passwordsMatch = await compare(password, users[0].password!);
if (!passwordsMatch) return null;
return users[0] as any;
},
}),
],
callbacks: {
async jwt({ token, user }) {
if (user) {
token.id = user.id;
}

return token;
},
async session({
session,
token,
}: {
session: ExtendedSession;
token: any;
}) {
if (session.user) {
session.user.id = token.id as string;
}

return session;
},
},
providers,
});
Loading
Loading