Files
nobat724_front/.claude/prompt/fix-login-session-userinfo.md
T
hamedandClaude Opus 4.8 08f5829a22 fix(auth): complete login by reading userinfo from response envelope
oauth/userinfo returns { success, data: {...} }, so after the interceptor
unwraps once the user lives at res.data, not res. getInfo checked
res.uuid (undefined), so it never set the userInfo cookie or redirected —
the /login page just sat there after entering the OTP. Read res.data,
store the user object (with a username alias for mobile_number so the
appointment flow keeps working), redirect via location.href, and show a
toast instead of silently staying when userinfo fails.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 18:16:36 +03:30

8.5 KiB
Raw Blame History

رفع ماندن صفحه‌ی لاگین بعد از وارد کردن کد OTP (نشست ساخته نمی‌شود)

پروژه

nobat724_front — سایت عمومی. این کار صرفاً frontend است؛ backend درست کار می‌کند (با curl تأیید شد: send-code → uuid، verify-code با 12345 → ۲۰۰، oauth/token → توکن، oauth/userinfo{ success, data: {...} }).

زمینه

در صفحه‌ی /login، کاربر شماره موبایل را وارد می‌کند، کد 12345 (کد ثابت محیط dev) را می‌زند، ولی صفحه روی /login می‌ماند و وارد نمی‌شود. توکن گرفته می‌شود ولی نشست کامل نمی‌شود.

علت دقیق: بعد از گرفتن توکن، components/register/verificationPage/SendReq.js تابع getInfo را صدا می‌زند که request.getUserInfo() (یعنی GET /oauth/userinfo) را فراخوانی می‌کند و فقط در صورت res.uuid کوکی userInfo را ست و ریدایرکت می‌کند. اما پاسخ oauth/userinfo به شکل { success: true, data: { uuid, mobile_number, roles, ... } } است و interceptor در services/api.js یک‌بار پاسخ را باز می‌کند، پس داده‌ی واقعی در res.data است، نه res مستقیم. در نتیجه res.uuid === undefined، شرط if (res && res.uuid) رد می‌شود، نه کوکی userInfo ست می‌شود و نه ریدایرکت اتفاق می‌افتد → صفحه می‌ماند.

توجه: این همان pitfall «double-nesting» است که قبلاً در پروفایل و لیست بیمه هم دیده شد. اینجا پاسخ یک‌لایه envelope دارد ({success, data})؛ بعد از interceptor، داده در res.data.

مشکل / هدف

تابع getInfo در SendReq.js باید کاربر را از res.data بخواند (نه res)، کوکی userInfo را با شیء کاربر (نه envelope) ست کند، و سپس ریدایرکت/پیشروی مرحله را انجام دهد. همچنین در صورت پاسخ نامعتبر، خطای واضح نمایش داده شود (نه ماندن بی‌صدا).

فایل‌های مرتبط

فایل نقش
components/register/verificationPage/SendReq.js بعد از گرفتن توکن، userinfo می‌گیرد و کوکی userInfo را ست می‌کند — محل باگ
services/response.js getUserInfoGET /oauth/userinfo با requireAuth
services/api.js interceptor که یک‌بار response.data را برمی‌گرداند
lib/auth.js getUser() — وجود کوکی userInfo را چک می‌کند (server-side)
مصرف‌کننده‌های userInfo components/appointment/index.js، components/layout/sidebar/*، components/dashboard/* — همگی userInfo را parse می‌کنند و انتظار شیء کاربر (.uuid، .mobile_number، .representation_uuid) دارند

وضعیت فعلی (کد واقعی)

SendReq.jsgetInfo (باگ‌دار)

const getInfo = (token, cookieOptions) => {
  request
    .getUserInfo({ headers: { Authorization: `Bearer ${token}` } })
    .then((res) => {
      setLoading(false);
      if (res && res.uuid) {                                   // ❌ uuid در res.data است نه res
        Cookies.set("userInfo", JSON.stringify(res), cookieOptions);  // ❌ کل envelope ذخیره می‌شود
        if (setStep) {
          setStep(3);
        } else {
          window.location.pathname = "/";
        }
      }
      // ❌ اگر uuid نبود، هیچ اتفاقی نمی‌افتد — صفحه می‌ماند، خطا هم نشان داده نمی‌شود
    })
    .catch(() => {
      setLoading(false);
      setIsError(true);
    });
};

قرارداد واقعی GET /oauth/userinfo (از clinicpro/docs/api/auth.md)

{
  "success": true,
  "data": {
    "id": 4766,
    "uuid": "550e8400-...",
    "mobile_number": "09123456789",
    "realName": "...",
    "roles": ["ROLE_USER"],
    "primary_role": "...",
    "doctor_uuid": "...",
    "representation_uuid": null
  }
}

بعد از interceptor: داده در res.data. هیچ فیلد username در پاسخ نیست؛ شماره موبایل mobile_number است.

وظایف

۱. خواندن کاربر از res.data و ست‌کردن کوکی با شیء کاربر

const getInfo = (token, cookieOptions) => {
  request
    .getUserInfo({ headers: { Authorization: `Bearer ${token}` } })
    .then((res) => {
      setLoading(false);
      const userInfo = res?.data;
      if (userInfo?.uuid) {
        Cookies.set("userInfo", JSON.stringify(userInfo), cookieOptions);
        if (setStep) {
          setStep(3);
        } else {
          window.location.href = "/";
        }
      } else {
        setIsError(true);
        toast.error("دریافت اطلاعات کاربر ناموفق بود. دوباره تلاش کنید.");
      }
    })
    .catch(() => {
      setLoading(false);
      setIsError(true);
    });
};
  • کوکی userInfo باید شیء کاربر (res.data) باشد تا مصرف‌کننده‌ها (parsedData.uuid, .mobile_number, .representation_uuid) درست کار کنند.
  • window.location.href = "/" به‌جای window.location.pathname = "/" (مطمئن‌تر برای reload کامل و خواندن کوکی‌های تازه server-side).
  • toast از قبل در این فلو استفاده می‌شود (در همان فایل بعد از رفع قبلی import { toast } from "react-toastify" اضافه شده) — اگر نبود اضافه کن؛ ToastContainer در app/layout.js mount است.

۲. سازگاری مصرف‌کننده‌های username

  • بعضی جاها (مثل components/appointment/index.js) parsedData.username خوانده می‌شود، ولی پاسخ oauth/userinfo فیلد username ندارد (فقط mobile_number). بررسی کن:
    • یا در getInfo هنگام ذخیره، یک فیلد username معادل mobile_number هم اضافه کن (سازگاری عقب‌رو، کم‌ریسک‌تر):
      const userInfo = { ...res.data, username: res.data.mobile_number };
      
    • یا همه‌ی خواننده‌های username را به mobile_number مهاجرت بده.
    • یکی را انتخاب کن؛ گزینه‌ی اول (افزودن username) دیف کوچک‌تری دارد و فلوی appointment را نمی‌شکند. در گزارش ذکر کن.

۳. تأیید عدم ماندن بی‌صدا

  • اگر verify-code یا oauth/token خطا داد (۴۰۰)، از قبل (رفع قبلی) پیام toast نمایش داده می‌شود. مطمئن شو مسیر userinfo هم در خطا، setIsError(true) و پیام می‌دهد، نه سکوت.

نکات مهم

  • هیچ تغییری در backend لازم نیست. پاسخ oauth/userinfo درست است؛ فقط استخراج سمت کلاینت غلط بود.
  • interceptor: همه‌ی request.* بدنه‌ی HTTP را یک‌بار باز می‌کنند → برای {success, data} داده در res.data. این الگو را با pitfallهای قبلی (پروفایل، بیمه) یکدست نگه‌دار.
  • شکل کوکی userInfo: باید شیء کاربر باشد (نه {success, data})؛ مصرف‌کننده‌های متعدد (appointment, sidebar, dashboard, lib/auth.getUser) به این متکی‌اند. تغییر این شکل را در همه‌ی مصرف‌کننده‌ها بررسی کن.
  • دو مسیر استفاده از SendReq: صفحه‌ی مستقل /login (RegisterPage با setStep={false} → ریدایرکت به /) و فلوی نوبت (setStep تابع است → setStep(3)). هر دو باید بعد از رفع کار کنند. هر دو را تست کن.
  • dev OTP: کد ثابت 12345 است؛ هنگام تست با همین وارد شو.
  • rate-limit: backend روی send-code rate-limit دارد؛ اگر حین تست ERR_RATE_LIMIT_001 گرفتی، چند دقیقه صبر کن یا شماره‌ی متفاوت بزن.
  • تست: npm run build؛ سپس دستی روی /login: موبایل + 12345 → باید کوکی‌های access_token/refresh_token/uuid/userInfo ست شوند و به / ریدایرکت شود. سپس commit با پیام توصیفی.