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-only acces to public information 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' } }) async function sendVerificationRequest ({ identifier: email, url, token, baseUrl, provider }) { const user = await prisma.user.findUnique({ where: { email } }) 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: user ? html({ url, site, email }) : newUserHtml({ 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 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` const newUserHtml = ({ url, site, email }) => { const escapedEmail = `${email.replace(/\./g, '​.')}` const replaceCb = (path) => { const urlObj = new URL(url) urlObj.searchParams.set('callbackUrl', path) return urlObj.href } const dailyUrl = replaceCb('/daily') const guideUrl = replaceCb('/guide') const faqUrl = replaceCb('/faq') const topUrl = replaceCb('/top/users/forever') const postUrl = replaceCb('/post') // Some simple styling options const backgroundColor = '#f5f5f5' const textColor = '#212529' const mainBackgroundColor = '#ffffff' const buttonBackgroundColor = '#FADA5E' return `
Welcome to Stacker News!
If you know how Stacker News works, click the login button below.
If you want to learn how Stacker News works, keep reading.
login as ${escapedEmail}
login
Or copy and paste this link: ${url}
Stacker News is like Reddit or Hacker News, but it pays you Bitcoin. Instead of giving posts or comments “upvotes,” Stacker News users (aka stackers) send you small amounts of Bitcoin called sats.
In fact, some stackers have already stacked millions of sats just for posting and starting thoughtful conversations.
To start earning sats, click here to make your first post. You can share links, discussion questions, polls, or even bounties with other stackers. This guide offers some useful tips and best practices for sharing content on Stacker News.
If you’re not sure what to share, click here to introduce yourself to the community with a comment on the daily discussion thread.
If you still have questions, click here to learn more about Stacker News by reading our FAQ.
If anything isn’t clear, comment on the FAQ post and we’ll answer your question.
Zap,
Stacker News
P.S. Stacker News loves you!
If you did not request this email you can safely ignore it.
` }