Files
wms-app/app/login/api/engine/invited_onboarding.php
T
2026-05-21 16:51:19 +07:00

150 lines
6.2 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
/**
* invited_onboarding.php — Complete account setup for an invited user.
*
* Called by: invited_onboarding.php page AJAX after the user fills in
* their name, username, and password.
*
* The invited user was created with license='user', status='pending', and
* default_company = the inviting company. This endpoint activates the account
* so they can log in using the inviting company's SMTP for OTP delivery.
*
* Full flow:
* 1. Session guard — rejects if 'invited_user_id' is missing.
* 2. CSRF check.
* 3. Re-validate token against DB (expiry + status='pending' + license='user').
* 4. Validate and sanitise input fields.
* 5. Username format and uniqueness check.
* 6. Password match and strength check.
* 7. Hash password.
* 8. UPDATE user: name, surname, username, password, status='active',
* verify_token=NULL, verify_expires_at=NULL.
* 9. UPDATE company_map_user: invite_token=NULL.
* 10. Return { success: 1 }.
*/
require '../../../session.php';
require '../../../config.php';
require '../../../preset.php';
define('UNAUTHENTICATED_ROUTE', true);
require '../../../assets/utils/db_auth.php';
require '../../../assets/utils/classes/PasswordManager.php';
header('Content-Type: application/json; charset=utf-8');
$answer = ['success' => 0, 'message' => ''];
// ── Step 1: Session guard ─────────────────────────────────────────────────────
if (empty($_SESSION['invited_user_id']) || empty($_SESSION['invited_token'])) {
$answer['message'] = 'Invalid session. Please use your invitation link.';
http_response_code(403);
exit(json_encode($answer));
}
$user_id = (int)$_SESSION['invited_user_id'];
$token = $_SESSION['invited_token'];
// ── Step 2: CSRF check ────────────────────────────────────────────────────────
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$csrf = $_SERVER['HTTP_X_CSRF_TOKEN'] ?? '';
if (empty($csrf) || $csrf !== ($_SESSION['csrf_token'] ?? '')) {
http_response_code(403);
$answer['message'] = 'Invalid request.';
exit(json_encode($answer));
}
}
$data = json_decode($_POST['json'] ?? '{}', true) ?: [];
try {
// ── Step 3: Re-validate token ─────────────────────────────────────────────
$sth = $pdo1->prepare(
"SELECT user_id FROM user
WHERE user_id = :uid
AND verify_token = :token
AND status = 'pending'
AND license = 'user'
AND verify_expires_at > NOW()
LIMIT 1"
);
$sth->execute([':uid' => $user_id, ':token' => $token]);
if (!$sth->fetchColumn()) {
$answer['message'] = 'Invitation has expired or already been used. Please request a new invitation.';
http_response_code(403);
exit(json_encode($answer));
}
// ── Step 4: Sanitise and validate input ───────────────────────────────────
$name = trim($data['name'] ?? '');
$surname = trim($data['surname'] ?? '');
$username = strtolower(trim($data['username'] ?? ''));
$password = $data['password'] ?? '';
$confirm = $data['confirm_password'] ?? '';
if (!$name || !$surname || !$username || !$password || !$confirm) {
throw new Exception('All fields are required.');
}
// ── Step 5: Username format and uniqueness ────────────────────────────────
if (!preg_match('/^[a-z0-9_]+$/', $username)) {
throw new Exception('Username may only contain lowercase letters, numbers and underscores.');
}
$sth = $pdo1->prepare("SELECT user_id FROM user WHERE username = :u AND user_id != :uid LIMIT 1");
$sth->execute([':u' => $username, ':uid' => $user_id]);
if ($sth->fetchColumn()) {
throw new Exception('Username is already taken. Please choose another.');
}
// ── Step 6: Password match and strength ───────────────────────────────────
if ($password !== $confirm) {
throw new Exception('Passwords do not match.');
}
$pm = new PasswordManager($pdo1, $include_url);
$result = $pm->checkStrength($password, [$name, $surname, $username]);
if ($result['score'] < PasswordManager::MIN_SCORE) {
$msg = $result['warning'] ?: ($result['suggestions'][0] ?? 'Please choose a stronger password.');
throw new Exception('Password is too weak. ' . $msg);
}
// ── Step 7–8: Hash and activate account ──────────────────────────────────
$hashed = password_hash($password, PASSWORD_BCRYPT);
$pdo1->prepare(
"UPDATE user
SET name = :name,
surname = :surname,
username = :username,
password = :password,
status = 'active',
verify_token = NULL,
verify_expires_at = NULL
WHERE user_id = :uid"
)->execute([
':name' => $name,
':surname' => $surname,
':username' => $username,
':password' => $hashed,
':uid' => $user_id,
]);
// ── Step 9: Clear invite token from company_map_user ─────────────────────
$pdo1->prepare(
"UPDATE company_map_user SET invite_token = NULL WHERE user_id = :uid"
)->execute([':uid' => $user_id]);
// ── Step 10: Clear session invite keys ────────────────────────────────────
unset($_SESSION['invited_user_id'], $_SESSION['invited_token']);
$answer['success'] = 1;
$answer['message'] = 'Account setup complete.';
} catch (Exception $e) {
$answer['message'] = $e->getMessage();
http_response_code(400);
}
exit(json_encode($answer));