source: app/(auth)/actions.ts@ b04ba1e

nextjs
Last change on this file since b04ba1e was b04ba1e, checked in by Vasilaki Totsili <vasilaki@…>, 3 days ago

refactor: implement transaction management for db operations

Replaced single transaction operations with transaction blocks for
critical database mutations to ensure atomicity and consistency,
preventing race conditions and ensuring that operations like checking
for existing records and subsequent inserts or updates are atomic:

  • Implement transactional blocks for addTag, updateProfile and register functions to handle duplicate checks and data insertion/updating in a single transaction scope
  • Handle specific error messages in a more controlled manner by checking exception types and messages
  • Property mode set to 100644
File size: 2.7 KB
Line 
1'use server'
2
3import { z } from 'zod';
4import { sql } from '@/app/lib/db';
5import { signIn } from '@/auth';
6import bcrypt from "bcryptjs";
7import { AuthError } from 'next-auth';
8
9export async function authenticate(
10 prevState: string | undefined,
11 formData: FormData,
12) {
13 try {
14 const redirectTo =
15 (formData.get('redirectTo') as string)?.startsWith('/')
16 ? (formData.get('redirectTo') as string)
17 : '/dashboard';
18
19 await signIn('credentials', {
20 ...Object.fromEntries(formData),
21 redirectTo,
22 });
23 } catch (error) {
24 if (error instanceof AuthError) {
25 switch (error.type) {
26 case 'CredentialsSignin':
27 return 'Invalid email or password.';
28 default:
29 return 'Something went wrong. Please try again.';
30 }
31 }
32 throw error;
33 }
34}
35
36export async function register(
37 prevState: string | undefined,
38 formData: FormData,
39) {
40 const schema = z.object({
41 user_name: z.string().min(1),
42 email: z.string().email(),
43 password: z.string().min(6),
44 redirectTo: z.string().optional(),
45 });
46
47 const parsed = schema.safeParse({
48 user_name: formData.get('user_name'),
49 email: formData.get('email'),
50 password: formData.get('password'),
51 redirectTo: formData.get('redirectTo'),
52 });
53
54 if (!parsed.success) {
55 return 'Invalid form data.';
56 }
57
58 const { user_name, email, password, redirectTo } = parsed.data;
59
60 // sanitize redirect
61 const safeRedirect =
62 redirectTo?.startsWith('/') ? redirectTo : '/dashboard';
63
64 const hashed = await bcrypt.hash(password, 10);
65
66 try {
67 // eslint-disable-next-line @typescript-eslint/no-explicit-any
68 await sql.begin(async (tx: any) => {
69 const existing = await tx`SELECT user_id FROM "user" WHERE email=${email}`;
70
71 if (existing.length > 0) {
72 throw new Error('User already exists.');
73 }
74
75 await tx`
76 INSERT INTO "user" (user_name, email, password)
77 VALUES (${user_name}, ${email}, ${hashed})
78 `;
79 });
80 } catch (e: any) {
81 if (e instanceof Error && e.message === 'User already exists.') {
82 return e.message;
83 }
84 return 'Failed to create user.';
85 }
86
87 try {
88 await signIn('credentials', {
89 email,
90 password,
91 redirectTo: safeRedirect,
92 });
93 } catch (error) {
94 if (error instanceof AuthError) {
95 return 'Account created, but auto-login failed. Please log in.';
96 }
97 throw error;
98 }
99}
Note: See TracBrowser for help on using the repository browser.