plebdevs/src/pages/api/auth/[...nextauth].js

224 lines
8.0 KiB
JavaScript
Raw Normal View History

import NextAuth from "next-auth";
import { track } from '@vercel/analytics/server';
import CredentialsProvider from "next-auth/providers/credentials";
2024-09-06 12:32:23 -05:00
import EmailProvider from "next-auth/providers/email";
import NDK from "@nostr-dev-kit/ndk";
2024-09-06 12:32:23 -05:00
import { PrismaAdapter } from "@next-auth/prisma-adapter";
import prisma from "@/db/prisma";
2024-10-06 15:49:32 -05:00
import nodemailer from 'nodemailer';
import { findKind0Fields } from "@/utils/nostr";
2024-09-06 12:32:23 -05:00
import { generateSecretKey, getPublicKey } from 'nostr-tools/pure'
import { bytesToHex } from '@noble/hashes/utils'
2024-10-06 17:04:03 -05:00
import { updateUser, getUserByPubkey, createUser, getUserByEmail } from "@/db/models/userModels";
import { createRole } from "@/db/models/roleModels";
2024-09-17 13:55:51 -05:00
import appConfig from "@/config/appConfig";
2024-09-30 14:50:10 -05:00
// todo update EMAIL_FROM to be a plebdevs email
const ndk = new NDK({
2024-09-17 13:55:51 -05:00
explicitRelayUrls: appConfig.defaultRelayUrls,
});
2024-08-12 17:27:47 -05:00
const authorize = async (pubkey) => {
await ndk.connect();
const user = ndk.getUser({ pubkey });
try {
const profile = await user.fetchProfile();
// Check if user exists, create if not
let dbUser = await getUserByPubkey(pubkey);
2024-10-04 16:41:49 -05:00
if (dbUser) {
2024-08-12 17:27:47 -05:00
const fields = await findKind0Fields(profile);
// Only update 'avatar' or 'username' if they are different from kind0 fields on the dbUser
2024-10-05 16:37:44 -05:00
if (fields.avatar !== dbUser.avatar) {
const updatedUser = await updateUser(dbUser.id, { avatar: fields.avatar });
if (updatedUser) {
dbUser = await getUserByPubkey(pubkey);
}
} else if (fields.username !== dbUser.username) {
const updatedUser = await updateUser(dbUser.id, { username: fields.username });
if (updatedUser) {
dbUser = await getUserByPubkey(pubkey);
2024-10-04 16:41:49 -05:00
}
}
2024-10-05 16:37:44 -05:00
// add the kind0 fields to the user
const combinedUser = { ...dbUser, kind0: fields };
2024-08-12 17:27:47 -05:00
2024-10-04 16:41:49 -05:00
return combinedUser;
} else {
2024-08-12 17:27:47 -05:00
// Create user
if (profile) {
2024-10-06 15:46:33 -05:00
track('Nostr Signup', { pubkey: pubkey });
2024-08-12 17:27:47 -05:00
const fields = await findKind0Fields(profile);
2024-09-30 15:29:52 -05:00
const payload = { pubkey, username: fields.username, avatar: fields.avatar };
2024-08-12 17:27:47 -05:00
2024-10-04 16:41:49 -05:00
if (appConfig.authorPubkeys.includes(pubkey)) {
// create a new author role for this user
const createdUser = await createUser(payload);
const role = await createRole({
userId: createdUser.id,
admin: true,
subscribed: false,
});
if (!role) {
console.error("Failed to create role");
return null;
}
const updatedUser = await updateUser(createdUser.id, { role: role.id });
if (!updatedUser) {
console.error("Failed to update user");
return null;
}
2024-10-04 16:41:49 -05:00
const fullUser = await getUserByPubkey(pubkey);
return { ...fullUser, kind0: fields };
2024-10-04 16:41:49 -05:00
} else {
dbUser = await createUser(payload);
return { ...dbUser, kind0: fields };
2024-10-04 16:41:49 -05:00
}
2024-08-12 17:27:47 -05:00
}
}
} catch (error) {
console.error("Nostr login error:", error);
}
return null;
}
export const authOptions = {
2024-09-06 12:32:23 -05:00
adapter: PrismaAdapter(prisma),
providers: [
CredentialsProvider({
id: "nostr",
name: "Nostr",
credentials: {
pubkey: { label: "Public Key", type: "text" },
},
authorize: async (credentials) => {
if (credentials?.pubkey) {
2024-08-12 17:27:47 -05:00
return await authorize(credentials.pubkey);
}
return null;
},
}),
2024-09-06 12:32:23 -05:00
EmailProvider({
server: {
host: process.env.EMAIL_SERVER_HOST,
port: process.env.EMAIL_SERVER_PORT,
auth: {
user: process.env.EMAIL_SERVER_USER,
pass: process.env.EMAIL_SERVER_PASSWORD
}
},
from: process.env.EMAIL_FROM,
sendVerificationRequest: async ({ identifier, url, provider }) => {
2024-10-06 15:46:33 -05:00
// Track the email signup event
track('Email Signup', { email: identifier });
2024-10-06 15:46:33 -05:00
// Use nodemailer to send the email
const transport = nodemailer.createTransport(provider.server);
await transport.sendMail({
to: identifier,
from: provider.from,
subject: `Sign in to ${new URL(url).host}`,
text: `Sign in to ${new URL(url).host}\n${url}\n\n`,
html: `<p>Sign in to <strong>${new URL(url).host}</strong></p><p><a href="${url}">Sign in</a></p>`,
});
}
2024-09-06 12:32:23 -05:00
}),
2024-10-08 19:24:40 -05:00
CredentialsProvider({
id: "anonymous",
name: "Anonymous",
credentials: {
pubkey: { label: "Public Key", type: "text" },
privkey: { label: "Private Key", type: "text" },
},
authorize: async (credentials) => {
let pubkey, privkey;
if (credentials?.pubkey && credentials?.pubkey !== "null" && credentials?.privkey && credentials?.privkey !== "null") {
// Use provided keys
pubkey = credentials.pubkey;
privkey = credentials.privkey;
} else {
// Generate new keys
const sk = generateSecretKey();
pubkey = getPublicKey(sk);
privkey = bytesToHex(sk);
console.log('pubkey', pubkey);
console.log('privkey', privkey);
}
// Check if user exists in the database
let dbUser = await getUserByPubkey(pubkey);
if (!dbUser) {
// Create new user if not exists
dbUser = await createUser({
pubkey: pubkey,
username: pubkey.slice(0, 8), // Use first 8 characters of pubkey as username
});
}
// Return user object with pubkey and privkey
return { ...dbUser, pubkey, privkey };
},
}),
],
callbacks: {
2024-10-08 19:24:40 -05:00
async jwt({ token, user, account }) { // Add 'account' parameter here
2024-10-06 16:53:36 -05:00
if (user) {
token.user = user;
2024-10-08 19:24:40 -05:00
if (user.pubkey && user.privkey) {
token.pubkey = user.pubkey;
token.privkey = user.privkey;
}
}
if (account?.provider === 'anonymous') {
token.isAnonymous = true;
2024-10-06 16:53:36 -05:00
}
return token;
},
async session({ session, token }) {
2024-08-07 17:06:53 -05:00
session.user = token.user;
2024-10-08 19:24:40 -05:00
if (token.pubkey && token.privkey) {
session.pubkey = token.pubkey;
session.privkey = token.privkey;
}
session.isAnonymous = token.isAnonymous;
return session;
},
async redirect({ url, baseUrl }) {
2024-08-07 17:06:53 -05:00
return baseUrl;
},
async signOut({ token, session }) {
token = {}
session = {}
return true
2024-10-04 16:41:49 -05:00
},
2024-10-08 19:24:40 -05:00
async signIn({ user, account }) {
if (account.provider === 'anonymous') {
return {
...user,
pubkey: user.pubkey,
privkey: user.privkey,
};
}
return true;
},
},
secret: process.env.NEXTAUTH_SECRET,
2024-08-07 17:06:53 -05:00
session: { strategy: "jwt" },
jwt: {
signingKey: process.env.JWT_SECRET,
},
pages: {
signIn: "/auth/signin",
}
};
export default NextAuth(authOptions);