feat: implement OTP login flow and enhance role-based access control
- Added OTP login functionality in driver.mjs to handle user authentication with a fixed code in dev environment. - Enhanced RoleRoute component in App.tsx to support clinic-scoped doctor roles and permissions. - Updated ClinicDoctorsManager component to include pagination and search functionality for better user experience. - Refactored tests for ClinicDoctorsManager to cover new features and ensure proper API mocking. - Adjusted permissions in settingsMenu.ts and PracticeDomainSettingsPage.tsx to align with updated backend requirements. - Created RoleRoute.test.tsx to validate role-based access logic for different user scenarios.
This commit is contained in:
@@ -26,7 +26,7 @@
|
||||
* `ds` prints the design system — tokens from styles.css and the shared
|
||||
* components — so a redesign starts from what exists.
|
||||
*/
|
||||
import { spawn } from 'node:child_process';
|
||||
import { spawn, execSync } from 'node:child_process';
|
||||
import { readFileSync, writeFileSync, existsSync, readdirSync, mkdirSync } from 'node:fs';
|
||||
import { resolve, dirname } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
@@ -82,6 +82,33 @@ function cdp(ws) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* ورود با OTP — در dev کد ثابتِ `12345` است (OtpService::sendCode)، پس کل زنجیرهٔ
|
||||
* send-code → verify-code → otp-login اسکریپتپذیر است.
|
||||
*
|
||||
* راهِ نجاتِ دیتابیسی که seed نشده: کاربر واقعیِ چنین دیتابیسی ممکن است اصلاً
|
||||
* `password_hash` نداشته باشد یا رمزش را ندانیم، ولی شمارهٔ موبایلش کافی است.
|
||||
*/
|
||||
async function otpLogin(mobile) {
|
||||
const post = async (path, body) => {
|
||||
const r = await fetch(`${BASE}${path}`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
return r.json();
|
||||
};
|
||||
|
||||
const sent = await post('/api/v1/user/send-code', { mobile });
|
||||
if (!sent.uuid) throw new Error(`send-code failed: ${JSON.stringify(sent).slice(0, 160)}`);
|
||||
|
||||
const ver = await post('/api/v1/user/verify-code', { uuid: sent.uuid, code: '12345' });
|
||||
const grant = ver?.data?.grant;
|
||||
if (!grant) throw new Error(`verify-code failed: ${JSON.stringify(ver).slice(0, 160)}`);
|
||||
|
||||
return post('/api/v1/user/otp-login', { grant });
|
||||
}
|
||||
|
||||
async function login() {
|
||||
const r = await fetch(`${BASE}/api/v1/user/login`, {
|
||||
method: 'POST',
|
||||
@@ -89,14 +116,30 @@ async function login() {
|
||||
body: JSON.stringify({ mobile_number: USER, password: PASS }),
|
||||
});
|
||||
const j = await r.json();
|
||||
if (!j.access_token) {
|
||||
throw new Error(
|
||||
`login failed for ${USER}: ${JSON.stringify(j).slice(0, 160)}\n`
|
||||
+ ' → seeded users are in TEST_USERS.md; rebuild them with\n'
|
||||
+ ' ddev exec php bin/console app:seed-scenarios --reset -n',
|
||||
if (j.access_token) return j;
|
||||
|
||||
// رمز نخورد؛ با OTP امتحان کن. دیتابیسهای واقعی (نه seed) رمزِ مستندشده ندارند و
|
||||
// بدون این، تنها راه یا reset کردن دیتابیس کاربر بود یا نوشتنِ رمز روی حسابش.
|
||||
const otp = await otpLogin(USER).catch((e) => ({ _err: e.message }));
|
||||
if (otp.access_token) return otp;
|
||||
|
||||
// `send-code` سقف ۵ بار در ساعت دارد. وقتی سوخت، بهجای شلکردن یک محدودیتِ واقعیِ
|
||||
// محصول برای تست، توکن را با کامندِ خودِ اپ میسازیم — همان کلید و همان claimها.
|
||||
try {
|
||||
const out = execSync(
|
||||
`ddev exec 'php bin/console lexik:jwt:generate-token ${USER} --user-class="App\\\\Auth\\\\Entity\\\\User"'`,
|
||||
{ encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] },
|
||||
);
|
||||
}
|
||||
return j;
|
||||
const minted = out.trim().split('\n').pop().trim();
|
||||
if (minted.startsWith('ey')) return { access_token: minted, refresh_token: null };
|
||||
} catch { /* کامند در دسترس نیست؛ میافتیم روی خطای زیر */ }
|
||||
|
||||
throw new Error(
|
||||
`login failed for ${USER}: ${JSON.stringify(j).slice(0, 160)}\n`
|
||||
+ ` → OTP fallback also failed: ${otp._err ?? JSON.stringify(otp).slice(0, 120)}\n`
|
||||
+ ' → CLINICPRO_USER را روی شمارهٔ یک کاربر واقعیِ همین دیتابیس بگذار،\n'
|
||||
+ ' یا حسابهای تست را بساز: ddev exec php bin/console app:seed-scenarios --reset -n',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -104,12 +147,10 @@ async function login() {
|
||||
* مینشیند و صفحههای کلینیک خالی میآیند — که شبیه باگ است ولی نیست.
|
||||
*/
|
||||
async function switchContext(token, kind) {
|
||||
const r = await fetch(`${BASE}/api/v1/auth/contexts`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
}).catch(() => null);
|
||||
|
||||
// اندپوینت فهرست محیطها عمومی نیست؛ uuid را از /api/v1/user/me میگیریم.
|
||||
const me = await (await fetch(`${BASE}/api/v1/user/me`, {
|
||||
// فهرست محیطها فقط در `/oauth/userinfo` است. مسیر قبلی (`/api/v1/user/me`) اصلاً
|
||||
// وجود ندارد و ۴۰۴ میداد، پس `--context clinic` همیشه بیصدا نادیده گرفته میشد و
|
||||
// اسکرینشاتِ محیطِ اشتباه گرفته میشد.
|
||||
const me = await (await fetch(`${BASE}/oauth/userinfo`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
})).json().catch(() => ({}));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user