From a94c32042bbe12e8ce5ab67a6754eb459ccab577 Mon Sep 17 00:00:00 2001 From: Nadir Hamid Date: Tue, 9 Jun 2026 22:14:05 +0000 Subject: [PATCH 1/3] store workspace invite hash in database table and remove some duplicate code. --- app/app/Helpers/WorkflowTraits/User/UserWorkflow.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/app/Helpers/WorkflowTraits/User/UserWorkflow.php b/app/app/Helpers/WorkflowTraits/User/UserWorkflow.php index f90d48678..f827a4f56 100755 --- a/app/app/Helpers/WorkflowTraits/User/UserWorkflow.php +++ b/app/app/Helpers/WorkflowTraits/User/UserWorkflow.php @@ -16,7 +16,7 @@ use \App\Helpers\WorkspaceHelper; use \App\Helpers\EmailHelper; use \App\Helpers\WorkflowTraits\User\UserWorkflow; -use \App\Enum\WorkspaceUserStatus; +use \App\Enums\WorkspaceUserStatus; use App\WorkspaceUser; use App\WorkspaceInvite; use DB; @@ -50,7 +50,6 @@ public function createInvite($workspaceUser) { private function sendInvite($invite, $newUser, $workspaceUser, $workspace) { $mail = Config::get("mail"); $mailData = compact('newUser', 'workspace'); - $invite = $this->createInvite($workspaceUser); //$hash = $workspaceUser->createJoinHash(); $link = MainHelper::createPortalLink("/#/join-workspace/". $invite->hash); $mailData['link'] = $link; @@ -89,10 +88,14 @@ public function addUser(Request $request) } $resource = WorkspaceUser::create($attrs); + UserEmailOption::create([ 'user_id' =>$resource->id, ]); $invite = $this->createInvite($resource); + $resource->update([ + 'hash' => $invite->hash + ]); $this->sendInvite($invite, $reqUser,$resource, $workspace); return $this->response->array($resource->toArray())->withHeader('X-WorkspaceUser-ID', $resource->public_id); } From 484e67b3919714b2456eca84a4aeb3350a2b46b3 Mon Sep 17 00:00:00 2001 From: Nadir Hamid Date: Wed, 10 Jun 2026 21:37:22 +0000 Subject: [PATCH 2/3] integrate workspace user account management APIs --- app/app/Enums/WorkspaceUserStatus.php | 3 + .../WorkflowTraits/User/UserWorkflow.php | 71 ++++++++++++++++++- app/app/Http/routes.php | 5 ++ .../emails/deactivated_account.blade.php | 43 +++++++++++ .../emails/reactivated_account.blade.php | 43 +++++++++++ 5 files changed, 164 insertions(+), 1 deletion(-) create mode 100755 app/resources/views/emails/deactivated_account.blade.php create mode 100755 app/resources/views/emails/reactivated_account.blade.php diff --git a/app/app/Enums/WorkspaceUserStatus.php b/app/app/Enums/WorkspaceUserStatus.php index 2ee8b539a..5ac902a72 100644 --- a/app/app/Enums/WorkspaceUserStatus.php +++ b/app/app/Enums/WorkspaceUserStatus.php @@ -10,6 +10,8 @@ abstract class WorkspaceUserStatus public const ACTIVE = 'ACTIVE'; public const INVITED = 'INVITED'; public const TERMINATED = 'TERMINATED'; + public const DEACTIVATED = 'DEACTIVATED'; + /** * Optional: Helper to get all values for validation @@ -20,6 +22,7 @@ public static function all(): array self::ACTIVE, self::INVITED, self::TERMINATED, + self::DEACTIVATED, ]; } } \ No newline at end of file diff --git a/app/app/Helpers/WorkflowTraits/User/UserWorkflow.php b/app/app/Helpers/WorkflowTraits/User/UserWorkflow.php index f827a4f56..bc24d2b70 100755 --- a/app/app/Helpers/WorkflowTraits/User/UserWorkflow.php +++ b/app/app/Helpers/WorkflowTraits/User/UserWorkflow.php @@ -171,6 +171,52 @@ public function terminateUser(Request $request, $userId) return $this->response->noContent(); } + + public function deactivateUser(Request $request, $userId) + { + $data = $request->json()->all(); + $user = $this->getUser($request); + $workspaceUser = WorkspaceUser::select(DB::raw("workspaces_users.*, users.email, users.first_name, users.last_name"))->join('users', 'users.id', '=', 'workspaces_users.user_id')->where('workspaces_users.public_id', '=', $userId)->firstOrFail(); + if (!$this->hasPermissions($request, $workspaceUser, 'manage_users')) { + return $this->response->errorForbidden(); + } + $workspaceUser->update([ + 'status' => WorkspaceUserStatus::DEACTIVATED + ]); + + $workspace = $this->getWorkspace($request); + $mailData = compact('user', 'workspace', 'workspaceUser'); + $subject = "Your account has been deactivated"; + + \Log::info("Sending email to workspace user email address: " . $workspaceUser->email); + EmailHelper::sendEmail($subject, $workspaceUser->email, 'deactivated_account', $mailData); + + return $this->response->noContent(); + } + + + public function reactivateUser(Request $request, $userId) + { + $data = $request->json()->all(); + $user = $this->getUser($request); + $workspaceUser = WorkspaceUser::select(DB::raw("workspaces_users.*, users.email, users.first_name, users.last_name"))->join('users', 'users.id', '=', 'workspaces_users.user_id')->where('workspaces_users.public_id', '=', $userId)->firstOrFail(); + if (!$this->hasPermissions($request, $workspaceUser, 'manage_users')) { + return $this->response->errorForbidden(); + } + $workspaceUser->update([ + 'status' => WorkspaceUserStatus::ACTIVE + ]); + + $workspace = $this->getWorkspace($request); + $mailData = compact('user', 'workspace', 'workspaceUser'); + $subject = "Your account has been reactivated"; + + \Log::info("Sending email to workspace user email address: " . $workspaceUser->email); + EmailHelper::sendEmail($subject, $workspaceUser->email, 'reactivated_account', $mailData); + + return $this->response->noContent(); + } + public function resendInvite(Request $request, $userId) { $data = $request->json()->all(); @@ -184,8 +230,31 @@ public function resendInvite(Request $request, $userId) WorkspaceInvite::where("workspace_user_id", $resource->id)->update(['valid' => FALSE]); $invite = $this->createInvite($resource); - //invalidate current invite if needed + $resource->update([ + 'hash' => $invite->hash + ]); + $this->sendInvite($invite, $reqUser,$resource, $workspace); return $this->response->noContent(); } + + public function changeAccountType(Request $request, $userId) + { + $data = $request->json()->all(); + $user = $this->getUser($request); + $workspace = $this->getWorkspace($request); + $resource = WorkspaceUser::where('public_id', $userId)->firstOrFail(); + if (!WorkspaceHelper::canPerformAction($user, $workspace, 'manage_users')) { + return $this->response->errorForbidden(); + } + + if (!empty($data['assigned_role_id'])) { + $resource->update([ + 'assigned_role_id' => $data['assigned_role_id'] + ]); + } + + return $this->response->array($resource->toArray()); + } + } diff --git a/app/app/Http/routes.php b/app/app/Http/routes.php index 07bfadab1..bfbe5081c 100755 --- a/app/app/Http/routes.php +++ b/app/app/Http/routes.php @@ -824,6 +824,11 @@ $api->post("/", "WorkspaceUserController@addUser"); //$api->delete("/{userId}", "WorkspaceUserController@deleteUser"); $api->post("/{userId}/terminate", "WorkspaceUserController@terminateUser"); + $api->post("/{userId}/deactivate", "WorkspaceUserController@deactivateUser"); + $api->post("/{userId}/reactivate", "WorkspaceUserController@reactivateUser"); + + $api->post("/{userId}/changeAccountType", "WorkspaceUserController@changeAccountType"); + $api->post("/{userId}", "WorkspaceUserController@updateUser"); $api->put("/{userId}", "WorkspaceUserController@updateUser"); $api->get("/{userId}", "WorkspaceUserController@userData"); diff --git a/app/resources/views/emails/deactivated_account.blade.php b/app/resources/views/emails/deactivated_account.blade.php new file mode 100755 index 000000000..219b1e963 --- /dev/null +++ b/app/resources/views/emails/deactivated_account.blade.php @@ -0,0 +1,43 @@ +@extends('emails.layouts.account_invite') +@section('title') About :: @parent @endsection +@section('content') + + + + + + + + +
  + + + + + + + + + + + + + + + + + + + + + + +
 
Hello {{$workspaceUser->first_name}} {{$workspaceUser->last_name}}, your account has been deactivated +
 
+Your account in workspace {{$workspace->name}} has been deactivated. If you believe this was done in error, please contact your workspace administrator.

+
 
 
+
 
+ + + +@endsection diff --git a/app/resources/views/emails/reactivated_account.blade.php b/app/resources/views/emails/reactivated_account.blade.php new file mode 100755 index 000000000..46d81c9e3 --- /dev/null +++ b/app/resources/views/emails/reactivated_account.blade.php @@ -0,0 +1,43 @@ +@extends('emails.layouts.account_invite') +@section('title') About :: @parent @endsection +@section('content') + + + + + + + + +
  + + + + + + + + + + + + + + + + + + + + + + +
 
Hello {{$workspaceUser->first_name}} {{$workspaceUser->last_name}}, your account has been reactivated +
 
+Your account in workspace {{$workspace->name}} has been reactivated. You can now access the workspace again. If you have any questions, please contact your workspace administrator.

+
 
 
+
 
+ + + +@endsection From ced6e84503225c698a7246b85de31ff8ea22f686 Mon Sep 17 00:00:00 2001 From: Nadir Hamid Date: Wed, 10 Jun 2026 22:47:15 +0000 Subject: [PATCH 3/3] add API to generate tokens for available workspace under logged in account --- app/app/Helpers/MainHelper.php | 5 ++- .../JWT/AuthenticateController.php | 43 ++++++++++++++++++- app/app/Http/routes.php | 2 + 3 files changed, 47 insertions(+), 3 deletions(-) diff --git a/app/app/Helpers/MainHelper.php b/app/app/Helpers/MainHelper.php index a8764fcde..1fac1be2e 100755 --- a/app/app/Helpers/MainHelper.php +++ b/app/app/Helpers/MainHelper.php @@ -781,13 +781,14 @@ public static function sortAlphabet(&$items, $key) { return strcmp(strtolower($a[$key]), strtolower($b[$key])); }); } - public static function createWorkspaceLoginResult($token, $user, $workspace){ + public static function createWorkspaceLoginResult($token, $user, $workspace, $availableWorkspaces = []){ $result = [ 'token' => MainHelper::createJWTPayload($token), 'workspace' => $workspace->toArrayWithRoles($user), 'enable_2fa' => $user->enable_2fa, 'isAdmin' => FALSE, - 'adminWorkspaceToken' => '' + 'adminWorkspaceToken' => '', + 'availableWorkspaces' => $availableWorkspaces //'expire_in' => \Carbon\Carbon::now()->addMinutes(config('jwt.ttl'))->format('Y-m-d H:i:s') ]; if ($user->admin) { diff --git a/app/app/Http/Controllers/JWT/AuthenticateController.php b/app/app/Http/Controllers/JWT/AuthenticateController.php index fe4c731fb..50b6fada6 100755 --- a/app/app/Http/Controllers/JWT/AuthenticateController.php +++ b/app/app/Http/Controllers/JWT/AuthenticateController.php @@ -105,6 +105,17 @@ public function authenticate(Request $request) $currentUser = Auth::user(); $workspace = $this->getRequestedWorkspace($request, $currentUser); + $availableWorkspaces = Workspace::join('workspaces_users', 'workspaces_users.workspace_id', '=', 'workspaces.id') + ->where('workspaces_users.user_id', $currentUser->id) + ->select('workspaces.id', 'workspaces.name') + ->get() + ->map(function ($workspace) { + return [ + 'id' => $workspace->id, + 'name' => $workspace->name + ]; + }) + ->toArray(); $workspaceUser = WorkspaceUser::select(array('workspaces_users.*')); $workspaceUser->where('workspaces_users.user_id', $currentUser->id); if (!empty($workspace)) { @@ -167,9 +178,39 @@ public function authenticate(Request $request) return $this->response->array($result); } - $result = MainHelper::createWorkspaceLoginResult($token, $currentUser, $workspace); + $result = MainHelper::createWorkspaceLoginResult($token, $currentUser, $workspace, $availableWorkspaces); return $this->response->array($result); } + + public function requestWorkspaceToken(Request $request) + { + $currentUser = Auth::user(); + $workspaceId = $request->get('workspace_id'); + $workspace = Workspace::find($workspaceId); + if (empty($workspace)) { + return $this->errorInternal($request, 'No workspace found for user.'); + } + + if (!$token = JWTAuth::fromUser($currentUser)) { + return response()->json(['error' => 'could not create token'], 401); + } + + $availableWorkspaces = Workspace::join('workspaces_users', 'workspaces_users.workspace_id', '=', 'workspaces.id') + ->where('workspaces_users.user_id', $currentUser->id) + ->select('workspaces.id', 'workspaces.name') + ->get() + ->map(function ($workspace) { + return [ + 'id' => $workspace->id, + 'name' => $workspace->name + ]; + }) + ->toArray(); + + $result = MainHelper::createWorkspaceLoginResult($token, $currentUser, $workspace, $availableWorkspaces); + return $this->response->array($result); + } + public function heartbeat(Request $request) { return $this->response->noContent(); diff --git a/app/app/Http/routes.php b/app/app/Http/routes.php index bfbe5081c..4ce3c9a87 100755 --- a/app/app/Http/routes.php +++ b/app/app/Http/routes.php @@ -533,6 +533,7 @@ $api->post('authenticate', '\App\Http\Controllers\JWT\AuthenticateController@authenticate'); $api->post('publicAuthenticate', '\App\Http\Controllers\JWT\AuthenticateController@authenticatePublic'); $api->get('heartbeat', '\App\Http\Controllers\JWT\AuthenticateController@heartbeat'); + $api->get('requestWorkspaceToken', '\App\Http\Controllers\JWT\AuthenticateController@requestWorkspaceToken'); }); $api->group([ 'prefix' => 'account'], function($api) { @@ -583,6 +584,7 @@ $api->get('getCallRoutingTemplates', '\App\Http\Controllers\MergedController@getCallRoutingTemplates'); $api->get('getWorkspaceTokens', '\App\Http\Controllers\MergedController@getWorkspaceTokens'); $api->get('refreshWorkspaceTokens', '\App\Http\Controllers\MergedController@refreshWorkspaceTokens'); + $api->post('requestWorkspaceToken', '\App\Http\Controllers\MergedController@requestWorkspaceToken'); $api->get('getConfig', '\App\Http\Controllers\ConfigController@getConfig'); $api->get('dashboard', '\App\Http\Controllers\MergedController@dashboard'); $api->get('feed', '\App\Http\Controllers\MergedController@feed');