11<?php
2+ /**
3+ * Argora Foundry
4+ *
5+ * A modular PHP boilerplate for building SaaS applications, admin panels, and control systems.
6+ *
7+ * @package App
8+ * @author Taras Kondratyuk <help@argora.org>
9+ * @copyright Copyright (c) 2025 Argora
10+ * @license MIT License
11+ * @link https://github.com/getargora/foundry
12+ */
213
314namespace App \Controllers ;
415
718use Psr \Http \Message \ServerRequestInterface as Request ;
819use Psr \Container \ContainerInterface ;
920use Respect \Validation \Validator as v ;
21+ use App \Auth \Auth ;
1022
1123class UsersController extends Controller
1224{
@@ -20,7 +32,7 @@ public function listUsers(Request $request, Response $response)
2032 $ users = $ userModel ->getAllUsers ();
2133 return view ($ response ,'admin/users/listUsers.twig ' , compact ('users ' ));
2234 }
23-
35+
2436 public function createUser (Request $ request , Response $ response )
2537 {
2638 // Registrars can not create new users, then need to ask the registry
@@ -48,7 +60,7 @@ public function createUser(Request $request, Response $response)
4860 'password ' => v::stringType ()->notEmpty ()->length (6 , 255 )->setName ('Password ' ),
4961 'password_confirmation ' => v::equals ($ data ['password ' ] ?? '' )->setName ('Password Confirmation ' ),
5062 'status ' => v::in (['0 ' , '4 ' ])->setName ('Status ' ),
51- 'role ' => v::in (['admin ' , 'zone ' ])->setName ('Role ' ),
63+ 'role ' => v::in (['admin ' , 'client ' ])->setName ('Role ' ),
5264 ];
5365
5466 // Add registrar_id validation if role is registrar
@@ -88,96 +100,71 @@ public function createUser(Request $request, Response $response)
88100 return $ response ->withHeader ('Location ' , '/user/create ' )->withStatus (302 );
89101 }
90102
91- if ($ _SESSION ["auth_roles " ] != 0 ) {
92- $ registrar = true ;
93- } else {
94- $ registrar = null ;
95- }
96-
97- if ($ email ) {
98- if ($ zone_id ) {
99- $ db ->beginTransaction ();
103+ if ($ email ) {
104+ $ roles = [
105+ 'admin ' => 0 ,
106+ 'zone ' => 4 ,
107+ ];
100108
101- $ password_hashed = password_hash ($ password , PASSWORD_ARGON2ID , ['memory_cost ' => 1024 * 128 , 'time_cost ' => 6 , 'threads ' => 4 ]);
109+ $ role = $ role ?? (!empty ($ zone_id ) ? 'zone ' : 'admin ' );
110+ $ roles_mask = $ roles [$ role ] ?? 4 ;
102111
103- try {
104- $ db ->insert (
105- 'users ' ,
106- [
107- 'email ' => $ email ,
108- 'password ' => $ password_hashed ,
109- 'username ' => $ username ,
110- 'verified ' => $ verified ,
111- 'roles_mask ' => 4 ,
112- 'status ' => $ status ,
113- 'registered ' => \time ()
114- ]
115- );
116- $ user_id = $ db ->getLastInsertId ();
117-
118- $ db ->insert (
119- 'zone_users ' ,
120- [
121- 'zone_id ' => $ zone_id ,
122- 'user_id ' => $ user_id
123- ]
124- );
125-
126- $ db ->commit ();
127- } catch (Exception $ e ) {
128- $ db ->rollBack ();
129- $ this ->container ->get ('flash ' )->addMessage ('error ' , 'Database failure: ' . $ e ->getMessage ());
130- return $ response ->withHeader ('Location ' , '/user/create ' )->withStatus (302 );
131- }
112+ $ password_hashed = password_hash ($ password , PASSWORD_ARGON2ID , [
113+ 'memory_cost ' => 1024 * 128 ,
114+ 'time_cost ' => 6 ,
115+ 'threads ' => 4
116+ ]);
132117
133- $ this ->container ->get ('flash ' )->addMessage ('success ' , 'User ' . $ email . ' has been created successfully ' );
134- return $ response ->withHeader ('Location ' , '/users ' )->withStatus (302 );
135- } else {
118+ try {
136119 $ db ->beginTransaction ();
137120
138- $ password_hashed = password_hash ($ password , PASSWORD_ARGON2ID , ['memory_cost ' => 1024 * 128 , 'time_cost ' => 6 , 'threads ' => 4 ]);
139-
140- try {
141- $ db ->insert (
142- 'users ' ,
143- [
144- 'email ' => $ email ,
145- 'password ' => $ password_hashed ,
146- 'username ' => $ username ,
147- 'verified ' => $ verified ,
148- 'roles_mask ' => 0 ,
149- 'status ' => $ status ,
150- 'registered ' => \time ()
151- ]
152- );
153- $ userId = $ db ->getlastInsertId ();
154-
155- $ db ->commit ();
156- } catch (Exception $ e ) {
157- $ db ->rollBack ();
158- $ this ->container ->get ('flash ' )->addMessage ('error ' , 'Database failure: ' . $ e ->getMessage ());
159- return $ response ->withHeader ('Location ' , '/user/create ' )->withStatus (302 );
121+ $ db ->insert ('users ' , [
122+ 'email ' => $ email ,
123+ 'password ' => $ password_hashed ,
124+ 'username ' => $ username ,
125+ 'verified ' => $ verified ,
126+ 'roles_mask ' => $ roles_mask ,
127+ 'status ' => $ status ,
128+ 'registered ' => \time (),
129+ 'password_last_updated ' => date ('Y-m-d H:i:s ' ),
130+ ]);
131+
132+ $ user_id = $ db ->getLastInsertId ();
133+
134+ if ($ roles_mask === $ roles ['zone ' ] && !empty ($ zone_id )) {
135+ $ db ->insert ('zone_users ' , [
136+ 'zone_id ' => $ zone_id ,
137+ 'user_id ' => $ user_id ,
138+ ]);
160139 }
161140
162- $ db ->exec ('UPDATE users SET password_last_updated = NOW() WHERE id = ? ' , [$ userId ]);
141+ $ db ->commit ();
142+
163143 $ this ->container ->get ('flash ' )->addMessage ('success ' , 'User ' . $ email . ' has been created successfully ' );
164144 return $ response ->withHeader ('Location ' , '/users ' )->withStatus (302 );
165- }
145+ } catch (Exception $ e ) {
146+ $ this ->container ->get ('flash ' )->addMessage ('error ' , 'Database failure: ' . $ e ->getMessage ());
147+ return $ response ->withHeader ('Location ' , '/user/create ' )->withStatus (302 );
148+ }
149+ } else {
150+ $ this ->container ->get ('flash ' )->addMessage ('error ' , 'An unexpected error occurred. Please try again later ' );
151+ return $ response ->withHeader ('Location ' , '/user/create ' )->withStatus (302 );
166152 }
167153 }
168154
169155 $ db = $ this ->container ->get ('db ' );
170156 $ zones = $ db ->select ("SELECT id, domain_name FROM zones " );
157+
171158 if ($ _SESSION ["auth_roles " ] != 0 ) {
172- $ registrar = true ;
159+ $ user = true ;
173160 } else {
174- $ registrar = null ;
161+ $ user = null ;
175162 }
176163
177164 // Default view for GET requests or if POST data is not set
178165 return view ($ response ,'admin/users/createUser.twig ' , [
179166 'zones ' => $ zones ,
180- 'registrar ' => $ registrar ,
167+ 'user ' => $ user ,
181168 ]);
182169 }
183170
@@ -212,7 +199,7 @@ public function updateUser(Request $request, Response $response, $args)
212199 $ _SESSION ['user_to_update ' ] = [$ args ];
213200
214201 $ roles_new = [
215- '4 ' => ($ user ['roles_mask ' ] & 4 ) ? true : false , // Zone
202+ '4 ' => ($ user ['roles_mask ' ] & 4 ) ? true : false , // Client
216203 '8 ' => ($ user ['roles_mask ' ] & 8 ) ? true : false , // Accountant
217204 '16 ' => ($ user ['roles_mask ' ] & 16 ) ? true : false , // Support
218205 '32 ' => ($ user ['roles_mask ' ] & 32 ) ? true : false , // Auditor
@@ -351,7 +338,7 @@ public function updateUserProcess(Request $request, Response $response)
351338
352339 // Prevent elevating privileges to 4 unless the user was already 4
353340 if ($ roles_mask == 4 && $ currentRolesMask != 4 ) {
354- $ errors [] = 'You cannot elevate role to registrar unless the user was already registrar ' ;
341+ $ errors [] = 'You cannot elevate role to client administrator unless the user was already client administrator ' ;
355342 }
356343 }
357344
@@ -391,6 +378,7 @@ public function updateUserProcess(Request $request, Response $response)
391378 if (!empty ($ password )) {
392379 $ password_hashed = password_hash ($ password , PASSWORD_ARGON2ID , ['memory_cost ' => 1024 * 128 , 'time_cost ' => 6 , 'threads ' => 4 ]);
393380 $ updateData ['password ' ] = $ password_hashed ;
381+ $ updateData ['password_last_updated ' ] = date ('Y-m-d H:i:s ' );
394382 }
395383
396384 $ db ->update (
@@ -410,10 +398,42 @@ public function updateUserProcess(Request $request, Response $response)
410398
411399 $ userId = $ db ->selectValue ('SELECT id from users WHERE username = ? ' , [ $ username ]);
412400 unset($ _SESSION ['user_to_update ' ]);
413- $ db ->exec ('UPDATE users SET password_last_updated = NOW() WHERE id = ? ' , [$ userId ]);
414401 $ this ->container ->get ('flash ' )->addMessage ('success ' , 'User ' . $ username . ' has been updated successfully on ' . $ update );
415402 return $ response ->withHeader ('Location ' , '/user/update/ ' .$ username )->withStatus (302 );
416403 }
417404 }
418-
405+
406+ public function impersonateUser (Request $ request , Response $ response , $ args )
407+ {
408+ if ($ _SESSION ["auth_roles " ] != 0 ) {
409+ return $ response ->withHeader ('Location ' , '/dashboard ' )->withStatus (302 );
410+ }
411+
412+ $ db = $ this ->container ->get ('db ' );
413+
414+ if ($ args ) {
415+ $ args = trim ($ args );
416+
417+ if (!preg_match ('/^[a-z0-9_-]+$/ ' , $ args )) {
418+ $ this ->container ->get ('flash ' )->addMessage ('error ' , 'Invalid user name ' );
419+ return $ response ->withHeader ('Location ' , '/users ' )->withStatus (302 );
420+ }
421+
422+ $ user_id = $ db ->selectValue ('SELECT id FROM users WHERE username = ? AND status = 0 ' , [ $ args ]);
423+ if (!$ user_id ) {
424+ $ this ->container ->get ('flash ' )->addMessage ('error ' , 'The specified user does not exist or is no longer active ' );
425+ return $ response ->withHeader ('Location ' , '/users ' )->withStatus (302 );
426+ }
427+
428+ Auth::impersonateUser ($ user_id );
429+ } else {
430+ // Redirect to the users view
431+ return $ response ->withHeader ('Location ' , '/users ' )->withStatus (302 );
432+ }
433+ }
434+
435+ public function leave_impersonation (Request $ request , Response $ response )
436+ {
437+ Auth::leaveImpersonation ();
438+ }
419439}
0 commit comments