251 lines
12 KiB
PHP
251 lines
12 KiB
PHP
<?php
|
|
/**
|
|
* onboarding.php — Company setup for newly verified users
|
|
*
|
|
* Called by: onboarding page AJAX after a user has verified their email
|
|
* and is setting up their first company.
|
|
* Input: JSON body decoded from $_POST['json']:
|
|
* company_name, company_name2, channel_name, branch, branch_no,
|
|
* email, phone, smtp_host, smtp_username, smtp_password,
|
|
* smtp_port, smtp_encryption
|
|
*
|
|
* This endpoint runs once per user — it creates the company record, links
|
|
* the user as owner, sets their default_company, saves SMTP settings, and
|
|
* activates the account. The session must contain 'onboarding_user_id'
|
|
* (written by verify.php after successful email verification).
|
|
*
|
|
* Full flow:
|
|
* 1. Session guard — rejects if 'onboarding_user_id' is missing (403).
|
|
* 2. CSRF check — rejects requests missing a valid X-CSRF-Token header.
|
|
* 3. Decode and sanitise input fields.
|
|
* 4. Required field validation — company_name and channel_name must be non-empty.
|
|
* 5. Required SMTP validation — smtp_host, smtp_username, smtp_password
|
|
* must all be provided (company SMTP is mandatory for WMS email delivery).
|
|
* 6. Normalise smtp_port to one of ['25', '465', '587'] (default: 587).
|
|
* Normalise smtp_encryption to one of ['tls', 'ssl', 'none'] (default: tls).
|
|
* 7. Encrypt SMTP password with OpenSSL (same method/iv/key as rest of app).
|
|
* 8. Silent SMTP test — attempt to send a test email BEFORE touching the DB.
|
|
* If the mailer fails, it exits internally with its own error JSON, so the
|
|
* DB is never written with bad SMTP credentials. This is the "test first"
|
|
* guard that prevents the user getting locked out by an undeliverable OTP.
|
|
* 9. Duplicate channel_name check — 409 if already taken.
|
|
* 10. INSERT company_list row.
|
|
* 11. INSERT company_map_user row (user_id → company_id, role='owner').
|
|
* 12. UPDATE user: set default_company = new company_id, status = 'active'.
|
|
* 13. INSERT company_smtp row with the encrypted password.
|
|
* 14. Clear onboarding session keys (onboarding_user_id, _name, _email).
|
|
* 15. Return { success: 1, message: "Setup complete." }
|
|
*
|
|
* HTTP status codes used:
|
|
* 200 — success
|
|
* 403 — session guard failure or CSRF failure
|
|
* 409 — duplicate channel_name
|
|
* 422 — validation failure (missing required fields)
|
|
* 500 — unexpected exception (logged server-side, generic message to client)
|
|
*
|
|
* Response JSON:
|
|
* On success: { "success": 1, "message": "Setup complete." }
|
|
* On failure: { "success": 0, "message": "<reason>" }
|
|
*/
|
|
|
|
require '../../../session.php';
|
|
require '../../../config.php';
|
|
require '../../../dbconn.php';
|
|
require '../../../assets/utils/db_helpers.php';
|
|
|
|
header('Content-Type: application/json; charset=utf-8');
|
|
|
|
$answer = ['success' => 0, 'message' => ''];
|
|
|
|
// ── Step 1: Session guard ─────────────────────────────────────────────────────
|
|
// 'onboarding_user_id' is only written by verify.php after successful email
|
|
// verification. If it's missing, this request is out-of-sequence — reject.
|
|
if (empty($_SESSION['onboarding_user_id'])) {
|
|
$answer['message'] = 'Invalid session. Please verify your email first.';
|
|
http_response_code(403);
|
|
exit(json_encode($answer));
|
|
}
|
|
|
|
$user_id = (int)$_SESSION['onboarding_user_id'];
|
|
|
|
// ── 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);
|
|
exit(json_encode(['message' => 'Invalid request.']));
|
|
}
|
|
}
|
|
|
|
$data = json_decode($_POST['json'] ?? '{}', true) ?: [];
|
|
|
|
try {
|
|
|
|
// ── Step 3: Sanitise input ────────────────────────────────────────────────
|
|
$company_name = trim($data['company_name'] ?? '');
|
|
$company_name2 = trim($data['company_name2'] ?? '');
|
|
|
|
// channel_name is the URL slug / identifier — strip everything except
|
|
// lowercase letters, digits, hyphens, and underscores.
|
|
$channel_name = strtolower(preg_replace('/[^a-z0-9\-_]/', '', $data['channel_name'] ?? ''));
|
|
|
|
$branch = trim($data['branch'] ?? 'สำนักงานใหญ่');
|
|
$branch_no = trim($data['branch_no'] ?? '00000');
|
|
$email = trim($data['email'] ?? '');
|
|
$phone = trim($data['phone'] ?? '');
|
|
|
|
// ── Step 4: Required field validation ────────────────────────────────────
|
|
if (!$company_name || !$channel_name) {
|
|
$answer['message'] = 'Company name and channel name are required.';
|
|
http_response_code(422);
|
|
exit(json_encode($answer));
|
|
}
|
|
|
|
// ── Step 5: SMTP field validation ────────────────────────────────────────
|
|
// SMTP is mandatory because the company needs to send OTP emails to users.
|
|
// An account without working SMTP would be unable to complete 2FA login.
|
|
$smtp_host = trim($data['smtp_host'] ?? '');
|
|
$smtp_username = trim($data['smtp_username'] ?? '');
|
|
$smtp_password = $data['smtp_password'] ?? '';
|
|
|
|
if (!$smtp_host || !$smtp_username || !$smtp_password) {
|
|
$answer['message'] = 'SMTP configuration is required. Please fill in all SMTP fields.';
|
|
http_response_code(422);
|
|
exit(json_encode($answer));
|
|
}
|
|
|
|
// ── Step 6: Normalise SMTP port and encryption ────────────────────────────
|
|
// Clamp to known-good values to prevent storing unsupported configuration.
|
|
$smtp_port = trim($data['smtp_port'] ?? '587');
|
|
$smtp_encryption = trim($data['smtp_encryption'] ?? 'tls');
|
|
|
|
if (!in_array($smtp_port, ['25', '465', '587'], true)) $smtp_port = '587';
|
|
if (!in_array($smtp_encryption, ['tls', 'ssl', 'none'], true)) $smtp_encryption = 'tls';
|
|
|
|
// ── Step 7: Encrypt SMTP password ────────────────────────────────────────
|
|
// Uses the same OpenSSL method/iv/key as the rest of the app (from config.php)
|
|
// so the stored password can be decrypted by the mailer module.
|
|
$encrypted_pass = openssl_encrypt($smtp_password, $method, $pinkey, 0, $iv);
|
|
|
|
// Assemble a temporary SMTP config for the test send (step 8)
|
|
$smtp_config = [
|
|
'server' => $smtp_host,
|
|
'port' => $smtp_port,
|
|
'username' => $smtp_username,
|
|
'password' => $encrypted_pass,
|
|
'from_name' => $company_name ?: $smtp_username,
|
|
'from_email' => $email ?: $smtp_username,
|
|
'encryption' => $smtp_encryption,
|
|
];
|
|
|
|
// ── Step 8: Silent SMTP test — before any DB writes ──────────────────────
|
|
// Sends a test email to the onboarding user's registered address.
|
|
// If the mailer throws or exits, no DB records have been created yet,
|
|
// so the user can correct their SMTP settings and retry cleanly.
|
|
require_once $include_url . 'assets/utils/module/mailer.php';
|
|
|
|
$mailer = new mailer(['pdo1' => $pdo1]);
|
|
$mailer->send_email([
|
|
'company_id' => 0,
|
|
'smtp' => $smtp_config,
|
|
'to' => $_SESSION['onboarding_email'] ?? $smtp_username,
|
|
'subject' => 'WMS — SMTP Verification',
|
|
'message' => "Your SMTP is working correctly.\n\nSetup is now complete.",
|
|
'channel_name' => $company_name ?: 'WMS',
|
|
'key' => $pinkey,
|
|
]);
|
|
// If mailer fails, it calls exit() internally — nothing below this line runs.
|
|
|
|
// ── Step 9: Duplicate channel_name check ─────────────────────────────────
|
|
// channel_name is the unique identifier used in URLs and API calls — must be globally unique.
|
|
$sth = $pdo1->prepare('SELECT company_id FROM company_list WHERE channel_name = :c LIMIT 1');
|
|
$sth->execute([':c' => $channel_name]);
|
|
db_check($sth, $answer);
|
|
if ($sth->fetchColumn()) {
|
|
$answer['message'] = 'Channel name is already taken. Please choose another.';
|
|
http_response_code(409);
|
|
exit(json_encode($answer));
|
|
}
|
|
|
|
// ── Step 10: Create company record ───────────────────────────────────────
|
|
// fx (currency) defaults to 'thb' — can be changed later in company settings.
|
|
$sth = $pdo1->prepare("
|
|
INSERT INTO company_list
|
|
(channel_name, company_name, company_name2, branch, branch_no, email, phone, fx)
|
|
VALUES
|
|
(:channel_name, :company_name, :company_name2, :branch, :branch_no, :email, :phone, 'thb')
|
|
");
|
|
$sth->execute([
|
|
':channel_name' => $channel_name,
|
|
':company_name' => $company_name,
|
|
':company_name2' => $company_name2,
|
|
':branch' => $branch,
|
|
':branch_no' => $branch_no,
|
|
':email' => $email,
|
|
':phone' => $phone,
|
|
]);
|
|
db_check($sth, $answer);
|
|
$company_id = (int)$pdo1->lastInsertId();
|
|
|
|
// ── Step 11: Map user as company owner ───────────────────────────────────
|
|
// company_map_user is the many-to-many table between users and companies.
|
|
// 'owner' role grants full admin access within the company.
|
|
$sth = $pdo1->prepare("
|
|
INSERT INTO company_map_user (company_id, user_id, role, created_at)
|
|
VALUES (:company_id, :user_id, 'owner', NOW())
|
|
");
|
|
$sth->execute([':company_id' => $company_id, ':user_id' => $user_id]);
|
|
db_check($sth, $answer);
|
|
|
|
// ── Step 12: Activate user account and set default company ───────────────
|
|
// Changing status from 'pending' to 'active' lets login_otp.php proceed
|
|
// past the unverified-account check. default_company scopes all DB queries
|
|
// after login to this company.
|
|
$sth = $pdo1->prepare("UPDATE user SET default_company = :c, `status` = 'active' WHERE user_id = :u");
|
|
$sth->execute([':c' => $company_id, ':u' => $user_id]);
|
|
db_check($sth, $answer);
|
|
|
|
// ── Step 13: Save company SMTP settings ──────────────────────────────────
|
|
// Stored with the encrypted password so the mailer module can decrypt and
|
|
// use it for all outgoing email from this company (OTP, notifications, etc.).
|
|
$sth = $pdo1->prepare("
|
|
INSERT INTO company_smtp
|
|
(company_id, server, port, username, password,
|
|
from_name, from_email, encryption, updated_at)
|
|
VALUES
|
|
(:company_id, :server, :port, :username, :password,
|
|
:from_name, :from_email, :encryption, NOW())
|
|
");
|
|
$sth->execute([
|
|
':company_id' => $company_id,
|
|
':server' => $smtp_host,
|
|
':port' => $smtp_port,
|
|
':username' => $smtp_username,
|
|
':password' => $encrypted_pass,
|
|
':from_name' => $company_name,
|
|
':from_email' => $email ?: $smtp_username,
|
|
':encryption' => $smtp_encryption,
|
|
]);
|
|
db_check($sth, $answer);
|
|
|
|
// ── Step 14: Clear onboarding session keys ───────────────────────────────
|
|
// These keys are no longer needed and should not persist into the
|
|
// authenticated session. The user will be redirected to the login page.
|
|
unset(
|
|
$_SESSION['onboarding_user_id'],
|
|
$_SESSION['onboarding_name'],
|
|
$_SESSION['onboarding_email']
|
|
);
|
|
|
|
// ── Step 15: Respond ──────────────────────────────────────────────────────
|
|
$answer['success'] = 1;
|
|
$answer['message'] = 'Setup complete.';
|
|
|
|
} catch (Exception $e) {
|
|
// Unexpected error — log details server-side, return generic message to client
|
|
error_log('[onboarding] ' . $e->getMessage());
|
|
$answer['message'] = 'Setup failed. Please try again.';
|
|
http_response_code(500);
|
|
}
|
|
|
|
exit(json_encode($answer)); |