@@ -1276,3 +1276,242 @@ export const newPassword = async (
1276
1276
```
1277
1277
1278
1278
It shoul now start working.
1279
+
1280
+ ## 2Factor Authentication
1281
+
1282
+ 95 . Update ` schema.prisma ` file
1283
+
1284
+ - user model
1285
+
1286
+ ```
1287
+ model User {
1288
+ id String @id @default(cuid())
1289
+ name String?
1290
+ email String? @unique
1291
+ emailVerified DateTime?
1292
+ image String?
1293
+ password String?
1294
+ accounts Account[]
1295
+ role UserRole @default(USER)
1296
+ isTwoFactorEnabled Boolean @default(false)
1297
+ twoFactorConfirmation TwoFactorConfirmation?
1298
+ }
1299
+ ```
1300
+
1301
+ - isTwoFactorEnabled model
1302
+
1303
+ ```
1304
+ model TwoFactorToken {
1305
+ id String @id @default(cuid())
1306
+ email String
1307
+ token String @unique
1308
+ expires DateTime
1309
+
1310
+ @@unique([email, token])
1311
+ }
1312
+ ```
1313
+
1314
+ - TwoFactorConfirmation model
1315
+
1316
+ ```
1317
+ model TwoFactorConfirmation {
1318
+ id String @id @default(cuid())
1319
+
1320
+ userId String
1321
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
1322
+
1323
+ @@unique([userId])
1324
+ }
1325
+ ```
1326
+
1327
+ 96 . Now run the following command
1328
+ ` npx prisma generate ` and ` npx prisma db push `
1329
+
1330
+ 97 . # Setting up two-factor-token
1331
+
1332
+ - Create a new file in ` lib/actions/auth ` folder i.e ` two-factor-token.ts ` file
1333
+
1334
+ ```
1335
+ import { db } from "@/lib/database.connection";
1336
+
1337
+ export const getTwoFactorTokenByToken = async (token: string) => {
1338
+ try {
1339
+ const twoFactorToken = await db.twoFactorToken.findUnique({
1340
+ where: { token },
1341
+ });
1342
+
1343
+ return twoFactorToken;
1344
+ } catch {
1345
+ return null;
1346
+ }
1347
+ };
1348
+
1349
+ export const getTwoFactorTokenByEmail = async (email: string) => {
1350
+ try {
1351
+ const twoFactorToken = await db.twoFactorToken.findFirst({
1352
+ where: { email },
1353
+ });
1354
+
1355
+ return twoFactorToken;
1356
+ } catch {
1357
+ return null;
1358
+ }
1359
+ };
1360
+ ```
1361
+
1362
+ - Setting up the action for confirming two-factor token in ` lib/actions/auth ` folder ` two-factor-confirmation.ts ` file
1363
+
1364
+ ```
1365
+ import { db } from "@/lib/database.connection";
1366
+
1367
+ export const getTwoFactorConfirmationByUserId = async (userId: string) => {
1368
+ try {
1369
+ const twoFactorConfirmation = await db.twoFactorConfirmation.findUnique({
1370
+ where: { userId },
1371
+ });
1372
+
1373
+ return twoFactorConfirmation;
1374
+ } catch {
1375
+ return null;
1376
+ }
1377
+ };
1378
+
1379
+ ```
1380
+
1381
+ - Generating Two factor token in ` lib/token ` file
1382
+
1383
+ ```
1384
+ export const generateTwoFactorToken = async (email: string) => {
1385
+ const token = crypto.randomInt(100_000, 1_000_000).toString();
1386
+ const expires = new Date(new Date().getTime() + 5 * 60 * 1000);
1387
+
1388
+ const existingToken = await getTwoFactorTokenByEmail(email);
1389
+
1390
+ if (existingToken) {
1391
+ await db.twoFactorToken.delete({
1392
+ where: {
1393
+ id: existingToken.id,
1394
+ }
1395
+ });
1396
+ }
1397
+
1398
+ const twoFactorToken = await db.twoFactorToken.create({
1399
+ data: {
1400
+ email,
1401
+ token,
1402
+ expires,
1403
+ }
1404
+ });
1405
+
1406
+ return twoFactorToken;
1407
+ }
1408
+ ```
1409
+
1410
+ - Sned two factor token email
1411
+
1412
+ ```
1413
+ export const sendTwoFactorTokenEmail = async (
1414
+ email: string,
1415
+ token: string
1416
+ ) => {
1417
+ await resend.emails.send({
1418
+
1419
+ to: email,
1420
+ subject: "2FA Code",
1421
+ html: `<p>Your 2FA code: ${token}</p>`
1422
+ });
1423
+ };
1424
+ ```
1425
+
1426
+ 98 . Go to prisma studio and enable the 2FA for a user
1427
+
1428
+ 99 . Modify the login function in ` auth.ts ` file
1429
+
1430
+ ```
1431
+
1432
+ // * Prevent sign in without two factor confirmation (99)
1433
+ if (existingUser.isTwoFactorEnabled) {
1434
+ const twoFactorConfirmation = await getTwoFactorConfirmationByUserId(
1435
+ existingUser.id
1436
+ );
1437
+
1438
+ if (!twoFactorConfirmation) return false;
1439
+
1440
+ // Delete two factor confirmation for next sign in
1441
+ await db.twoFactorConfirmation.delete({
1442
+ where: { id: twoFactorConfirmation.id },
1443
+ });
1444
+ }
1445
+ ```
1446
+
1447
+ 100 . Add 2FA Verification in ` login.ts ` file
1448
+
1449
+ ```
1450
+ //* 2FA verification
1451
+ if (exisitingUser.isTwoFactorEnabled && exisitingUser.email) {
1452
+ if (code) {
1453
+ const twoFactorToken = await getTwoFactorTokenByEmail(exisitingUser.email);
1454
+
1455
+ if (!twoFactorToken) {
1456
+ return { error: "Invalid code!" };
1457
+ }
1458
+
1459
+ if (twoFactorToken.token !== code) {
1460
+ return { error: "Invalid code!" };
1461
+ }
1462
+
1463
+ const hasExpired = new Date(twoFactorToken.expires) < new Date();
1464
+
1465
+ if (hasExpired) {
1466
+ return { error: "Code expired!" };
1467
+ }
1468
+
1469
+ await db.twoFactorToken.delete({
1470
+ where: { id: twoFactorToken.id },
1471
+ });
1472
+
1473
+ const existingConfirmation = await getTwoFactorConfirmationByUserId(
1474
+ exisitingUser.id
1475
+ );
1476
+
1477
+ if (existingConfirmation) {
1478
+ await db.twoFactorConfirmation.delete({
1479
+ where: { id: existingConfirmation.id },
1480
+ });
1481
+ }
1482
+
1483
+ await db.twoFactorConfirmation.create({
1484
+ data: {
1485
+ userId: exisitingUser.id,
1486
+ },
1487
+ });
1488
+ } else {
1489
+ const twoFactorToken = await generateTwoFactorToken(exisitingUser.email);
1490
+ await sendTwoFactorTokenEmail(twoFactorToken.email, twoFactorToken.token);
1491
+
1492
+ return { twoFactor: true };
1493
+ }
1494
+ }
1495
+ ```
1496
+
1497
+ 101 . Update the login schema
1498
+
1499
+ ```
1500
+ export const LoginSchema = z.object({
1501
+ email: z.string().email({
1502
+ message: "Email is required",
1503
+ }),
1504
+ password: z.string().min(1, {
1505
+ message: "Password is required",
1506
+ }),
1507
+ code: z.optional(z.string()),
1508
+ });
1509
+
1510
+ ```
1511
+ 102 . Update the login form, based on the conditions (see the code directly from file)
1512
+ - concept is to show 2FA code, when after the login button is clicked, and two factor is enabled
1513
+ - (might be incomplete)
1514
+
1515
+
1516
+
1517
+
0 commit comments