Implement Enterprise Logic (Redis, Phone Auth, Full Schema)

This commit is contained in:
Hamza-Ayed
2026-08-26 17:34:51 +03:00
parent df5d8dfc60
commit e944d92aac
10 changed files with 707 additions and 86 deletions
+206
View File
@@ -0,0 +1,206 @@
<?php
namespace App\Controllers;
use App\Core\Request;
use App\Core\Response;
use App\Core\Database;
use App\Core\Security;
use App\Core\Validator;
use App\Core\RedisClient;
class AuthController
{
/**
* Register a new user using Phone Number
*/
public function register(Request $request, Response $response): void
{
$body = $request->getBody();
$validator = new Validator();
$isValid = $validator->validate($body, [
'full_name' => 'required',
'phone_number' => 'required',
'password' => 'required|min:8',
'role' => 'required'
]);
if (!$isValid) {
$response->status(400)->json([
'status' => 'error',
'message' => 'Validation failed',
'errors' => $validator->getErrors()
]);
return;
}
$phone = $body['phone_number'];
$role = $body['role'];
if (!in_array($role, ['student', 'teacher', 'guardian'])) {
$response->status(400)->json([
'status' => 'error',
'message' => 'Invalid role specified'
]);
return;
}
$phoneHash = Security::blindIndex($phone);
$existing = Database::selectOne("SELECT id FROM users WHERE phone_hash = ? LIMIT 1", [$phoneHash]);
if ($existing) {
$response->status(409)->json([
'status' => 'error',
'message' => 'Phone number is already registered'
]);
return;
}
$passwordHash = Security::hashPassword($body['password']);
// Generate UUID
$uuid = sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
mt_rand(0, 0xffff), mt_rand(0, 0xffff),
mt_rand(0, 0xffff),
mt_rand(0, 0x0fff) | 0x4000,
mt_rand(0, 0x3fff) | 0x8000,
mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
);
$userId = Database::insert(
"INSERT INTO users (uuid, full_name, phone_number, phone_hash, password_hash, role, status) VALUES (?, ?, ?, ?, ?, ?, 'active')",
[$uuid, $body['full_name'], $phone, $phoneHash, $passwordHash, $role]
);
$this->generateSessionAndRespond($userId, $uuid, $role, $response, "User registered successfully");
}
/**
* Login using Phone Number and Password
*/
public function login(Request $request, Response $response): void
{
$body = $request->getBody();
$validator = new Validator();
if (!$validator->validate($body, [
'phone_number' => 'required',
'password' => 'required'
])) {
$response->status(400)->json([
'status' => 'error',
'message' => 'Validation failed',
'errors' => $validator->getErrors()
]);
return;
}
$phoneHash = Security::blindIndex($body['phone_number']);
$user = Database::selectOne("SELECT * FROM users WHERE phone_hash = ? LIMIT 1", [$phoneHash]);
if (!$user || !Security::verifyPassword($body['password'], $user['password_hash'])) {
$response->status(401)->json([
'status' => 'error',
'message' => 'Invalid phone number or password'
]);
return;
}
if ($user['status'] === 'suspended') {
$response->status(403)->json([
'status' => 'error',
'message' => 'Account is suspended'
]);
return;
}
$this->generateSessionAndRespond($user['id'], $user['uuid'], $user['role'], $response, "Login successful");
}
/**
* Request OTP for phone verification
*/
public function requestOtp(Request $request, Response $response): void
{
$body = $request->getBody();
if (empty($body['phone_number'])) {
$response->status(400)->json(['status' => 'error', 'message' => 'Phone number is required']);
return;
}
$phone = $body['phone_number'];
$otp = (string)random_int(100000, 999999);
// Save OTP to Redis for 5 minutes
$redis = RedisClient::getInstance();
$redis->setex('otp:' . $phone, 300, $otp);
// TODO: Integrate SMS gateway here to actually send the OTP via SMS
$response->json([
'status' => 'success',
'message' => 'OTP sent successfully (Simulated: ' . $otp . ')'
]);
}
/**
* Common method to generate JWT and save session to Redis
*/
private function generateSessionAndRespond(int $userId, string $uuid, string $role, Response $response, string $msg): void
{
$payload = [
'user_id' => $userId,
'uuid' => $uuid,
'role' => $role
];
$token = Security::generateJWT($payload);
// Store session in Redis (Active for 30 days)
try {
$redis = RedisClient::getInstance();
$redis->setex("session:{$userId}:{$token}", 30 * 86400, "active");
} catch (\Exception $e) {
error_log("Failed to save session to Redis: " . $e->getMessage());
}
$response->status(200)->json([
'status' => 'success',
'message' => $msg,
'data' => [
'token' => $token,
'user' => [
'uuid' => $uuid,
'role' => $role
]
]
]);
}
/**
* Get Current User Data
*/
public function me(Request $request, Response $response): void
{
$userId = $request->user_id;
$user = Database::selectOne(
"SELECT uuid, full_name, role, status, created_at FROM users WHERE id = ? LIMIT 1",
[$userId]
);
if (!$user) {
$response->status(404)->json([
'status' => 'error',
'message' => 'User not found'
]);
return;
}
$response->json([
'status' => 'success',
'data' => $user
]);
}
}