import NextAuth from 'next-auth' import Providers from 'next-auth/providers' import { PrismaLegacyAdapter } from '../../../lib/prisma-adapter' import prisma from '../../../api/models' import nodemailer from 'nodemailer' import { getSession } from 'next-auth/client' export default (req, res) => NextAuth(req, res, { callbacks: { /** * @param {object} token Decrypted JSON Web Token * @param {object} user User object (only available on sign in) * @param {object} account Provider account (only available on sign in) * @param {object} profile Provider profile (only available on sign in) * @param {boolean} isNewUser True if new user (only available on sign in) * @return {object} JSON Web Token that will be saved */ async jwt (token, user, account, profile, isNewUser) { // Add additional session params if (user?.id) { token.id = Number(user.id) // HACK next-auth needs this to do account linking with jwts // see: https://github.com/nextauthjs/next-auth/issues/625 token.user = { id: Number(user.id) } } if (isNewUser) { // if referrer exists, set on user if (req.cookies.sn_referrer && user?.id) { const referrer = await prisma.user.findUnique({ where: { name: req.cookies.sn_referrer } }) if (referrer) { await prisma.user.update({ where: { id: user.id }, data: { referrerId: referrer.id } }) } } // sign them up for the newsletter if (profile.email) { fetch(process.env.LIST_MONK_URL + '/api/subscribers', { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: 'Basic ' + Buffer.from(process.env.LIST_MONK_AUTH).toString('base64') }, body: JSON.stringify({ email: profile.email, name: 'blank', lists: [2], status: 'enabled', preconfirm_subscriptions: true }) }).then(async r => console.log(await r.json())).catch(console.log) } } return token }, async session (session, token) { // we need to add additional session params here session.user.id = Number(token.id) return session } }, providers: [ Providers.Credentials({ id: 'lightning', // The name to display on the sign in form (e.g. 'Sign in with...') name: 'Lightning', // The credentials is used to generate a suitable form on the sign in page. // You can specify whatever fields you are expecting to be submitted. // e.g. domain, username, password, 2FA token, etc. credentials: { pubkey: { label: 'publickey', type: 'text' }, k1: { label: 'k1', type: 'text' } }, async authorize (credentials, req) { const { k1, pubkey } = credentials try { const lnauth = await prisma.lnAuth.findUnique({ where: { k1 } }) await prisma.lnAuth.delete({ where: { k1 } }) if (lnauth.pubkey === pubkey) { let user = await prisma.user.findUnique({ where: { pubkey } }) const session = await getSession({ req }) if (!user) { // if we are logged in, update rather than create if (session?.user) { user = await prisma.user.update({ where: { id: session.user.id }, data: { pubkey } }) } else { user = await prisma.user.create({ data: { name: pubkey.slice(0, 10), pubkey } }) } } else if (session && session.user?.id !== user.id) { throw new Error('account not linked') } return user } } catch (error) { console.log(error) } return null } }), Providers.Credentials({ id: 'slashtags', // The name to display on the sign in form (e.g. 'Sign in with...') name: 'Slashtags', // The credentials is used to generate a suitable form on the sign in page. // You can specify whatever fields you are expecting to be submitted. // e.g. domain, username, password, 2FA token, etc. credentials: { pubkey: { label: 'publickey', type: 'text' }, k1: { label: 'k1', type: 'text' } }, async authorize (credentials, req) { const { k1, pubkey } = credentials try { const lnauth = await prisma.lnAuth.findUnique({ where: { k1 } }) await prisma.lnAuth.delete({ where: { k1 } }) if (lnauth.pubkey === pubkey) { let user = await prisma.user.findUnique({ where: { slashtagId: pubkey } }) const session = await getSession({ req }) if (!user) { // if we are logged in, update rather than create if (session?.user) { user = await prisma.user.update({ where: { id: session.user.id }, data: { slashtagId: pubkey } }) } else { user = await prisma.user.create({ data: { name: pubkey.slice(0, 10), slashtagId: pubkey } }) } } else if (session && session.user?.id !== user.id) { throw new Error('account not linked') } return user } } catch (error) { console.log(error) } return null } }), Providers.GitHub({ clientId: process.env.GITHUB_ID, clientSecret: process.env.GITHUB_SECRET, authorization: 'https://github.com/login/oauth/authorize?scope=read:user', profile: profile => { return { ...profile, name: profile.login } } }), Providers.Twitter({ clientId: process.env.TWITTER_ID, clientSecret: process.env.TWITTER_SECRET, profile: profile => { return { ...profile, name: profile.screen_name } } }), Providers.Email({ server: process.env.LOGIN_EMAIL_SERVER, from: process.env.LOGIN_EMAIL_FROM, sendVerificationRequest, profile: profile => { return profile } }) ], adapter: PrismaLegacyAdapter({ prisma }), secret: process.env.NEXTAUTH_SECRET, session: { jwt: true }, jwt: { signingKey: process.env.JWT_SIGNING_PRIVATE_KEY }, pages: { signIn: '/login', verifyRequest: '/email' } }) function sendVerificationRequest ({ identifier: email, url, token, baseUrl, provider }) { return new Promise((resolve, reject) => { const { server, from } = provider // Strip protocol from URL and use domain as site name const site = baseUrl.replace(/^https?:\/\//, '') nodemailer.createTransport(server).sendMail( { to: email, from, subject: `login to ${site}`, text: text({ url, site, email }), html: html({ url, site, email }) }, (error) => { if (error) { return reject(new Error('SEND_VERIFICATION_EMAIL_ERROR', error)) } return resolve() } ) }) } // Email HTML body const html = ({ url, site, email }) => { // Insert invisible space into domains and email address to prevent both the // email address and the domain from being turned into a hyperlink by email // clients like Outlook and Apple mail, as this is confusing because it seems // like they are supposed to click on their email address to sign in. const escapedEmail = `${email.replace(/\./g, '​.')}` const escapedSite = `${site.replace(/\./g, '​.')}` // Some simple styling options const backgroundColor = '#f5f5f5' const textColor = '#212529' const mainBackgroundColor = '#ffffff' const buttonBackgroundColor = '#FADA5E' const buttonBorderColor = '#FADA5E' const buttonTextColor = '#212529' // Uses tables for layout and inline CSS due to email client limitations return `
${escapedSite}
login as ${escapedEmail}
login
Or copy and paste this link: ${url}
If you did not request this email you can safely ignore it.
` } // Email text body –fallback for email clients that don't render HTML const text = ({ url, site }) => `Sign in to ${site}\n${url}\n\n`