@@ -14,8 +14,16 @@ const { getCompiledModel } = require("../utils/injectModel")
1414const QueryEngine = require ( "../utils/queryEngine" ) ;
1515const { storageRegistry } = require ( "../utils/registry" ) ;
1616const { deleteProjectByApiKeyCache, setProjectById, getProjectById, deleteProjectById } = require ( "../services/redisCaching" ) ;
17+ const { v4 : uuidv4 } = require ( 'uuid' ) ;
1718const { getPublicIp } = require ( "../utils/network" ) ;
1819
20+ const validateUsersSchema = ( schema ) => {
21+ if ( ! Array . isArray ( schema ) ) return false ;
22+ const hasEmail = schema . find ( f => f . key === 'email' && f . type === 'String' && f . required ) ;
23+ const hasPassword = schema . find ( f => f . key === 'password' && f . type === 'String' && f . required ) ;
24+ return ! ! ( hasEmail && hasPassword ) ;
25+ } ;
26+
1927
2028
2129const getBucket = ( project ) =>
@@ -95,20 +103,38 @@ module.exports.getAllProject = async (req, res) => {
95103
96104module . exports . getSingleProject = async ( req , res ) => {
97105 try {
98- let project ;
99- project = await getProjectById ( req . params . projectId ) ;
100- let projectObj ;
101- if ( ! project ) {
102- project = await Project . findOne ( { _id : req . params . projectId , owner : req . user . _id } ) . select ( '-publishableKey -secretKey -jwtSecret' ) ;
106+ let projectObj = await getProjectById ( req . params . projectId ) ;
107+
108+ if ( ! projectObj ) {
109+ const project = await Project . findOne ( { _id : req . params . projectId , owner : req . user . _id } ) . select ( '-publishableKey -secretKey -jwtSecret' ) ;
103110 if ( ! project ) return res . status ( 404 ) . json ( { error : "Project not found." } ) ;
104111 projectObj = project . toObject ( ) ;
105- await setProjectById ( req . params . projectId , project ) ;
112+ await setProjectById ( req . params . projectId , projectObj ) ;
113+ }
114+
115+ // Ownership Check (Even for Cache)
116+ if ( projectObj . owner . toString ( ) !== req . user . _id . toString ( ) ) {
117+ return res . status ( 403 ) . json ( { error : "Access denied." } ) ;
106118 }
107119
108- projectObj = project ;
120+ // Just to be safe, we remove sensitive fields again even if from cache
109121 delete projectObj . publishableKey ;
110122 delete projectObj . secretKey ;
111123 delete projectObj . jwtSecret ;
124+
125+ // SANITIZE COLLECTION MODELS: Remove "password" from "users" collection schema
126+ if ( projectObj . collections && Array . isArray ( projectObj . collections ) ) {
127+ projectObj . collections = projectObj . collections . map ( col => {
128+ if ( col . name === 'users' && col . model ) {
129+ return {
130+ ...col ,
131+ model : col . model . filter ( m => m . key !== 'password' )
132+ } ;
133+ }
134+ return col ;
135+ } ) ;
136+ }
137+
112138 res . json ( projectObj ) ;
113139 } catch ( err ) {
114140 res . status ( 500 ) . json ( { error : err . message } ) ;
@@ -283,6 +309,7 @@ module.exports.deleteExternalStorageConfig = async (req, res) => {
283309
284310
285311
312+ // POST REQ FOR CREATE COLLECTION
286313module . exports . createCollection = async ( req , res ) => {
287314 try {
288315 const { projectId, collectionName, schema } = createCollectionSchema . parse ( req . body ) ;
@@ -295,14 +322,19 @@ module.exports.createCollection = async (req, res) => {
295322
296323 if ( ! project . jwtSecret ) project . jwtSecret = uuidv4 ( ) ;
297324
325+ if ( collectionName === 'users' ) {
326+ if ( ! validateUsersSchema ( schema ) ) {
327+ return res . status ( 422 ) . json ( { error : "The 'users' collection must have required 'email' and 'password' string fields." } ) ;
328+ }
329+ }
330+
298331 project . collections . push ( { name : collectionName , model : schema } ) ;
299332 await project . save ( ) ;
300333
301334 await deleteProjectById ( projectId ) ;
302335 await setProjectById ( projectId , project ) ;
303336 await deleteProjectByApiKeyCache ( project . publishableKey ) ;
304337 await deleteProjectByApiKeyCache ( project . secretKey ) ;
305- // RESPONSE
306338 const projectObj = project . toObject ( ) ;
307339 delete projectObj . publishableKey ;
308340 delete projectObj . secretKey ;
@@ -371,7 +403,12 @@ module.exports.getData = async (req, res) => {
371403
372404 // const collectionsList = await mongoose.connection.db.listCollections({ name: finalCollectionName }).toArray();
373405
374- const features = new QueryEngine ( model . find ( ) , req . query )
406+ const query = model . find ( ) ;
407+ if ( collectionName === 'users' ) {
408+ query . select ( '-password' ) ;
409+ }
410+
411+ const features = new QueryEngine ( query , req . query )
375412 . filter ( )
376413 . sort ( )
377414 . paginate ( ) ;
@@ -391,6 +428,10 @@ module.exports.insertData = async (req, res) => {
391428 const project = await Project . findOne ( { _id : projectId , owner : req . user . _id } ) ;
392429 if ( ! project ) return res . status ( 404 ) . json ( { error : "Project not found." } ) ;
393430
431+ if ( collectionName === 'users' ) {
432+ return res . status ( 400 ) . json ( { error : "Direct inserts into 'users' collection are not allowed. Please use the Auth signup or admin endpoints." } ) ;
433+ }
434+
394435 const finalCollectionName = `${ project . _id } _${ collectionName } ` ;
395436 const incomingData = req . body ;
396437
@@ -419,7 +460,6 @@ module.exports.insertData = async (req, res) => {
419460 project . databaseUsed = ( project . databaseUsed || 0 ) + docSize ;
420461 }
421462 await project . save ( ) ;
422- await project . save ( ) ;
423463
424464 res . json ( result ) ;
425465 } catch ( err ) {
@@ -480,6 +520,14 @@ module.exports.editRow = async (req, res) => {
480520 const connection = await getConnection ( projectId ) ;
481521 const Model = getCompiledModel ( connection , collectionConfig , projectId , project . resources . db . isExternal ) ;
482522
523+ if ( collectionName === 'users' ) {
524+ delete req . body . password ;
525+ // Also ensure it's not and nested or sneaky
526+ Object . keys ( req . body ) . forEach ( key => {
527+ if ( key . toLowerCase ( ) . includes ( 'password' ) ) delete req . body [ key ] ;
528+ } ) ;
529+ }
530+
483531 const docToEdit = await Model . findById ( id ) ;
484532 if ( ! docToEdit ) {
485533 return res . status ( 404 ) . json ( { error : "Document not found." } ) ;
@@ -505,8 +553,12 @@ module.exports.editRow = async (req, res) => {
505553 }
506554
507555 const updatedDoc = await docToEdit . save ( ) ;
556+ const responseData = updatedDoc . toObject ( ) ;
557+ if ( collectionName === 'users' ) {
558+ delete responseData . password ;
559+ }
508560
509- res . json ( { success : true , message : "Document edited successfully" , data : updatedDoc } ) ;
561+ res . json ( { success : true , message : "Document edited successfully" , data : responseData } ) ;
510562
511563 } catch ( err ) {
512564 console . error ( "Edit Error:" , err ) ;
@@ -754,35 +806,41 @@ module.exports.deleteProject = async (req, res) => {
754806 if ( ! project ) {
755807 return res . status ( 404 ) . json ( { error : "Project not found or access denied." } ) ;
756808 }
757- for ( const col of project . collections ) {
758- const collectionName = `${ project . _id } _${ col . name } ` ;
809+
810+ // DROP COLLECTIONS: Only for internal databases
811+ if ( ! project . resources . db . isExternal ) {
812+ for ( const col of project . collections ) {
813+ const collectionName = `${ project . _id } _${ col . name } ` ;
814+ try {
815+ await mongoose . connection . db . dropCollection ( collectionName ) ;
816+ } catch ( e ) { }
817+ }
818+
759819 try {
760- await mongoose . connection . db . dropCollection ( collectionName ) ;
820+ await mongoose . connection . db . dropCollection ( ` ${ project . _id } _users` ) ;
761821 } catch ( e ) { }
762822 }
763823
764- try {
765- await mongoose . connection . db . dropCollection ( `${ project . _id } _users` ) ;
766- } catch ( e ) { }
767-
768- // DELETE FILES
769- const supabase = await getStorage ( project ) ;
770- const bucket = getBucket ( project ) ;
824+ // DELETE: Only for internal Infraa
825+ if ( ! isExternalStorage ( project ) ) {
826+ const supabase = await getStorage ( project ) ;
827+ const bucket = getBucket ( project ) ;
771828
772- let hasMoreFiles = true ;
829+ let hasMoreFiles = true ;
773830
774- while ( hasMoreFiles ) {
775- const { data : files , error } = await supabase . storage
776- . from ( bucket )
777- . list ( projectId , { limit : 100 } ) ;
831+ while ( hasMoreFiles ) {
832+ const { data : files , error } = await supabase . storage
833+ . from ( bucket )
834+ . list ( projectId , { limit : 100 } ) ;
778835
779- if ( error ) throw error ;
836+ if ( error ) throw error ;
780837
781- if ( files && files . length > 0 ) {
782- const paths = files . map ( f => `${ projectId } /${ f . name } ` ) ;
783- await supabase . storage . from ( bucket ) . remove ( paths ) ;
784- } else {
785- hasMoreFiles = false ;
838+ if ( files && files . length > 0 ) {
839+ const paths = files . map ( f => `${ projectId } /${ f . name } ` ) ;
840+ await supabase . storage . from ( bucket ) . remove ( paths ) ;
841+ } else {
842+ hasMoreFiles = false ;
843+ }
786844 }
787845 }
788846
@@ -800,7 +858,8 @@ module.exports.deleteProject = async (req, res) => {
800858module . exports . analytics = async ( req , res ) => {
801859 try {
802860 const { projectId } = req . params ;
803- const project = await Project . findOne ( { _id : projectId } ) ;
861+ const project = await Project . findOne ( { _id : projectId , owner : req . user . _id } ) ;
862+ if ( ! project ) return res . status ( 404 ) . json ( { error : "Project not found or access denied." } ) ;
804863 const totalRequests = await Log . countDocuments ( { projectId } ) ;
805864 const logs = await Log . find ( { projectId } ) . sort ( { timestamp : - 1 } ) . limit ( 50 ) ;
806865
@@ -828,4 +887,71 @@ module.exports.analytics = async (req, res) => {
828887 } catch ( err ) {
829888 res . status ( 500 ) . json ( { error : err . message } ) ;
830889 }
890+ }
891+
892+ // FUNCTION - TOGGLE AUTH
893+ module . exports . toggleAuth = async ( req , res ) => {
894+ try {
895+ const { projectId } = req . params ;
896+ const { enable } = req . body ; // true or false
897+
898+ // Ensure user owns project
899+ const project = await Project . findOne ( { _id : projectId , owner : req . user . _id } ) ;
900+ if ( ! project ) return res . status ( 404 ) . json ( { error : "Project not found" } ) ;
901+
902+ if ( enable ) {
903+ let usersCol = project . collections . find ( c => c . name === 'users' ) ;
904+ if ( ! usersCol ) {
905+ usersCol = {
906+ name : 'users' ,
907+ model : [
908+ { key : 'email' , type : 'String' , required : true } ,
909+ { key : 'username' , type : 'String' , required : false } ,
910+ { key : 'password' , type : 'String' , required : true } ,
911+ { key : 'emailVerified' , type : 'Boolean' , required : false }
912+ ]
913+ } ;
914+ project . collections . push ( usersCol ) ;
915+ } else {
916+ if ( ! validateUsersSchema ( usersCol . model ) ) {
917+ return res . status ( 422 ) . json ( {
918+ error : "Invalid Users Schema" ,
919+ message : "The 'users' collection must have required 'email' and 'password' string fields. Please fix the schema before enabling Auth."
920+ } ) ;
921+ }
922+ }
923+ }
924+
925+ project . isAuthEnabled = ! ! enable ;
926+ await project . save ( ) ;
927+
928+ await deleteProjectById ( projectId ) ;
929+ await deleteProjectByApiKeyCache ( project . publishableKey ) ;
930+ await deleteProjectByApiKeyCache ( project . secretKey ) ;
931+
932+ const projectObj = project . toObject ( ) ;
933+ delete projectObj . publishableKey ;
934+ delete projectObj . secretKey ;
935+ delete projectObj . jwtSecret ;
936+
937+ if ( projectObj . collections && Array . isArray ( projectObj . collections ) ) {
938+ projectObj . collections = projectObj . collections . map ( col => {
939+ if ( col . name === 'users' && col . model ) {
940+ return {
941+ ...col ,
942+ model : col . model . filter ( m => m . key !== 'password' )
943+ } ;
944+ }
945+ return col ;
946+ } ) ;
947+ }
948+
949+ res . json ( {
950+ message : `Authentication ${ project . isAuthEnabled ? 'enabled' : 'disabled' } successfully` ,
951+ isAuthEnabled : project . isAuthEnabled ,
952+ project : projectObj
953+ } ) ;
954+ } catch ( err ) {
955+ res . status ( 500 ) . json ( { error : err . message } ) ;
956+ }
831957}
0 commit comments