userModel = new UserModel(); $this->authorizedUserModel = new AuthorizedUserModel(); } private function requireLogin() { if (!session()->get('is_logged_in')) { return $this->failUnauthorized('Authentication required.'); } return null; } private function requireOwnership(array $authorizedUser) { $userId = (int) session()->get('user_id'); if ($userId <= 0 || (int) ($authorizedUser['user_id'] ?? 0) !== $userId) { return $this->failForbidden('You do not have access to this resource.'); } return null; } private function hashToken(string $token): string { return hash('sha256', $token); } /** * Return a list of authorized users for the logged-in main user. * * @return ResponseInterface */ public function index() { if ($resp = $this->requireLogin()) { return $resp; } $userId = session()->get('user_id'); $authorizedUsers = $this->authorizedUserModel->where('user_id', $userId)->findAll(); return $this->respond($authorizedUsers); } /** * Show a specific authorized user by ID. * * @param int|string|null $id * @return ResponseInterface */ public function show($id = null) { if ($resp = $this->requireLogin()) { return $resp; } $authorizedUser = $this->authorizedUserModel->find($id); if (!$authorizedUser) { return $this->failNotFound('Authorized user not found.'); } if ($resp = $this->requireOwnership($authorizedUser)) { return $resp; } return $this->respond($authorizedUser); } /** * Create a new authorized user (add by email). * * @return ResponseInterface */ public function create() { if ($resp = $this->requireLogin()) { return $resp; } $email = strtolower($this->request->getPost('email')); // Validate email if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { return $this->failValidationErrors('Invalid email address.'); } $user = $this->userModel->where('email', $email)->first(); if (!$user) { return $this->respondCreated(['message' => 'Authorized user added. A confirmation email has been sent.']); } // Generate a token for confirmation helper('text'); $token = bin2hex(random_bytes(48)); $tokenHash = $this->hashToken($token); // Add entry to the authorized_users table $this->authorizedUserModel->insert([ 'user_id' => session()->get('user_id'), // Main user ID 'authorized_user_id' => $user['id'], 'email' => $email, 'token' => $tokenHash, 'status' => 'Pending' ]); // Send confirmation email to the authorized user $this->sendAuthorizedUserConfirmationEmail($email, $token); return $this->respondCreated(['message' => 'Authorized user added. A confirmation email has been sent.']); } /** * Update an existing authorized user. * * @param int|string|null $id * @return ResponseInterface */ public function update($id = null) { if ($resp = $this->requireLogin()) { return $resp; } // Fetch the authorized user $authorizedUser = $this->authorizedUserModel->find($id); if (!$authorizedUser) { return $this->failNotFound('Authorized user not found.'); } if ($resp = $this->requireOwnership($authorizedUser)) { return $resp; } // Update the authorized user’s information (e.g., email) $email = strtolower($this->request->getPost('email')); if ($email && filter_var($email, FILTER_VALIDATE_EMAIL)) { $authorizedUser['email'] = $email; } $this->authorizedUserModel->save($authorizedUser); return $this->respondUpdated(['message' => 'Authorized user information updated.']); } /** * Delete an authorized user. * * @param int|string|null $id * @return ResponseInterface */ public function delete($id = null) { if ($resp = $this->requireLogin()) { return $resp; } $authorizedUser = $this->authorizedUserModel->find($id); if (!$authorizedUser) { return $this->failNotFound('Authorized user not found.'); } if ($resp = $this->requireOwnership($authorizedUser)) { return $resp; } // Delete the authorized user record $this->authorizedUserModel->delete($id); return $this->respondDeleted(['message' => 'Authorized user deleted successfully.']); } /** * Confirms the authorized user's token and allows them to set a password. * * @return ResponseInterface */ public function confirm() { $token = $this->request->getGet('token'); if (!$token) { return $this->fail('Invalid confirmation link.'); } $tokenHash = $this->hashToken($token); $authorizedUser = $this->authorizedUserModel ->groupStart() ->where('token', $tokenHash) ->orWhere('token', $token) ->groupEnd() ->where('created_at >=', Time::now()->subHours(self::TOKEN_TTL_HOURS)->toDateTimeString()) ->first(); if (!$authorizedUser) { return $this->fail('Invalid or expired confirmation link.'); } // Mark the authorized user as active and rotate token for password setup $nextToken = bin2hex(random_bytes(48)); $nextTokenHash = $this->hashToken($nextToken); $this->authorizedUserModel->update($authorizedUser['id'], [ 'status' => 'Active', 'token' => $nextTokenHash, ]); return redirect()->to('/set_authorized_user_password/' . $authorizedUser['authorized_user_id'] . '?token=' . $nextToken); } /** * Displays the form for the authorized user to set their password. * * @param int $authorizedUserId * @return ResponseInterface */ public function setPassword($authorizedUserId) { $token = (string) $this->request->getGet('token'); if ($token === '') { return $this->fail('Invalid confirmation link.'); } $tokenHash = $this->hashToken($token); $authorizedUser = $this->authorizedUserModel ->groupStart() ->where('token', $tokenHash) ->orWhere('token', $token) ->groupEnd() ->where('authorized_user_id', $authorizedUserId) ->where('status', 'Active') ->where('updated_at >=', Time::now()->subHours(self::TOKEN_TTL_HOURS)->toDateTimeString()) ->first(); if (!$authorizedUser) { return $this->fail('Invalid or expired confirmation link.'); } $user = $this->userModel->find($authorizedUserId); if (!$user) { return $this->failNotFound('User not found.'); } return view('user/set_authorized_user_password', [ 'userId' => $authorizedUserId, 'token' => $token, ]); } /** * Processes the password for the authorized user. * * @return ResponseInterface */ public function savePassword($authorizedUserId = null) { $validation = \Config\Services::validation(); $validation->setRules([ 'password' => [ 'label' => 'Password', 'rules' => 'required|min_length[8]|regex_match[/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@\\-=\\+*#$%&!?])[A-Za-z\\d@\\-=\\+*#$%&!?]{8,}$/]', ], 'password_confirm' => 'required|matches[password]', 'user_id' => 'required|integer', 'token' => 'required', ]); if (!$this->validate($validation->getRules())) { return $this->failValidationErrors($validation->getErrors()); } $userId = (int) $this->request->getPost('user_id'); $token = (string) $this->request->getPost('token'); $authorizedUserId = $authorizedUserId !== null ? (int) $authorizedUserId : $userId; if ($userId <= 0 || $authorizedUserId <= 0 || $userId !== $authorizedUserId) { return $this->fail('Invalid request.'); } $tokenHash = $this->hashToken($token); $authorizedUser = $this->authorizedUserModel ->groupStart() ->where('token', $tokenHash) ->orWhere('token', $token) ->groupEnd() ->where('authorized_user_id', $authorizedUserId) ->where('status', 'Active') ->where('updated_at >=', Time::now()->subHours(self::TOKEN_TTL_HOURS)->toDateTimeString()) ->first(); if (!$authorizedUser) { return $this->fail('Invalid or expired confirmation link.'); } $user = $this->userModel->find($authorizedUserId); if (!$user) { return $this->failNotFound('User not found.'); } $password = (string) $this->request->getPost('password'); $hashedPassword = pbkdf2_hash($password); $this->userModel->update($authorizedUserId, ['password' => $hashedPassword]); $this->authorizedUserModel->update($authorizedUser['id'], ['token' => null]); return $this->respond(['message' => 'Password has been successfully set.']); } /** * Sends a confirmation email to the authorized user. * * @param string $email * @param string $token */ private function sendAuthorizedUserConfirmationEmail($email, $token) { // Generate the confirmation link $confirmLink = site_url('/confirm_authorized_user?token=' . $token); // Compose the email message $message = "

You have been added as an authorized user for another account. Click the link below to confirm your access:

Confirm Access

If you did not request this, please ignore this email.

"; // Create an instance of the EmailController $emailController = new \App\Controllers\View\EmailController(); $subject = 'Authorized User Confirmation'; // Send email if ($emailController->sendEmail($email, $subject, $message)) { log_message('info', 'Authorized user confirmation email sent to ' . $email); } else { log_message('error', 'Failed to send authorized user confirmation email to ' . $email); } } }