added 2FA
This commit is contained in:
45
actions/logger.ts
Normal file
45
actions/logger.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import pino from 'pino'
|
||||
|
||||
const pinoConfigProd: pino.LoggerOptions = {
|
||||
transport: {
|
||||
targets: [
|
||||
{
|
||||
target: 'pino/file', options: {
|
||||
destination: './production.log', mkdir: true, minLength: 4096, sync: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
level: 'error',
|
||||
redact: {
|
||||
paths: ['password', '*.password'], remove: true,
|
||||
},
|
||||
}
|
||||
|
||||
const pinoConfigDev: pino.LoggerOptions = {
|
||||
redact: {
|
||||
paths: ['password', '*.password'], remove: false,
|
||||
},
|
||||
// formatters: {
|
||||
// bindings: (bindings) => {
|
||||
// return { pid: bindings.pid, host: bindings.hostname, node_version: process.version }
|
||||
// },
|
||||
// },
|
||||
transport: {
|
||||
targets: [
|
||||
{
|
||||
//target: 'pino/file',
|
||||
target: 'pino-pretty', options: { destination: `./pretty.log`, mkdir: true, colorize: false }, level: 'error',
|
||||
}, {
|
||||
target: 'pino-pretty', level: 'trace',
|
||||
}],
|
||||
},
|
||||
}
|
||||
|
||||
const journal = process.env.NODE_ENV === 'production'
|
||||
? pino(pinoConfigProd)
|
||||
: pino(pinoConfigDev)
|
||||
|
||||
export default journal
|
||||
|
||||
// TODO: wait for newer version of https://betterstack.com/docs/logs/javascript/pino/
|
||||
@@ -6,7 +6,15 @@ import { signIn } from '@/config/auth'
|
||||
import { DEFAULT_LOGIN_REDIRECT } from '@/config/routes'
|
||||
import { AuthError } from 'next-auth'
|
||||
import { getUserByEmail } from '@/data/user'
|
||||
import { sendVerificationEmail } from '@/actions/send-verification-email'
|
||||
import { sendTwoFactorTokenEmail, sendVerificationEmail } from '@/actions/send-verification-email'
|
||||
import { generateTwoFactorToken } from '@/lib/tokens'
|
||||
import { deleteTwoFactorToken, getTwoFactorTokenByEmail } from '@/data/two-factor-token'
|
||||
import {
|
||||
createTwoFactoComfirmation,
|
||||
deleteTwoFactoComfirmation,
|
||||
getTwoFactorConfirmationByUserId,
|
||||
} from '@/data/two-factor-confirmation'
|
||||
import journal from '@/actions/logger'
|
||||
|
||||
export const login = async (values: zInfer<typeof LoginSchema>) => {
|
||||
const validatedFields = LoginSchema.safeParse(values)
|
||||
@@ -15,7 +23,7 @@ export const login = async (values: zInfer<typeof LoginSchema>) => {
|
||||
return { error: 'auth.form.error.invalid_fields' }
|
||||
}
|
||||
|
||||
const { email, password } = validatedFields.data
|
||||
const { email, password, code } = validatedFields.data
|
||||
|
||||
const existingUser = await getUserByEmail(email)
|
||||
|
||||
@@ -27,6 +35,40 @@ export const login = async (values: zInfer<typeof LoginSchema>) => {
|
||||
return await sendVerificationEmail(existingUser.email, existingUser.name)
|
||||
}
|
||||
|
||||
if (existingUser.isTwoFactorEnabled && existingUser.email) {
|
||||
if (code) {
|
||||
const twoFactorToken = await getTwoFactorTokenByEmail(existingUser.email)
|
||||
if (!twoFactorToken || twoFactorToken.token !== code) {
|
||||
return { error: 'auth.form.error.invalid_code' }
|
||||
}
|
||||
|
||||
const hasExpired = new Date(twoFactorToken.expires) < new Date()
|
||||
if (hasExpired) {
|
||||
return { error: 'auth.form.error.expired_token' }
|
||||
}
|
||||
|
||||
await deleteTwoFactorToken(twoFactorToken.id)
|
||||
|
||||
const existingConfirmation = await getTwoFactorConfirmationByUserId(existingUser.id)
|
||||
|
||||
if (existingConfirmation) {
|
||||
await deleteTwoFactoComfirmation(existingConfirmation.id)
|
||||
}
|
||||
|
||||
await createTwoFactoComfirmation(existingUser.id)
|
||||
|
||||
} else {
|
||||
const twoFactorToken = await generateTwoFactorToken(existingUser.email)
|
||||
|
||||
if (twoFactorToken) {
|
||||
const isOk = await sendTwoFactorTokenEmail(twoFactorToken.email, twoFactorToken.token, existingUser.name)
|
||||
return { twoFactor: isOk }
|
||||
}
|
||||
console.error('ERROR.TYPE: could not send token')
|
||||
return { error: 'common.something_went_wrong' }
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await signIn('credentials', {
|
||||
email, password, redirectTo: DEFAULT_LOGIN_REDIRECT,
|
||||
|
||||
@@ -2,11 +2,32 @@
|
||||
|
||||
import mailer from '@/lib/mailer'
|
||||
import { AUTH_NEW_PASSWORD_URL, AUTH_USER_VERIFICATION_URL } from '@/config/routes'
|
||||
import { generatePasswordResetToken, generateVerificationToken } from '@/lib/tokens'
|
||||
import { generatePasswordResetToken, generateTwoFactorToken, generateVerificationToken } from '@/lib/tokens'
|
||||
import { env } from '@/lib/utils'
|
||||
import { __ct } from '@/lib/translate'
|
||||
import { body } from '@/templates/email/send-verification-email'
|
||||
|
||||
export const sendTwoFactorTokenEmail = async (email: string, token: string, name?: string | null) => {
|
||||
const { isOk, code, info, error } = await mailer({
|
||||
to: name ? { name: name?.toString(), address: email } : email,
|
||||
subject: await __ct({
|
||||
key: 'mailer.subject.send_2FA_code',
|
||||
params: { site_name: env('SITE_NAME') },
|
||||
}),
|
||||
text: `Your 2FA code: ${token}`,
|
||||
html: `<p>Your 2FA code: ${token}</p>`,
|
||||
})
|
||||
|
||||
return isOk
|
||||
// TODO: Log this action
|
||||
// if (isOk && code === 250) {
|
||||
// //return //'auth.email.success._2FA_email_sent'
|
||||
// return { success: code === 250 ? 'auth.email.success._2FA_email_sent' : info?.response }
|
||||
// } else {
|
||||
// return { error: env('DEBUG') === 'true' ? error?.response : 'auth.email.error._2FA_email_sending_error' }
|
||||
// }
|
||||
}
|
||||
|
||||
const sendVerificationEmail = async (
|
||||
email: string,
|
||||
name?: string | null,
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
'use server'
|
||||
|
||||
import db from '@/lib/db'
|
||||
import { getVerificationTokenByToken } from '@/data/verification-token'
|
||||
import { getUserByEmail } from '@/data/user'
|
||||
import { deleteVerificationToken, getVerificationTokenByToken } from '@/data/verification-token'
|
||||
import { getUserByEmail, updateUserEmailVerified } from '@/data/user'
|
||||
|
||||
export const userVerification = async (token: string) => {
|
||||
const existingToken = await getVerificationTokenByToken(token)
|
||||
@@ -17,25 +16,9 @@ export const userVerification = async (token: string) => {
|
||||
|
||||
if (!existingUser) return { error: 'Email associated with token not found!' }
|
||||
|
||||
try {
|
||||
await db.user.update({
|
||||
where: { id: existingUser.id }, data: {
|
||||
email: existingToken.email, emailVerified: new Date(),
|
||||
},
|
||||
})
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
return { error: 'db.error.update.user_data' }
|
||||
}
|
||||
await updateUserEmailVerified(existingUser.id, existingToken.email)
|
||||
|
||||
try {
|
||||
await db.verificationToken.delete({
|
||||
where: { id: existingToken.id },
|
||||
})
|
||||
} catch (e) {
|
||||
// TODO: log error on disc or db
|
||||
console.error(e)
|
||||
}
|
||||
await deleteVerificationToken(existingToken.id)
|
||||
|
||||
return { success: 'User verified!' }
|
||||
}
|
||||
Reference in New Issue
Block a user