Allow super admin to create users and reset password (#238)

1. Add user creation and password reset dialog functionalities; added Junie guidelines

Introduced new `AdminCreateUserDialog` and `AdminResetPasswordDialog` components for managing user accounts in the admin panel. Updated the `AdminAccountsTable` page with a button for user creation and implemented backend logic for password resets with robust error handling.

2. Added Jetbrains AI guidelines
This commit is contained in:
Giancarlo Buomprisco
2025-04-22 06:36:34 +07:00
committed by GitHub
parent e193c94f06
commit 4f41304be4
11 changed files with 2291 additions and 47 deletions

View File

@@ -15,6 +15,8 @@ import {
ImpersonateUserSchema,
ReactivateUserSchema,
} from './schema/admin-actions.schema';
import { CreateUserSchema } from './schema/create-user.schema';
import { ResetPasswordSchema } from './schema/reset-password.schema';
import { createAdminAccountsService } from './services/admin-accounts.service';
import { createAdminAuthUserService } from './services/admin-auth-user.service';
import { adminAction } from './utils/admin-action';
@@ -150,6 +152,80 @@ export const deleteAccountAction = adminAction(
),
);
/**
* @name createUserAction
* @description Create a new user in the system.
*/
export const createUserAction = adminAction(
enhanceAction(
async ({ email, password, emailConfirm }) => {
const adminClient = getSupabaseServerAdminClient();
const logger = await getLogger();
logger.info({ email }, `Super Admin is creating a new user...`);
const { data, error } = await adminClient.auth.admin.createUser({
email,
password,
email_confirm: emailConfirm,
});
if (error) {
logger.error({ error }, `Error creating user`);
throw new Error(`Error creating user: ${error.message}`);
}
logger.info(
{ userId: data.user.id },
`Super Admin has successfully created a new user`,
);
revalidateAdmin();
return {
success: true,
user: data.user,
};
},
{
schema: CreateUserSchema,
},
),
);
/**
* @name resetPasswordAction
* @description Reset a user's password by sending a password reset email.
*/
export const resetPasswordAction = adminAction(
enhanceAction(
async ({ userId }) => {
const service = getAdminAuthService();
const logger = await getLogger();
logger.info({ userId }, `Super Admin is resetting user password...`);
const result = await service.resetPassword(userId);
logger.info(
{ userId },
`Super Admin has successfully sent password reset email`,
);
revalidateAdmin();
return result;
},
{
schema: ResetPasswordSchema,
},
),
);
function revalidateAdmin() {
revalidatePath('/admin', 'layout');
}
function getAdminAuthService() {
const client = getSupabaseServerClient();
const adminClient = getSupabaseServerAdminClient();
@@ -162,7 +238,3 @@ function getAdminAccountsService() {
return createAdminAccountsService(adminClient);
}
function revalidateAdmin() {
revalidatePath('/admin', 'layout');
}

View File

@@ -0,0 +1,11 @@
import { z } from 'zod';
export const CreateUserSchema = z.object({
email: z.string().email({ message: 'Please enter a valid email address' }),
password: z
.string()
.min(8, { message: 'Password must be at least 8 characters' }),
emailConfirm: z.boolean().default(false).optional(),
});
export type CreateUserSchemaType = z.infer<typeof CreateUserSchema>;

View File

@@ -0,0 +1,9 @@
import { z } from 'zod';
/**
* Schema for resetting a user's password
*/
export const ResetPasswordSchema = z.object({
userId: z.string().uuid(),
confirmation: z.custom<string>((value) => value === 'CONFIRM'),
});

View File

@@ -2,6 +2,8 @@ import 'server-only';
import { SupabaseClient } from '@supabase/supabase-js';
import { z } from 'zod';
import { Database } from '@kit/supabase/database';
export function createAdminAuthUserService(
@@ -155,4 +157,47 @@ class AdminAuthUserService {
ban_duration: banDuration,
});
}
/**
* Reset a user's password by sending a password reset email.
* @param userId
*/
async resetPassword(userId: string) {
await this.assertUserIsNotCurrentSuperAdmin(userId);
const {
data: { user },
error,
} = await this.adminClient.auth.admin.getUserById(userId);
if (error ?? !user) {
throw new Error(`Error fetching user`);
}
const email = user.email;
if (!email) {
throw new Error(`User has no email. Cannot reset password`);
}
// Get the site URL from environment variable
const siteUrl = z.string().url().parse(process.env.NEXT_PUBLIC_SITE_URL);
const redirectTo = `${siteUrl}/update-password`;
const { error: resetError } =
await this.adminClient.auth.resetPasswordForEmail(email, {
redirectTo,
});
if (resetError) {
throw new Error(
`Error sending password reset email: ${resetError.message}`,
);
}
return {
success: true,
};
}
}