@@ -16,6 +16,8 @@ import {
1616 UserData ,
1717 getDefaultUsersData ,
1818 User ,
19+ PublicUser ,
20+ PublicUserData ,
1921 getDefaultWishlistData ,
2022 getDefaultHabitsData ,
2123 getDefaultCoinsData ,
@@ -29,13 +31,14 @@ import { signInSchema } from '@/lib/zod';
2931import _ from 'lodash' ;
3032import { getCurrentUser } from '@/lib/server-helpers'
3133import { prepareDataForHashing , generateCryptoHash } from '@/lib/utils' ;
34+ import { sanitizeUserData } from '@/lib/user-sanitizer'
35+ import { ALLOWED_AVATAR_EXTENSIONS , ALLOWED_AVATAR_MIME_TYPES } from '@/lib/avatar'
3236
3337
3438
3539type ResourceType = 'habit' | 'wishlist' | 'coins'
3640type ActionType = 'write' | 'interact'
3741
38-
3942async function verifyPermission (
4043 resource : ResourceType ,
4144 action : ActionType
@@ -66,22 +69,28 @@ async function ensureDataDir() {
6669
6770// --- Backup Debug Action ---
6871export async function triggerManualBackup ( ) : Promise < { success : boolean ; message : string } > {
69- // Optional: Add extra permission check if needed for debug actions
70- // const user = await getCurrentUser();
71- // if (!user?.isAdmin) {
72- // return { success: false, message: "Permission denied." };
73- // }
72+ if ( process . env . NODE_ENV !== 'development' ) {
73+ return { success : false , message : 'Permission denied.' }
74+ }
75+
76+ const user = await getCurrentUser ( )
77+ if ( ! user ?. isAdmin ) {
78+ return { success : false , message : 'Permission denied.' }
79+ }
7480
75- console . log ( " Manual backup trigger requested..." ) ;
81+ console . log ( ' Manual backup trigger requested...' )
7682 try {
7783 // Import runBackup locally to avoid potential circular dependencies if moved
78- const { runBackup } = await import ( '@/lib/backup' ) ;
79- await runBackup ( ) ;
80- console . log ( " Manual backup trigger completed successfully." ) ;
81- return { success : true , message : " Backup process completed successfully." } ;
84+ const { runBackup } = await import ( '@/lib/backup' )
85+ await runBackup ( )
86+ console . log ( ' Manual backup trigger completed successfully.' )
87+ return { success : true , message : ' Backup process completed successfully.' }
8288 } catch ( error ) {
83- console . error ( "Manual backup trigger failed:" , error ) ;
84- return { success : false , message : `Backup failed: ${ error instanceof Error ? error . message : 'Unknown error' } ` } ;
89+ console . error ( 'Manual backup trigger failed:' , error )
90+ return {
91+ success : false ,
92+ message : `Backup failed: ${ error instanceof Error ? error . message : 'Unknown error' } ` ,
93+ }
8594 }
8695}
8796
@@ -133,7 +142,7 @@ async function calculateServerFreshnessToken(): Promise<string | null> {
133142 const habits = await loadHabitsData ( ) ;
134143 const coins = await loadCoinsData ( ) ;
135144 const wishlist = await loadWishlistData ( ) ;
136- const users = await loadUsersData ( ) ;
145+ const users = await loadUsersPublicData ( ) ;
137146
138147 const dataString = prepareDataForHashing (
139148 settings ,
@@ -362,13 +371,22 @@ export async function uploadAvatar(formData: FormData): Promise<string> {
362371 throw new Error ( 'File size must be less than 5MB' )
363372 }
364373
374+ const mimeType = file . type . toLowerCase ( )
375+ if ( ! ALLOWED_AVATAR_MIME_TYPES . has ( mimeType ) ) {
376+ throw new Error ( 'Unsupported avatar MIME type' )
377+ }
378+
379+ const ext = path . extname ( file . name ) . toLowerCase ( )
380+ if ( ! ALLOWED_AVATAR_EXTENSIONS . has ( ext ) ) {
381+ throw new Error ( 'Unsupported avatar file extension' )
382+ }
383+
365384 // Create avatars directory if it doesn't exist
366385 const avatarsDir = path . join ( process . cwd ( ) , 'data' , 'avatars' )
367386 await fs . mkdir ( avatarsDir , { recursive : true } )
368387
369388 // Generate unique filename
370- const ext = file . name . split ( '.' ) . pop ( )
371- const filename = `${ Date . now ( ) } .${ ext } `
389+ const filename = `${ Date . now ( ) } -${ uuid ( ) } ${ ext } `
372390 const filePath = path . join ( avatarsDir , filename )
373391
374392 // Save file
@@ -389,14 +407,19 @@ export async function getChangelog(): Promise<string> {
389407}
390408
391409// user logic
392- export async function loadUsersData ( ) : Promise < UserData > {
410+ async function loadUsersData ( ) : Promise < UserData > {
393411 try {
394412 return await loadData < UserData > ( 'auth' )
395413 } catch {
396414 return getDefaultUsersData ( )
397415 }
398416}
399417
418+ export async function loadUsersPublicData ( ) : Promise < PublicUserData > {
419+ const data = await loadUsersData ( )
420+ return sanitizeUserData ( data )
421+ }
422+
400423export async function saveUsersData ( data : UserData ) : Promise < void > {
401424 return saveData ( 'auth' , data )
402425}
@@ -414,7 +437,7 @@ export async function getUser(username: string, plainTextPassword?: string): Pro
414437 return user
415438}
416439
417- export async function createUser ( formData : FormData ) : Promise < User > {
440+ export async function createUser ( formData : FormData ) : Promise < PublicUser > {
418441 const username = formData . get ( 'username' ) as string ;
419442 let password = formData . get ( 'password' ) as string | undefined ;
420443 const avatarPath = formData . get ( 'avatarPath' ) as string ;
@@ -451,10 +474,10 @@ export async function createUser(formData: FormData): Promise<User> {
451474 } ;
452475
453476 await saveUsersData ( newData ) ;
454- return newUser ;
477+ return sanitizeUserData ( { users : [ newUser ] } ) . users [ 0 ]
455478}
456479
457- export async function updateUser ( userId : string , updates : Partial < Omit < User , 'id' | 'password' > > ) : Promise < User > {
480+ export async function updateUser ( userId : string , updates : Partial < Omit < User , 'id' | 'password' > > ) : Promise < PublicUser > {
458481 const data = await loadUsersData ( )
459482 const userIndex = data . users . findIndex ( user => user . id === userId )
460483
@@ -486,7 +509,7 @@ export async function updateUser(userId: string, updates: Partial<Omit<User, 'id
486509 }
487510
488511 await saveUsersData ( newData )
489- return updatedUser
512+ return sanitizeUserData ( { users : [ updatedUser ] } ) . users [ 0 ]
490513}
491514
492515export async function updateUserPassword ( userId : string , newPassword ?: string ) : Promise < void > {
0 commit comments