@@ -413,7 +413,13 @@ def logout():
413413def me ():
414414 user = User .query .get (session ['user_id' ])
415415 dbs = [{'id' : d .id , 'name' : d .name , 'display_name' : d .display_name } for d in user .accessible_databases ]
416- return jsonify ({'username' : user .username , 'role' : user .role , 'databases' : dbs , 'current_db' : session .get ('db_name' )})
416+ return jsonify ({
417+ 'username' : user .username ,
418+ 'role' : user .role ,
419+ 'databases' : dbs ,
420+ 'current_db' : session .get ('db_name' ),
421+ 'is_account_owner' : user .is_account_owner if is_saas () else (user .role == 'admin' )
422+ })
417423
418424@api_bp .route ('/select-db/<string:db_name>' , methods = ['POST' ])
419425@login_required
@@ -426,32 +432,56 @@ def select_database(db_name):
426432@api_bp .route ('/databases' , methods = ['GET' , 'POST' ])
427433@admin_required
428434def databases_handler ():
435+ current_user = User .query .get (session .get ('user_id' ))
429436 if request .method == 'GET' :
430- dbs = Database .query .order_by (Database .created_at .desc ()).all ()
437+ # In SaaS mode, only show databases owned by this admin
438+ if is_saas ():
439+ dbs = Database .query .filter_by (owner_id = current_user .id ).order_by (Database .created_at .desc ()).all ()
440+ else :
441+ dbs = Database .query .order_by (Database .created_at .desc ()).all ()
431442 return jsonify ([{'id' : d .id , 'name' : d .name , 'display_name' : d .display_name , 'description' : d .description } for d in dbs ])
432443 else :
433444 data = request .get_json (); name , display_name = data .get ('name' ), data .get ('display_name' )
434445 if not name or not display_name : return jsonify ({'error' : 'Missing fields' }), 400
435446 if Database .query .filter_by (name = name ).first (): return jsonify ({'error' : 'Exists' }), 400
436447 new_db = Database (name = name , display_name = display_name , description = data .get ('description' , '' ))
448+ # In SaaS mode, set owner to current admin
449+ if is_saas ():
450+ new_db .owner_id = current_user .id
437451 db .session .add (new_db )
438- for admin in User .query .filter_by (role = 'admin' ).all (): admin .accessible_databases .append (new_db )
452+ # In SaaS mode, only grant access to this admin; in self-hosted, grant to all admins
453+ if is_saas ():
454+ current_user .accessible_databases .append (new_db )
455+ else :
456+ for admin in User .query .filter_by (role = 'admin' ).all (): admin .accessible_databases .append (new_db )
439457 db .session .commit (); return jsonify ({'message' : 'Created' , 'id' : new_db .id }), 201
440458
441459@api_bp .route ('/databases/<int:db_id>' , methods = ['DELETE' ])
442460@admin_required
443461def delete_database (db_id ):
444462 target_db = Database .query .get_or_404 (db_id )
463+ # In SaaS mode, only allow deleting databases you own
464+ if is_saas ():
465+ current_user_id = session .get ('user_id' )
466+ if target_db .owner_id != current_user_id :
467+ return jsonify ({'error' : 'Access denied' }), 403
445468 db .session .delete (target_db ); db .session .commit (); return jsonify ({'message' : 'Deleted' })
446469
447470@api_bp .route ('/databases/<int:db_id>/access' , methods = ['GET' , 'POST' ])
448471@admin_required
449472def database_access_handler (db_id ):
450473 target_db = Database .query .get_or_404 (db_id )
474+ current_user_id = session .get ('user_id' )
475+ # In SaaS mode, only allow managing access to databases you own
476+ if is_saas () and target_db .owner_id != current_user_id :
477+ return jsonify ({'error' : 'Access denied' }), 403
451478 if request .method == 'GET' :
452479 return jsonify ([{'id' : u .id , 'username' : u .username , 'role' : u .role } for u in target_db .users ])
453480 else :
454481 user = User .query .get_or_404 (request .get_json ().get ('user_id' ))
482+ # In SaaS mode, only allow granting access to users you created
483+ if is_saas () and user .created_by_id != current_user_id and user .id != current_user_id :
484+ return jsonify ({'error' : 'Cannot grant access to users outside your account' }), 403
455485 if target_db not in user .accessible_databases :
456486 user .accessible_databases .append (target_db ); db .session .commit ()
457487 return jsonify ({'message' : 'Granted' })
@@ -460,35 +490,65 @@ def database_access_handler(db_id):
460490@admin_required
461491def revoke_database_access (db_id , user_id ):
462492 target_db = Database .query .get_or_404 (db_id ); user = User .query .get_or_404 (user_id )
493+ current_user_id = session .get ('user_id' )
494+ # In SaaS mode, only allow revoking access to databases you own
495+ if is_saas () and target_db .owner_id != current_user_id :
496+ return jsonify ({'error' : 'Access denied' }), 403
463497 if target_db in user .accessible_databases :
464498 user .accessible_databases .remove (target_db ); db .session .commit ()
465499 return jsonify ({'message' : 'Revoked' })
466500
467501@api_bp .route ('/users' , methods = ['GET' , 'POST' ])
468502@admin_required
469503def users_handler ():
504+ current_user_id = session .get ('user_id' )
505+ current_user = User .query .get (current_user_id )
470506 if request .method == 'GET' :
471- users = User .query .all (); return jsonify ([{'id' : u .id , 'username' : u .username , 'role' : u .role } for u in users ])
507+ # In SaaS mode, only show users created by this admin (plus themselves)
508+ if is_saas ():
509+ users = User .query .filter (
510+ (User .created_by_id == current_user_id ) | (User .id == current_user_id )
511+ ).all ()
512+ else :
513+ users = User .query .all ()
514+ return jsonify ([{'id' : u .id , 'username' : u .username , 'role' : u .role } for u in users ])
472515 else :
473516 data = request .get_json (); username , password = data .get ('username' ), data .get ('password' )
474517 if User .query .filter_by (username = username ).first (): return jsonify ({'error' : 'Taken' }), 400
475518 new_user = User (username = username , role = data .get ('role' , 'user' ), password_change_required = True )
519+ # In SaaS mode, track who created this user
520+ if is_saas ():
521+ new_user .created_by_id = current_user_id
476522 new_user .set_password (data .get ('password' )); db .session .add (new_user )
477523 for db_id in data .get ('database_ids' , []):
478- d = Database .query .get (db_id );
479- if d : new_user .accessible_databases .append (d )
524+ d = Database .query .get (db_id )
525+ # In SaaS mode, only allow assigning access to databases you own
526+ if d :
527+ if is_saas () and d .owner_id != current_user_id :
528+ continue # Skip databases not owned by this admin
529+ new_user .accessible_databases .append (d )
480530 db .session .commit (); return jsonify ({'message' : 'Created' , 'id' : new_user .id }), 201
481531
482532@api_bp .route ('/users/<int:user_id>' , methods = ['DELETE' ])
483533@admin_required
484534def delete_user (user_id ):
485535 if user_id == session .get ('user_id' ): return jsonify ({'error' : 'Self' }), 400
486- user = User .query .get_or_404 (user_id ); db .session .delete (user ); db .session .commit (); return jsonify ({'message' : 'Deleted' })
536+ user = User .query .get_or_404 (user_id )
537+ # In SaaS mode, only allow deleting users you created
538+ if is_saas ():
539+ current_user_id = session .get ('user_id' )
540+ if user .created_by_id != current_user_id :
541+ return jsonify ({'error' : 'Access denied' }), 403
542+ db .session .delete (user ); db .session .commit (); return jsonify ({'message' : 'Deleted' })
487543
488544@api_bp .route ('/users/<int:user_id>/databases' , methods = ['GET' ])
489545@admin_required
490546def get_user_databases (user_id ):
491547 user = User .query .get_or_404 (user_id )
548+ current_user_id = session .get ('user_id' )
549+ # In SaaS mode, only allow viewing databases of users you created (or yourself)
550+ if is_saas () and user .created_by_id != current_user_id and user .id != current_user_id :
551+ return jsonify ({'error' : 'Access denied' }), 403
492552 return jsonify ([{'id' : d .id , 'name' : d .name , 'display_name' : d .display_name } for d in user .accessible_databases ])
493553
494554@api_bp .route ('/api/accounts' , methods = ['GET' ])
@@ -676,7 +736,7 @@ def process_auto_payments():
676736
677737@api_bp .route ('/api/version' , methods = ['GET' ])
678738def get_version ():
679- return jsonify ({'version' : '3.2.5 ' , 'license' : "O'Saasy" , 'license_url' : 'https://osaasy.dev/' , 'features' : ['enhanced_frequencies' , 'auto_payments' , 'postgresql_saas' , 'row_tenancy' ]})
739+ return jsonify ({'version' : '3.2.6 ' , 'license' : "O'Saasy" , 'license_url' : 'https://osaasy.dev/' , 'features' : ['enhanced_frequencies' , 'auto_payments' , 'postgresql_saas' , 'row_tenancy' ]})
680740
681741@api_bp .route ('/ping' )
682742def ping (): return jsonify ({'status' : 'ok' })
@@ -845,8 +905,9 @@ def register():
845905 if User .query .filter_by (email = email ).first ():
846906 return jsonify ({'success' : False , 'error' : 'Email already registered' }), 409
847907
848- # Create user
849- user = User (username = username , email = email , role = 'user' )
908+ # Create user - in SaaS mode, each registered user is an admin of their own account
909+ user_role = 'admin' if is_saas () else 'user'
910+ user = User (username = username , email = email , role = user_role )
850911 user .set_password (password )
851912
852913 # For self-hosted mode without email verification, mark as verified
@@ -872,6 +933,10 @@ def register():
872933 db .session .add (default_db )
873934 db .session .flush () # Get the IDs
874935
936+ # In SaaS mode, set the owner_id to track which admin owns this database
937+ if is_saas ():
938+ default_db .owner_id = user .id
939+
875940 # Grant user access to their default database
876941 user .accessible_databases .append (default_db )
877942
@@ -1400,7 +1465,8 @@ def jwt_me():
14001465 'username' : user .username ,
14011466 'role' : user .role ,
14021467 'databases' : databases ,
1403- 'current_db' : g .jwt_db_name
1468+ 'current_db' : g .jwt_db_name ,
1469+ 'is_account_owner' : user .is_account_owner if is_saas () else (user .role == 'admin' )
14041470 }
14051471 })
14061472
@@ -1809,7 +1875,7 @@ def jwt_get_version():
18091875 return jsonify ({
18101876 'success' : True ,
18111877 'data' : {
1812- 'version' : '3.2.5 ' ,
1878+ 'version' : '3.2.6 ' ,
18131879 'api_version' : 'v2' ,
18141880 'license' : "O'Saasy" ,
18151881 'license_url' : 'https://osaasy.dev/' ,
0 commit comments