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'
}
})
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 `
login as ${escapedEmail}
|
|
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`