chore: migrate user identity from auth0_sub to UUID #219
@@ -126,7 +126,7 @@ export class AuditLogRepository {
|
||||
al.resource_type, al.resource_id, al.details, al.created_at,
|
||||
up.email as user_email
|
||||
FROM audit_logs al
|
||||
LEFT JOIN user_profiles up ON al.user_id = up.auth0_sub
|
||||
LEFT JOIN user_profiles up ON al.user_id = up.id
|
||||
${whereClause}
|
||||
ORDER BY al.created_at DESC
|
||||
LIMIT $${nextParamIndex} OFFSET $${nextParamIndex + 1}
|
||||
@@ -170,7 +170,7 @@ export class AuditLogRepository {
|
||||
al.resource_type, al.resource_id, al.details, al.created_at,
|
||||
up.email as user_email
|
||||
FROM audit_logs al
|
||||
LEFT JOIN user_profiles up ON al.user_id = up.auth0_sub
|
||||
LEFT JOIN user_profiles up ON al.user_id = up.id
|
||||
${whereClause}
|
||||
ORDER BY al.created_at DESC
|
||||
LIMIT ${MAX_EXPORT_RECORDS}
|
||||
|
||||
@@ -45,12 +45,12 @@ export class BackupController {
|
||||
request: FastifyRequest<{ Body: CreateBackupBody }>,
|
||||
reply: FastifyReply
|
||||
): Promise<void> {
|
||||
const adminSub = (request as any).userContext?.auth0Sub;
|
||||
const adminUserId = request.userContext?.userId;
|
||||
|
||||
const result = await this.backupService.createBackup({
|
||||
name: request.body.name,
|
||||
backupType: 'manual',
|
||||
createdBy: adminSub,
|
||||
createdBy: adminUserId,
|
||||
includeDocuments: request.body.includeDocuments,
|
||||
});
|
||||
|
||||
@@ -58,7 +58,7 @@ export class BackupController {
|
||||
// Log backup creation to unified audit log
|
||||
await auditLogService.info(
|
||||
'system',
|
||||
adminSub || null,
|
||||
adminUserId || null,
|
||||
`Backup created: ${request.body.name || 'Manual backup'}`,
|
||||
'backup',
|
||||
result.backupId,
|
||||
@@ -74,7 +74,7 @@ export class BackupController {
|
||||
// Log backup failure
|
||||
await auditLogService.error(
|
||||
'system',
|
||||
adminSub || null,
|
||||
adminUserId || null,
|
||||
`Backup failed: ${request.body.name || 'Manual backup'}`,
|
||||
'backup',
|
||||
result.backupId,
|
||||
@@ -139,7 +139,7 @@ export class BackupController {
|
||||
request: FastifyRequest,
|
||||
reply: FastifyReply
|
||||
): Promise<void> {
|
||||
const adminSub = (request as any).userContext?.auth0Sub;
|
||||
const adminUserId = request.userContext?.userId;
|
||||
|
||||
// Handle multipart file upload
|
||||
const data = await request.file();
|
||||
@@ -173,7 +173,7 @@ export class BackupController {
|
||||
const backup = await this.backupService.importUploadedBackup(
|
||||
tempPath,
|
||||
filename,
|
||||
adminSub
|
||||
adminUserId
|
||||
);
|
||||
|
||||
reply.status(201).send({
|
||||
@@ -217,7 +217,7 @@ export class BackupController {
|
||||
request: FastifyRequest<{ Params: BackupIdParam; Body: RestoreBody }>,
|
||||
reply: FastifyReply
|
||||
): Promise<void> {
|
||||
const adminSub = (request as any).userContext?.auth0Sub;
|
||||
const adminUserId = request.userContext?.userId;
|
||||
|
||||
try {
|
||||
const result = await this.restoreService.executeRestore({
|
||||
@@ -229,7 +229,7 @@ export class BackupController {
|
||||
// Log successful restore to unified audit log
|
||||
await auditLogService.info(
|
||||
'system',
|
||||
adminSub || null,
|
||||
adminUserId || null,
|
||||
`Backup restored: ${request.params.id}`,
|
||||
'backup',
|
||||
request.params.id,
|
||||
@@ -246,7 +246,7 @@ export class BackupController {
|
||||
// Log restore failure
|
||||
await auditLogService.error(
|
||||
'system',
|
||||
adminSub || null,
|
||||
adminUserId || null,
|
||||
`Backup restore failed: ${request.params.id}`,
|
||||
'backup',
|
||||
request.params.id,
|
||||
|
||||
@@ -33,7 +33,7 @@ export class OcrController {
|
||||
request: FastifyRequest<{ Querystring: ExtractQuery }>,
|
||||
reply: FastifyReply
|
||||
) {
|
||||
const userId = (request as any).user?.sub as string;
|
||||
const userId = request.userContext?.userId as string;
|
||||
const preprocess = request.query.preprocess !== false;
|
||||
|
||||
logger.info('OCR extract requested', {
|
||||
@@ -140,7 +140,7 @@ export class OcrController {
|
||||
request: FastifyRequest,
|
||||
reply: FastifyReply
|
||||
) {
|
||||
const userId = (request as any).user?.sub as string;
|
||||
const userId = request.userContext?.userId as string;
|
||||
|
||||
logger.info('VIN extract requested', {
|
||||
operation: 'ocr.controller.extractVin',
|
||||
@@ -240,7 +240,7 @@ export class OcrController {
|
||||
request: FastifyRequest,
|
||||
reply: FastifyReply
|
||||
) {
|
||||
const userId = (request as any).user?.sub as string;
|
||||
const userId = request.userContext?.userId as string;
|
||||
|
||||
logger.info('Receipt extract requested', {
|
||||
operation: 'ocr.controller.extractReceipt',
|
||||
@@ -352,7 +352,7 @@ export class OcrController {
|
||||
request: FastifyRequest,
|
||||
reply: FastifyReply
|
||||
) {
|
||||
const userId = (request as any).user?.sub as string;
|
||||
const userId = request.userContext?.userId as string;
|
||||
|
||||
logger.info('Maintenance receipt extract requested', {
|
||||
operation: 'ocr.controller.extractMaintenanceReceipt',
|
||||
@@ -460,7 +460,7 @@ export class OcrController {
|
||||
request: FastifyRequest,
|
||||
reply: FastifyReply
|
||||
) {
|
||||
const userId = (request as any).user?.sub as string;
|
||||
const userId = request.userContext?.userId as string;
|
||||
|
||||
logger.info('Manual extract requested', {
|
||||
operation: 'ocr.controller.extractManual',
|
||||
@@ -584,7 +584,7 @@ export class OcrController {
|
||||
request: FastifyRequest<{ Body: JobSubmitBody }>,
|
||||
reply: FastifyReply
|
||||
) {
|
||||
const userId = (request as any).user?.sub as string;
|
||||
const userId = request.userContext?.userId as string;
|
||||
|
||||
logger.info('OCR job submit requested', {
|
||||
operation: 'ocr.controller.submitJob',
|
||||
@@ -691,7 +691,7 @@ export class OcrController {
|
||||
request: FastifyRequest<{ Params: JobIdParams }>,
|
||||
reply: FastifyReply
|
||||
) {
|
||||
const userId = (request as any).user?.sub as string;
|
||||
const userId = request.userContext?.userId as string;
|
||||
const { jobId } = request.params;
|
||||
|
||||
logger.debug('OCR job status requested', {
|
||||
|
||||
@@ -18,11 +18,12 @@ import {
|
||||
|
||||
export class UserProfileController {
|
||||
private userProfileService: UserProfileService;
|
||||
private userProfileRepository: UserProfileRepository;
|
||||
|
||||
constructor() {
|
||||
const repository = new UserProfileRepository(pool);
|
||||
this.userProfileRepository = new UserProfileRepository(pool);
|
||||
const adminRepository = new AdminRepository(pool);
|
||||
this.userProfileService = new UserProfileService(repository);
|
||||
this.userProfileService = new UserProfileService(this.userProfileRepository);
|
||||
this.userProfileService.setAdminRepository(adminRepository);
|
||||
}
|
||||
|
||||
@@ -31,27 +32,24 @@ export class UserProfileController {
|
||||
*/
|
||||
async getProfile(request: FastifyRequest, reply: FastifyReply) {
|
||||
try {
|
||||
const auth0Sub = request.userContext?.userId;
|
||||
const userId = request.userContext?.userId;
|
||||
|
||||
if (!auth0Sub) {
|
||||
if (!userId) {
|
||||
return reply.code(401).send({
|
||||
error: 'Unauthorized',
|
||||
message: 'User context missing',
|
||||
});
|
||||
}
|
||||
|
||||
// Get user data from Auth0 token
|
||||
const auth0User = {
|
||||
sub: auth0Sub,
|
||||
email: (request as any).user?.email || request.userContext?.email || '',
|
||||
name: (request as any).user?.name,
|
||||
};
|
||||
// Get profile by UUID (auth plugin ensures profile exists during authentication)
|
||||
const profile = await this.userProfileRepository.getById(userId);
|
||||
|
||||
// Get or create profile
|
||||
const profile = await this.userProfileService.getOrCreateProfile(
|
||||
auth0Sub,
|
||||
auth0User
|
||||
);
|
||||
if (!profile) {
|
||||
return reply.code(404).send({
|
||||
error: 'Not Found',
|
||||
message: 'User profile not found',
|
||||
});
|
||||
}
|
||||
|
||||
return reply.code(200).send(profile);
|
||||
} catch (error: any) {
|
||||
@@ -75,9 +73,9 @@ export class UserProfileController {
|
||||
reply: FastifyReply
|
||||
) {
|
||||
try {
|
||||
const auth0Sub = request.userContext?.userId;
|
||||
const userId = request.userContext?.userId;
|
||||
|
||||
if (!auth0Sub) {
|
||||
if (!userId) {
|
||||
return reply.code(401).send({
|
||||
error: 'Unauthorized',
|
||||
message: 'User context missing',
|
||||
@@ -96,9 +94,9 @@ export class UserProfileController {
|
||||
|
||||
const updates = validation.data;
|
||||
|
||||
// Update profile
|
||||
// Update profile by UUID
|
||||
const profile = await this.userProfileService.updateProfile(
|
||||
auth0Sub,
|
||||
userId,
|
||||
updates
|
||||
);
|
||||
|
||||
@@ -138,9 +136,9 @@ export class UserProfileController {
|
||||
reply: FastifyReply
|
||||
) {
|
||||
try {
|
||||
const auth0Sub = request.userContext?.userId;
|
||||
const userId = request.userContext?.userId;
|
||||
|
||||
if (!auth0Sub) {
|
||||
if (!userId) {
|
||||
return reply.code(401).send({
|
||||
error: 'Unauthorized',
|
||||
message: 'User context missing',
|
||||
@@ -159,9 +157,9 @@ export class UserProfileController {
|
||||
|
||||
const { confirmationText } = validation.data;
|
||||
|
||||
// Request deletion (user is already authenticated via JWT)
|
||||
// Request deletion by UUID
|
||||
const profile = await this.userProfileService.requestDeletion(
|
||||
auth0Sub,
|
||||
userId,
|
||||
confirmationText
|
||||
);
|
||||
|
||||
@@ -210,17 +208,17 @@ export class UserProfileController {
|
||||
*/
|
||||
async cancelDeletion(request: FastifyRequest, reply: FastifyReply) {
|
||||
try {
|
||||
const auth0Sub = request.userContext?.userId;
|
||||
const userId = request.userContext?.userId;
|
||||
|
||||
if (!auth0Sub) {
|
||||
if (!userId) {
|
||||
return reply.code(401).send({
|
||||
error: 'Unauthorized',
|
||||
message: 'User context missing',
|
||||
});
|
||||
}
|
||||
|
||||
// Cancel deletion
|
||||
const profile = await this.userProfileService.cancelDeletion(auth0Sub);
|
||||
// Cancel deletion by UUID
|
||||
const profile = await this.userProfileService.cancelDeletion(userId);
|
||||
|
||||
return reply.code(200).send({
|
||||
message: 'Account deletion canceled successfully',
|
||||
@@ -258,27 +256,24 @@ export class UserProfileController {
|
||||
*/
|
||||
async getDeletionStatus(request: FastifyRequest, reply: FastifyReply) {
|
||||
try {
|
||||
const auth0Sub = request.userContext?.userId;
|
||||
const userId = request.userContext?.userId;
|
||||
|
||||
if (!auth0Sub) {
|
||||
if (!userId) {
|
||||
return reply.code(401).send({
|
||||
error: 'Unauthorized',
|
||||
message: 'User context missing',
|
||||
});
|
||||
}
|
||||
|
||||
// Get user data from Auth0 token
|
||||
const auth0User = {
|
||||
sub: auth0Sub,
|
||||
email: (request as any).user?.email || request.userContext?.email || '',
|
||||
name: (request as any).user?.name,
|
||||
};
|
||||
// Get profile by UUID (auth plugin ensures profile exists)
|
||||
const profile = await this.userProfileRepository.getById(userId);
|
||||
|
||||
// Get or create profile
|
||||
const profile = await this.userProfileService.getOrCreateProfile(
|
||||
auth0Sub,
|
||||
auth0User
|
||||
);
|
||||
if (!profile) {
|
||||
return reply.code(404).send({
|
||||
error: 'Not Found',
|
||||
message: 'User profile not found',
|
||||
});
|
||||
}
|
||||
|
||||
const deletionStatus = this.userProfileService.getDeletionStatus(profile);
|
||||
|
||||
|
||||
@@ -194,7 +194,7 @@ export class UserProfileRepository {
|
||||
private mapRowToUserWithAdminStatus(row: any): UserWithAdminStatus {
|
||||
return {
|
||||
...this.mapRowToUserProfile(row),
|
||||
isAdmin: !!row.admin_auth0_sub,
|
||||
isAdmin: !!row.admin_id,
|
||||
adminRole: row.admin_role || null,
|
||||
vehicleCount: parseInt(row.vehicle_count, 10) || 0,
|
||||
};
|
||||
@@ -262,7 +262,7 @@ export class UserProfileRepository {
|
||||
up.id, up.auth0_sub, up.email, up.display_name, up.notification_email,
|
||||
up.subscription_tier, up.email_verified, up.onboarding_completed_at,
|
||||
up.deactivated_at, up.deactivated_by, up.created_at, up.updated_at,
|
||||
au.auth0_sub as admin_auth0_sub,
|
||||
au.id as admin_id,
|
||||
au.role as admin_role,
|
||||
(SELECT COUNT(*) FROM vehicles v
|
||||
WHERE v.user_id = up.id
|
||||
@@ -300,7 +300,7 @@ export class UserProfileRepository {
|
||||
up.id, up.auth0_sub, up.email, up.display_name, up.notification_email,
|
||||
up.subscription_tier, up.email_verified, up.onboarding_completed_at,
|
||||
up.deactivated_at, up.deactivated_by, up.created_at, up.updated_at,
|
||||
au.auth0_sub as admin_auth0_sub,
|
||||
au.id as admin_id,
|
||||
au.role as admin_role,
|
||||
(SELECT COUNT(*) FROM vehicles v
|
||||
WHERE v.user_id = up.id
|
||||
|
||||
@@ -60,7 +60,7 @@ export class UserProfileService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user profile by Auth0 sub
|
||||
* Get user profile by Auth0 sub (used during auth flow)
|
||||
*/
|
||||
async getProfile(auth0Sub: string): Promise<UserProfile | null> {
|
||||
try {
|
||||
@@ -72,10 +72,10 @@ export class UserProfileService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Update user profile
|
||||
* Update user profile by UUID
|
||||
*/
|
||||
async updateProfile(
|
||||
auth0Sub: string,
|
||||
userId: string,
|
||||
updates: UpdateProfileRequest
|
||||
): Promise<UserProfile> {
|
||||
try {
|
||||
@@ -85,17 +85,17 @@ export class UserProfileService {
|
||||
}
|
||||
|
||||
// Perform the update
|
||||
const profile = await this.repository.update(auth0Sub, updates);
|
||||
const profile = await this.repository.update(userId, updates);
|
||||
|
||||
logger.info('User profile updated', {
|
||||
auth0Sub,
|
||||
userId,
|
||||
profileId: profile.id,
|
||||
updatedFields: Object.keys(updates),
|
||||
});
|
||||
|
||||
return profile;
|
||||
} catch (error) {
|
||||
logger.error('Error updating user profile', { error, auth0Sub, updates });
|
||||
logger.error('Error updating user profile', { error, userId, updates });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -117,29 +117,29 @@ export class UserProfileService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user details with admin status (admin-only)
|
||||
* Get user details with admin status by UUID (admin-only)
|
||||
*/
|
||||
async getUserDetails(auth0Sub: string): Promise<UserWithAdminStatus | null> {
|
||||
async getUserDetails(userId: string): Promise<UserWithAdminStatus | null> {
|
||||
try {
|
||||
return await this.repository.getUserWithAdminStatus(auth0Sub);
|
||||
return await this.repository.getUserWithAdminStatus(userId);
|
||||
} catch (error) {
|
||||
logger.error('Error getting user details', { error, auth0Sub });
|
||||
logger.error('Error getting user details', { error, userId });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update user subscription tier (admin-only)
|
||||
* Update user subscription tier by UUID (admin-only)
|
||||
* Logs the change to admin audit logs
|
||||
*/
|
||||
async updateSubscriptionTier(
|
||||
auth0Sub: string,
|
||||
userId: string,
|
||||
tier: SubscriptionTier,
|
||||
actorAuth0Sub: string
|
||||
actorUserId: string
|
||||
): Promise<UserProfile> {
|
||||
try {
|
||||
// Get current user to log the change
|
||||
const currentUser = await this.repository.getByAuth0Sub(auth0Sub);
|
||||
const currentUser = await this.repository.getById(userId);
|
||||
if (!currentUser) {
|
||||
throw new Error('User not found');
|
||||
}
|
||||
@@ -147,14 +147,14 @@ export class UserProfileService {
|
||||
const previousTier = currentUser.subscriptionTier;
|
||||
|
||||
// Perform the update
|
||||
const updatedProfile = await this.repository.updateSubscriptionTier(auth0Sub, tier);
|
||||
const updatedProfile = await this.repository.updateSubscriptionTier(userId, tier);
|
||||
|
||||
// Log to audit trail
|
||||
if (this.adminRepository) {
|
||||
await this.adminRepository.logAuditAction(
|
||||
actorAuth0Sub,
|
||||
actorUserId,
|
||||
'UPDATE_TIER',
|
||||
auth0Sub,
|
||||
userId,
|
||||
'user_profile',
|
||||
updatedProfile.id,
|
||||
{ previousTier, newTier: tier }
|
||||
@@ -162,36 +162,36 @@ export class UserProfileService {
|
||||
}
|
||||
|
||||
logger.info('User subscription tier updated', {
|
||||
auth0Sub,
|
||||
userId,
|
||||
previousTier,
|
||||
newTier: tier,
|
||||
actorAuth0Sub,
|
||||
actorUserId,
|
||||
});
|
||||
|
||||
return updatedProfile;
|
||||
} catch (error) {
|
||||
logger.error('Error updating subscription tier', { error, auth0Sub, tier, actorAuth0Sub });
|
||||
logger.error('Error updating subscription tier', { error, userId, tier, actorUserId });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deactivate user account (admin-only soft delete)
|
||||
* Deactivate user account by UUID (admin-only soft delete)
|
||||
* Prevents self-deactivation
|
||||
*/
|
||||
async deactivateUser(
|
||||
auth0Sub: string,
|
||||
actorAuth0Sub: string,
|
||||
userId: string,
|
||||
actorUserId: string,
|
||||
reason?: string
|
||||
): Promise<UserProfile> {
|
||||
try {
|
||||
// Prevent self-deactivation
|
||||
if (auth0Sub === actorAuth0Sub) {
|
||||
if (userId === actorUserId) {
|
||||
throw new Error('Cannot deactivate your own account');
|
||||
}
|
||||
|
||||
// Verify user exists and is not already deactivated
|
||||
const currentUser = await this.repository.getByAuth0Sub(auth0Sub);
|
||||
const currentUser = await this.repository.getById(userId);
|
||||
if (!currentUser) {
|
||||
throw new Error('User not found');
|
||||
}
|
||||
@@ -200,14 +200,14 @@ export class UserProfileService {
|
||||
}
|
||||
|
||||
// Perform the deactivation
|
||||
const deactivatedProfile = await this.repository.deactivateUser(auth0Sub, actorAuth0Sub);
|
||||
const deactivatedProfile = await this.repository.deactivateUser(userId, actorUserId);
|
||||
|
||||
// Log to audit trail
|
||||
if (this.adminRepository) {
|
||||
await this.adminRepository.logAuditAction(
|
||||
actorAuth0Sub,
|
||||
actorUserId,
|
||||
'DEACTIVATE_USER',
|
||||
auth0Sub,
|
||||
userId,
|
||||
'user_profile',
|
||||
deactivatedProfile.id,
|
||||
{ reason: reason || 'No reason provided' }
|
||||
@@ -215,28 +215,28 @@ export class UserProfileService {
|
||||
}
|
||||
|
||||
logger.info('User deactivated', {
|
||||
auth0Sub,
|
||||
actorAuth0Sub,
|
||||
userId,
|
||||
actorUserId,
|
||||
reason,
|
||||
});
|
||||
|
||||
return deactivatedProfile;
|
||||
} catch (error) {
|
||||
logger.error('Error deactivating user', { error, auth0Sub, actorAuth0Sub });
|
||||
logger.error('Error deactivating user', { error, userId, actorUserId });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reactivate a deactivated user account (admin-only)
|
||||
* Reactivate a deactivated user account by UUID (admin-only)
|
||||
*/
|
||||
async reactivateUser(
|
||||
auth0Sub: string,
|
||||
actorAuth0Sub: string
|
||||
userId: string,
|
||||
actorUserId: string
|
||||
): Promise<UserProfile> {
|
||||
try {
|
||||
// Verify user exists and is deactivated
|
||||
const currentUser = await this.repository.getByAuth0Sub(auth0Sub);
|
||||
const currentUser = await this.repository.getById(userId);
|
||||
if (!currentUser) {
|
||||
throw new Error('User not found');
|
||||
}
|
||||
@@ -245,14 +245,14 @@ export class UserProfileService {
|
||||
}
|
||||
|
||||
// Perform the reactivation
|
||||
const reactivatedProfile = await this.repository.reactivateUser(auth0Sub);
|
||||
const reactivatedProfile = await this.repository.reactivateUser(userId);
|
||||
|
||||
// Log to audit trail
|
||||
if (this.adminRepository) {
|
||||
await this.adminRepository.logAuditAction(
|
||||
actorAuth0Sub,
|
||||
actorUserId,
|
||||
'REACTIVATE_USER',
|
||||
auth0Sub,
|
||||
userId,
|
||||
'user_profile',
|
||||
reactivatedProfile.id,
|
||||
{ previouslyDeactivatedBy: currentUser.deactivatedBy }
|
||||
@@ -260,29 +260,29 @@ export class UserProfileService {
|
||||
}
|
||||
|
||||
logger.info('User reactivated', {
|
||||
auth0Sub,
|
||||
actorAuth0Sub,
|
||||
userId,
|
||||
actorUserId,
|
||||
});
|
||||
|
||||
return reactivatedProfile;
|
||||
} catch (error) {
|
||||
logger.error('Error reactivating user', { error, auth0Sub, actorAuth0Sub });
|
||||
logger.error('Error reactivating user', { error, userId, actorUserId });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Admin update of user profile (email, displayName)
|
||||
* Admin update of user profile by UUID (email, displayName)
|
||||
* Logs the change to admin audit logs
|
||||
*/
|
||||
async adminUpdateProfile(
|
||||
auth0Sub: string,
|
||||
userId: string,
|
||||
updates: { email?: string; displayName?: string },
|
||||
actorAuth0Sub: string
|
||||
actorUserId: string
|
||||
): Promise<UserProfile> {
|
||||
try {
|
||||
// Get current user to log the change
|
||||
const currentUser = await this.repository.getByAuth0Sub(auth0Sub);
|
||||
const currentUser = await this.repository.getById(userId);
|
||||
if (!currentUser) {
|
||||
throw new Error('User not found');
|
||||
}
|
||||
@@ -293,14 +293,14 @@ export class UserProfileService {
|
||||
};
|
||||
|
||||
// Perform the update
|
||||
const updatedProfile = await this.repository.adminUpdateProfile(auth0Sub, updates);
|
||||
const updatedProfile = await this.repository.adminUpdateProfile(userId, updates);
|
||||
|
||||
// Log to audit trail
|
||||
if (this.adminRepository) {
|
||||
await this.adminRepository.logAuditAction(
|
||||
actorAuth0Sub,
|
||||
actorUserId,
|
||||
'UPDATE_PROFILE',
|
||||
auth0Sub,
|
||||
userId,
|
||||
'user_profile',
|
||||
updatedProfile.id,
|
||||
{
|
||||
@@ -311,14 +311,14 @@ export class UserProfileService {
|
||||
}
|
||||
|
||||
logger.info('User profile updated by admin', {
|
||||
auth0Sub,
|
||||
userId,
|
||||
updatedFields: Object.keys(updates),
|
||||
actorAuth0Sub,
|
||||
actorUserId,
|
||||
});
|
||||
|
||||
return updatedProfile;
|
||||
} catch (error) {
|
||||
logger.error('Error admin updating user profile', { error, auth0Sub, updates, actorAuth0Sub });
|
||||
logger.error('Error admin updating user profile', { error, userId, updates, actorUserId });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -328,12 +328,12 @@ export class UserProfileService {
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* Request account deletion
|
||||
* Request account deletion by UUID
|
||||
* Sets 30-day grace period before permanent deletion
|
||||
* Note: User is already authenticated via JWT, confirmation text is sufficient
|
||||
*/
|
||||
async requestDeletion(
|
||||
auth0Sub: string,
|
||||
userId: string,
|
||||
confirmationText: string
|
||||
): Promise<UserProfile> {
|
||||
try {
|
||||
@@ -343,7 +343,7 @@ export class UserProfileService {
|
||||
}
|
||||
|
||||
// Get user profile
|
||||
const profile = await this.repository.getByAuth0Sub(auth0Sub);
|
||||
const profile = await this.repository.getById(userId);
|
||||
if (!profile) {
|
||||
throw new Error('User not found');
|
||||
}
|
||||
@@ -354,14 +354,14 @@ export class UserProfileService {
|
||||
}
|
||||
|
||||
// Request deletion
|
||||
const updatedProfile = await this.repository.requestDeletion(auth0Sub);
|
||||
const updatedProfile = await this.repository.requestDeletion(userId);
|
||||
|
||||
// Log to audit trail
|
||||
if (this.adminRepository) {
|
||||
await this.adminRepository.logAuditAction(
|
||||
auth0Sub,
|
||||
userId,
|
||||
'REQUEST_DELETION',
|
||||
auth0Sub,
|
||||
userId,
|
||||
'user_profile',
|
||||
updatedProfile.id,
|
||||
{
|
||||
@@ -371,42 +371,42 @@ export class UserProfileService {
|
||||
}
|
||||
|
||||
logger.info('Account deletion requested', {
|
||||
auth0Sub,
|
||||
userId,
|
||||
deletionScheduledFor: updatedProfile.deletionScheduledFor,
|
||||
});
|
||||
|
||||
return updatedProfile;
|
||||
} catch (error) {
|
||||
logger.error('Error requesting account deletion', { error, auth0Sub });
|
||||
logger.error('Error requesting account deletion', { error, userId });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel pending deletion request
|
||||
* Cancel pending deletion request by UUID
|
||||
*/
|
||||
async cancelDeletion(auth0Sub: string): Promise<UserProfile> {
|
||||
async cancelDeletion(userId: string): Promise<UserProfile> {
|
||||
try {
|
||||
// Cancel deletion
|
||||
const updatedProfile = await this.repository.cancelDeletion(auth0Sub);
|
||||
const updatedProfile = await this.repository.cancelDeletion(userId);
|
||||
|
||||
// Log to audit trail
|
||||
if (this.adminRepository) {
|
||||
await this.adminRepository.logAuditAction(
|
||||
auth0Sub,
|
||||
userId,
|
||||
'CANCEL_DELETION',
|
||||
auth0Sub,
|
||||
userId,
|
||||
'user_profile',
|
||||
updatedProfile.id,
|
||||
{}
|
||||
);
|
||||
}
|
||||
|
||||
logger.info('Account deletion canceled', { auth0Sub });
|
||||
logger.info('Account deletion canceled', { userId });
|
||||
|
||||
return updatedProfile;
|
||||
} catch (error) {
|
||||
logger.error('Error canceling account deletion', { error, auth0Sub });
|
||||
logger.error('Error canceling account deletion', { error, userId });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -438,22 +438,22 @@ export class UserProfileService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Admin hard delete user (permanent deletion)
|
||||
* Admin hard delete user by UUID (permanent deletion)
|
||||
* Prevents self-delete
|
||||
*/
|
||||
async adminHardDeleteUser(
|
||||
auth0Sub: string,
|
||||
actorAuth0Sub: string,
|
||||
userId: string,
|
||||
actorUserId: string,
|
||||
reason?: string
|
||||
): Promise<void> {
|
||||
try {
|
||||
// Prevent self-delete
|
||||
if (auth0Sub === actorAuth0Sub) {
|
||||
if (userId === actorUserId) {
|
||||
throw new Error('Cannot delete your own account');
|
||||
}
|
||||
|
||||
// Get user profile before deletion for audit log
|
||||
const profile = await this.repository.getByAuth0Sub(auth0Sub);
|
||||
const profile = await this.repository.getById(userId);
|
||||
if (!profile) {
|
||||
throw new Error('User not found');
|
||||
}
|
||||
@@ -461,9 +461,9 @@ export class UserProfileService {
|
||||
// Log to audit trail before deletion
|
||||
if (this.adminRepository) {
|
||||
await this.adminRepository.logAuditAction(
|
||||
actorAuth0Sub,
|
||||
actorUserId,
|
||||
'HARD_DELETE_USER',
|
||||
auth0Sub,
|
||||
userId,
|
||||
'user_profile',
|
||||
profile.id,
|
||||
{
|
||||
@@ -475,18 +475,20 @@ export class UserProfileService {
|
||||
}
|
||||
|
||||
// Hard delete from database
|
||||
await this.repository.hardDeleteUser(auth0Sub);
|
||||
await this.repository.hardDeleteUser(userId);
|
||||
|
||||
// Delete from Auth0
|
||||
await auth0ManagementClient.deleteUser(auth0Sub);
|
||||
// Delete from Auth0 (using auth0Sub for Auth0 API)
|
||||
if (profile.auth0Sub) {
|
||||
await auth0ManagementClient.deleteUser(profile.auth0Sub);
|
||||
}
|
||||
|
||||
logger.info('User hard deleted by admin', {
|
||||
auth0Sub,
|
||||
actorAuth0Sub,
|
||||
userId,
|
||||
actorUserId,
|
||||
reason,
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Error hard deleting user', { error, auth0Sub, actorAuth0Sub });
|
||||
logger.error('Error hard deleting user', { error, userId, actorUserId });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user