2024-08-07 16:02:13 -05:00
|
|
|
import NextAuth from "next-auth";
|
|
|
|
import CredentialsProvider from "next-auth/providers/credentials";
|
2024-09-06 12:32:23 -05:00
|
|
|
import EmailProvider from "next-auth/providers/email";
|
2024-08-07 16:02:13 -05:00
|
|
|
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-08-07 16:02:13 -05:00
|
|
|
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-02 16:58:36 -05:00
|
|
|
import { updateUser, getUserByPubkey, createUser } from "@/db/models/userModels";
|
2024-09-11 16:48:56 -05:00
|
|
|
import { createRole } from "@/db/models/roleModels";
|
2024-09-17 13:55:51 -05:00
|
|
|
import appConfig from "@/config/appConfig";
|
2024-08-07 16:02:13 -05:00
|
|
|
|
2024-09-30 14:50:10 -05:00
|
|
|
// todo update EMAIL_FROM to be a plebdevs email
|
2024-08-07 16:02:13 -05:00
|
|
|
const ndk = new NDK({
|
2024-09-17 13:55:51 -05:00
|
|
|
explicitRelayUrls: appConfig.defaultRelayUrls,
|
2024-08-07 16:02:13 -05:00
|
|
|
});
|
|
|
|
|
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
|
2024-10-02 16:58:36 -05:00
|
|
|
let dbUser = await getUserByPubkey(pubkey);
|
2024-10-04 16:41:49 -05:00
|
|
|
|
2024-10-02 16:58:36 -05:00
|
|
|
if (dbUser) {
|
2024-08-12 17:27:47 -05:00
|
|
|
const fields = await findKind0Fields(profile);
|
2024-10-05 16:13:01 -05:00
|
|
|
// Only update 'avatar' or 'username' if they are different from kind0 fields on the dbUser
|
|
|
|
const updatedFields = ['avatar', 'username'].reduce((acc, key) => {
|
|
|
|
if (fields[key] !== dbUser[key]) {
|
2024-10-04 16:41:49 -05:00
|
|
|
acc[key] = fields[key];
|
|
|
|
}
|
|
|
|
return acc;
|
|
|
|
}, {});
|
|
|
|
|
|
|
|
// if there are updated fields, update the user only with the updated fields
|
|
|
|
if (Object.keys(updatedFields).length > 0) {
|
|
|
|
dbUser = await updateUser(dbUser.id, updatedFields);
|
|
|
|
}
|
|
|
|
|
2024-08-12 17:27:47 -05:00
|
|
|
// Combine user object with kind0Fields, giving priority to kind0Fields
|
2024-10-05 16:13:01 -05:00
|
|
|
const combinedUser = { ...dbUser, kind0: fields };
|
2024-08-12 17:27:47 -05:00
|
|
|
|
2024-10-04 16:41:49 -05:00
|
|
|
return combinedUser;
|
2024-10-02 16:58:36 -05:00
|
|
|
} else {
|
2024-08-12 17:27:47 -05:00
|
|
|
// Create user
|
|
|
|
if (profile) {
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
const fullUser = await getUserByPubkey(pubkey);
|
|
|
|
|
2024-10-05 16:13:01 -05:00
|
|
|
return { ...fullUser, kind0: fields };
|
2024-10-04 16:41:49 -05:00
|
|
|
} else {
|
|
|
|
dbUser = await createUser(payload);
|
2024-10-05 16:13:01 -05:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2024-09-08 18:41:51 -05:00
|
|
|
export const authOptions = {
|
2024-09-06 12:32:23 -05:00
|
|
|
adapter: PrismaAdapter(prisma),
|
2024-08-07 16:02:13 -05:00
|
|
|
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);
|
2024-08-07 16:02:13 -05:00
|
|
|
}
|
|
|
|
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
|
|
|
|
}),
|
2024-08-07 16:02:13 -05:00
|
|
|
],
|
|
|
|
callbacks: {
|
2024-08-12 17:27:47 -05:00
|
|
|
async jwt({ token, trigger, user }) {
|
|
|
|
if (trigger === "update") {
|
|
|
|
// if we trigger an update call the authorize function again
|
|
|
|
const newUser = await authorize(token.user.pubkey);
|
|
|
|
token.user = newUser;
|
|
|
|
}
|
2024-09-06 12:32:23 -05:00
|
|
|
// if the user has no pubkey, generate a new key pair
|
|
|
|
if (token && token?.user && token?.user?.id && !token.user?.pubkey) {
|
|
|
|
try {
|
2024-10-04 16:41:49 -05:00
|
|
|
let sk = generateSecretKey()
|
2024-09-06 12:32:23 -05:00
|
|
|
let pk = getPublicKey(sk)
|
|
|
|
let skHex = bytesToHex(sk)
|
2024-10-04 16:41:49 -05:00
|
|
|
const updatedUser = await updateUser(token.user.id, { pubkey: pk, privkey: skHex });
|
2024-09-06 12:32:23 -05:00
|
|
|
if (!updatedUser) {
|
|
|
|
console.error("Failed to update user");
|
|
|
|
return null;
|
|
|
|
}
|
2024-10-05 16:13:01 -05:00
|
|
|
const fullUser = await getUserByPubkey(pk);
|
|
|
|
token.user = fullUser;
|
2024-09-06 12:32:23 -05:00
|
|
|
} catch (error) {
|
|
|
|
console.error("Ephemeral key pair generation error:", error);
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
2024-10-05 16:13:01 -05:00
|
|
|
|
2024-08-07 16:02:13 -05:00
|
|
|
if (user) {
|
2024-08-07 17:06:53 -05:00
|
|
|
token.user = user;
|
2024-08-07 16:02:13 -05:00
|
|
|
}
|
|
|
|
return token;
|
|
|
|
},
|
|
|
|
async session({ session, token }) {
|
2024-08-07 17:06:53 -05:00
|
|
|
// Add user from token to session
|
|
|
|
session.user = token.user;
|
|
|
|
session.jwt = token;
|
2024-08-07 16:02:13 -05:00
|
|
|
return session;
|
|
|
|
},
|
|
|
|
async redirect({ url, baseUrl }) {
|
2024-08-07 17:06:53 -05:00
|
|
|
return baseUrl;
|
2024-08-07 16:02:13 -05:00
|
|
|
},
|
2024-08-13 16:28:25 -05:00
|
|
|
async signOut({ token, session }) {
|
|
|
|
token = {}
|
|
|
|
session = {}
|
|
|
|
return true
|
2024-10-04 16:41:49 -05:00
|
|
|
},
|
2024-08-07 16:02:13 -05:00
|
|
|
},
|
|
|
|
secret: process.env.NEXTAUTH_SECRET,
|
2024-08-07 17:06:53 -05:00
|
|
|
session: { strategy: "jwt" },
|
2024-08-07 16:02:13 -05:00
|
|
|
jwt: {
|
|
|
|
signingKey: process.env.JWT_SECRET,
|
|
|
|
},
|
|
|
|
pages: {
|
|
|
|
signIn: "/auth/signin",
|
2024-09-09 15:44:18 -05:00
|
|
|
}
|
2024-09-08 18:41:51 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
export default NextAuth(authOptions);
|