Files
alrahma_sunday_school/app/Controllers/View/AuthorizedUsersController.php
T
2026-03-24 01:02:36 -04:00

360 lines
11 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace App\Controllers\View;
use CodeIgniter\HTTP\ResponseInterface;
use CodeIgniter\RESTful\ResourceController;
use App\Models\UserModel;
use App\Models\AuthorizedUserModel;
use CodeIgniter\I18n\Time;
class AuthorizedUsersController extends ResourceController
{
private const TOKEN_TTL_HOURS = 24;
protected $userModel;
protected $authorizedUserModel;
public function __construct(private \CodeIgniter\HTTP\IncomingRequest $request)
{
$this->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 users 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 = "
<p>You have been added as an authorized user for another account. Click the link below to confirm your access:</p>
<p><a href='{$confirmLink}'>Confirm Access</a></p>
<p>If you did not request this, please ignore this email.</p>
";
// 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);
}
}
}