Files
clinicpro/docs/api/auth.md
T

19 KiB
Raw Blame History

Authentication API

Prefix: /api/v1/user and /oauth
Permission: All endpoints in this module are PUBLIC (no JWT required) except userinfo and logout


POST /api/v1/user/send-code

Send OTP code to mobile number.

Permission: PUBLIC

Request Body

{
  "mobile": "09123456789"
}
Field Type Required Validation
mobile string Format: 09XXXXXXXXX (11 digits)

Response 200

{
  "success": true,
  "data": {
    "uuid": "550e8400-e29b-41d4-a716-446655440000",
    "message": "کد تأیید ارسال شد"
  }
}

Errors

Code HTTP Description
ERR_VALIDATION_001 422 Invalid mobile format
ERR_AUTH_004 429 OTP rate limit exceeded
ERR_RATE_LIMIT_001 429 بیش از حد مجاز — هم per-IP (۵ در ۶۰ دقیقه) و هم per-mobile (۵ در ۶۰ دقیقه؛ ضد flood از IPهای چرخشی)

POST /api/v1/user/verify-code

Verify OTP code. Returns whether this is a new or existing user.

Permission: PUBLIC

Request Body

{
  "uuid": "550e8400-e29b-41d4-a716-446655440000",
  "code": "123456"
}
Field Type Required Description
uuid string UUID returned from send-code
code string 6-digit OTP

Response 200

{
  "success": true,
  "data": {
    "message": "کد با موفقیت تایید شد.",
    "grant": "ddf8a5994d6a2768203f15606621e3fa0e968438765e22061721a03d336d4039",
    "is_new_user": false
  }
}

grant یک توکنِ یک‌بارمصرف و کوتاه‌عمر (۱۲۰ ثانیه) است که در مرحله‌ی بعد (register / oauth/token / otp-login / reset-password) مصرف می‌شود. خودِ OTP (uuid) پس از تأیید موفق حذف می‌شود. هر grant فقط یک‌بار قابل‌استفاده است؛ هر action که grant می‌خواهد یک grant جداگانه لازم دارد (یعنی برای new-user که هم register و هم login لازم است، باید دوبار verify انجام شود یا مستقیماً oauth/token صدا زده شود که خودش کاربر را می‌سازد).

Errors

Code HTTP Description
ERR_AUTH_002 400 Invalid OTP code
ERR_AUTH_003 400 OTP expired
ERR_VALIDATION_002 422 Missing required field
ERR_RATE_LIMIT_001 429 بیش از ۱۰ تلاش در ۱۵ دقیقه (per-IP)

POST /api/v1/user/register

Complete registration for new users (called only when is_new_user: true).

Permission: PUBLIC

Request Body

{
  "grant": "ddf8a5994d6a2768203f15606621e3fa0e968438765e22061721a03d336d4039",
  "real_name": "علی احمدی"
}
Field Type Required Description
grant string grant یک‌بارمصرف از verify-code
real_name string User's full name

Response 201

{
  "success": true,
  "data": {
    "message": "ثبت‌نام با موفقیت انجام شد",
    "uuid": "550e8400-e29b-41d4-a716-446655440000"
  }
}

Errors

Code HTTP Description
ERR_VALIDATION_002 422 Missing grant
ERR_AUTH_002 400 grant نامعتبر یا منقضی/مصرف‌شده

POST /api/v1/user/login

Login with mobile number and password (for users who set a password).

Permission: PUBLIC

Request Body

{
  "mobile_number": "09123456789",
  "password": "mypassword"
}
Field Type Required
mobile_number string
password string

Response 200

{
  "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9...",
  "refresh_token": "def50200...",
  "token_type": "Bearer",
  "expires_in": 900,
  "refresh_token_expires_in": 2592000
}

فقط برای کاربران staff (ROLE_ADMIN/DOCTOR/CLINIC/SECRETARY). Access token TTL: ۹۰۰ ثانیه (۱۵ دقیقه).

Errors

Code HTTP Description
ERR_AUTH_005 401 Wrong credentials
ERR_AUTH_006 403 کاربر staff نیست
429 429 Too many attempts (۱۰ در دقیقه per-IP)

POST /oauth/token

Exchange a one-time grant (from verify-code) for a JWT access token. اگر کاربری با آن موبایل وجود نداشته باشد، ساخته می‌شود.

Permission: PUBLIC

Request Body

{
  "grant_type": "mobile",
  "grant": "ddf8a5994d6a2768203f15606621e3fa0e968438765e22061721a03d336d4039"
}
Field Type Required Description
grant_type string Must be "mobile"
grant string grant یک‌بارمصرف از verify-code (عمر ۱۲۰ ثانیه)

Response 200

{
  "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9...",
  "refresh_token": "def50200...",
  "token_type": "Bearer",
  "expires_in": 900,
  "refresh_token_expires_in": 2592000
}

JWT payload: { username: mobile_number, roles: [...], iat, exp }
Access token TTL: ۹۰۰ ثانیه (۱۵ دقیقه) | Refresh token TTL: ۳۰ روز (در cache، هش‌شده و rotate-on-use)

Errors

Code HTTP Description
ERR_VALIDATION_001 400 grant_type نامعتبر
ERR_VALIDATION_002 422 grant ارسال نشده
ERR_AUTH_002 400 grant نامعتبر، منقضی یا قبلاً مصرف‌شده
ERR_RATE_LIMIT_001 429 بیش از ۱۰ درخواست در ۵ دقیقه (per-IP)

POST /oauth/token/refresh

Refresh expired JWT using refresh token.

Permission: PUBLIC

Request Body

{
  "refresh_token": "def50200..."
}

قابل استفاده‌ی مجدد: refresh token تا انقضای TTL خود معتبر است و در پاسخ بدون تغییر برگردانده می‌شود (یک‌بارمصرف/چرخشی نیست — سایت عمومی در هر render سمت‌سرور refresh می‌زند و نمی‌تواند توکن چرخش‌یافته را ذخیره کند). کاربر معلق (status != 1) نمی‌تواند refresh کند (401).

Response 200

{
  "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9...",
  "refresh_token": "<همان توکن ارسالی — بدون تغییر>",
  "token_type": "Bearer",
  "expires_in": 900,
  "refresh_token_expires_in": 2592000
}

Errors

Code HTTP Description
ERR_AUTH_001 401 Invalid/expired refresh token, or suspended user (status != 1)

GET /oauth/userinfo

Get authenticated user info — extended with multi-context support.

Permission: AUTH — requires valid JWT

Headers

Authorization: Bearer <token>

Response 200

{
  "success": true,
  "data": {
    "id": 4766,
    "uuid": "550e8400-e29b-41d4-a716-446655440000",
    "mobile_number": "09123456789",
    "realName": "دکتر وحید درویشی",
    "status": 1,
    "roles": ["ROLE_USER", "ROLE_DOCTOR"],
    "primary_role": "doctor",
    "db_uuid": "a6ef5d29-38b8-4e69-b1ef-27a304696966",
    "db_key": "hmac-sha256-hash...",
    "doctor_uuid": "a6ef5d29-38b8-4e69-b1ef-27a304696966",
    "context": {
      "type": "doctor",
      "db_uuid": "a6ef5d29-38b8-4e69-b1ef-27a304696966",
      "name": "مطب شخصی دکتر وحید درویشی",
      "role": "doctor"
    },
    "available_contexts": [
      {
        "type": "doctor",
        "db_uuid": "a6ef5d29-38b8-4e69-b1ef-27a304696966",
        "name": "مطب شخصی دکتر وحید درویشی",
        "role": "doctor"
      },
      {
        "type": "clinic",
        "db_uuid": "clinic-uuid-...",
        "name": "کلینیک سلامت",
        "role": "clinic",
        "doctor_uuid": "a6ef5d29-38b8-4e69-b1ef-27a304696966"
      }
    ]
  }
}
فیلد نوع توضیح
primary_role string نقش اصلی: admin | clinic | doctor | secretary | representation | user
db_uuid string|null UUID موجودیت فعال (null = هنوز context انتخاب نشده)
db_key string|null HMAC-SHA256(db_uuid, APP_SECRET) برای اعتبارسنجی
doctor_uuid string|null UUID دکتر — ثابت است حتی در context کلینیک که db_uuid برابر UUID کلینیک است. برای کاربران غیر دکتر: null
context object|null context فعال انتخاب‌شده
available_contexts array همه محیط‌های کاری قابل انتخاب

قانون primary_role (اولویت‌بندی):

  • ROLE_ADMIN"admin"
  • ROLE_CLINIC"clinic"
  • ROLE_DOCTOR"doctor"
  • ROLE_SECRETARY"secretary"
  • ROLE_REPRESENTATION"representation" (نماینده؛ دسترسی محدود به پنل ادمین: افزودن پزشک/کلینیک، نوبت‌های پزشکانِ زیرمجموعه، داشبورد نماینده)
  • بقیه → "user"

قانون context.role — نقشی که در آن محیط کاری فعال است:

  • context مطب شخصی دکتر: "doctor"
  • context کلینیک که دکتر عضو آن است (مالک نیست): "doctor" + "scope": "clinic" — پزشک می‌ماند و فقط نوبت‌های خودش در آن کلینیک را می‌بیند؛ دسترسی مدیریتی پنل کلینیک ندارد
  • context کلینیک که دکتر صاحب آن است: "clinic" (دسترسی کامل مالک)
  • context منشی: "secretary"

نکته frontend: پس از switchContext، primaryRole در store از context.role و scope از context.scope آپدیت می‌شود. وقتی role:"doctor" و scope:"clinic" است (پزشکِ مهمان)، Sidebar فقط «داشبورد» و «نوبت‌ها» را نشان می‌دهد و مسیرهای مدیریتی (staff, clinic-services, subscription, my-secretaries, my-patients, profile) به داشبورد ریدایرکت می‌شوند. در سمت backend هم endpointهای مدیریتی برای پزشک فقط scope شخصیِ خودش را برمی‌گردانند (نه کلینیک) و endpointهای ویرایش کلینیک مالکیت را چک می‌کنند (۴۰۳).

قانون db_uuid:

  • اگر یک context وجود دارد: خودکار فعال می‌شود
  • اگر چند context وجود دارد و کاربر هنوز انتخاب نکرده: null — frontend باید صفحه انتخاب نشان دهد
  • پس از POST /api/v1/auth/switch-context: برابر context انتخاب‌شده

Errors

Code HTTP Description
ERR_AUTH_001 401 Invalid or missing token

POST /api/v1/auth/switch-context

تغییر محیط کاری فعال — باید از لیست available_contexts انتخاب شود.

Permission: AUTH

Request Body

{
  "db_uuid": "clinic-uuid-..."
}
فیلد نوع Required توضیح
db_uuid string (UUID) UUID محیط کاری از لیست available_contexts

Response 200

{
  "success": true,
  "data": {
    "db_uuid": "clinic-uuid-...",
    "db_key": "new-hmac-hash...",
    "context": {
      "type": "clinic",
      "db_uuid": "clinic-uuid-...",
      "name": "کلینیک سلامت",
      "role": "clinic",
      "doctor_uuid": "a6ef5d29-38b8-4e69-b1ef-27a304696966"
    }
  }
}

Errors

Code HTTP Description
ERR_AUTH_001 401 Missing or invalid token
ERR_AUTH_006 403 db_uuid در لیست context های این کاربر نیست
ERR_VALIDATION_001 422 db_uuid ارسال نشده

Notification Mobile (OTP)

Endpoints for setting/verifying a separate SMS notification number for doctors and clinics. This number receives appointment SMS notifications instead of the account login mobile.

Permission: All 4 endpoints require IS_AUTHENTICATED_FULLY (JWT).


POST /api/v1/notification-mobile/request-otp

Request an OTP code to verify a new notification mobile number.

Permission: AUTH

Request Body

{
  "target": "doctor",
  "new_mobile": "09123456789"
}
Field Type Required Validation
target string doctor or clinic
new_mobile string Format: 09XXXXXXXXX

Response 200

{
  "success": true,
  "data": {
    "message": "کد تأیید ارسال شد",
    "expires_in": 300
  }
}

Errors

Code HTTP Description
ERR_VALIDATION_001 422 Invalid target or mobile format
ERR_NOT_FOUND_001 404 No doctor/clinic profile found for this user

Notes

  • Previous unused OTPs for the entity are deleted before creating a new one
  • OTP is 6-digit, expires in 5 minutes
  • Sends via SMS asynchronously

POST /api/v1/notification-mobile/verify

Verify the OTP and save the notification mobile number.

Permission: AUTH

Request Body

{
  "target": "doctor",
  "otp_code": "123456"
}
Field Type Required
target string doctor or clinic
otp_code string 6-digit code

Response 200

{
  "success": true,
  "data": {
    "notification_mobile": "09123456789",
    "message": "شماره اعلان با موفقیت ذخیره شد"
  }
}

Errors

Code HTTP Description
ERR_VALIDATION_001 422 Invalid target / OTP expired / wrong code
ERR_NOT_FOUND_001 404 No OTP request found or profile not found

GET /api/v1/notification-mobile/{target}

Get the current notification mobile for the authenticated user.

Permission: AUTH
Path param: targetdoctor or clinic

Response 200

{
  "success": true,
  "data": {
    "notification_mobile": "09123456789"
  }
}
  • Returns null for notification_mobile if not set

Errors

Code HTTP Description
ERR_NOT_FOUND_001 404 No profile found for this user

DELETE /api/v1/notification-mobile/{target}

Remove the notification mobile number.

Permission: AUTH
Path param: targetdoctor or clinic

Response 200

{
  "success": true,
  "data": {
    "message": "شماره اعلان حذف شد"
  }
}

Errors

Code HTTP Description
ERR_NOT_FOUND_001 404 No profile found for this user

POST /oauth/logout

Invalidate the refresh token (stored in Redis).

Permission: AUTH

Request Body

{
  "refresh_token": "def50200..."
}
Field Type Required
refresh_token string

Response 200

{
  "success": true,
  "data": {
    "message": "با موفقیت خارج شدید"
  }
}

POST /api/v1/pre-registration

Submit a pre-registration request (doctor or clinic). Public endpoint — no auth required.

Permission: Public

Request Body

{
  "type": "independent_doctor",
  "name": "دکتر علی احمدی",
  "mobile": "09121234567",
  "info": "متخصص داخلی، ۱۰ سال سابقه"
}
Field Type Required Notes
type string independent_doctor | doctor_with_clinic | clinic_manager
name string min 2 chars
mobile string 1015 chars
info string specialty, address, etc.

Type meanings:

Value Roles granted on approval Entities created
independent_doctor ROLE_DOCTOR Doctor
doctor_with_clinic ROLE_DOCTOR + ROLE_CLINIC Doctor + Clinic
clinic_manager ROLE_CLINIC Clinic

Response 200

{
  "success": true,
  "data": { "uuid": "...", "status": "pending" }
}

Error Codes

Code HTTP Meaning
VALIDATION_ERROR 422 Invalid type / mobile / name
DUPLICATE_REQUEST 409 Pending request already exists for this mobile

POST /api/v1/user/otp-login

ورود با کد OTP تأییدشده — فقط کاربران موجود، کاربر جدید ایجاد نمی‌شود.

Permission: PUBLIC

Request Body

{ "grant": "ddf8a5994d6a2768203f15606621e3fa0e968438765e22061721a03d336d4039" }
Field Type Required Description
grant string grant یک‌بارمصرف از verify-code

Response 200

{
  "access_token": "eyJ...",
  "refresh_token": "...",
  "token_type": "Bearer",
  "expires_in": 900,
  "refresh_token_expires_in": 2592000
}

Error Codes

Code HTTP Description
ERR_VALIDATION_002 422 grant ارسال نشده
ERR_AUTH_002 400 grant نامعتبر، منقضی یا مصرف‌شده
ERR_AUTH_005 401 کاربری با این شماره یافت نشد
ERR_RATE_LIMIT_001 429 بیش از ۱۰ درخواست در ۵ دقیقه (per-IP)

POST /api/v1/user/reset-password

تغییر رمز عبور با تأیید هویت از طریق OTP.

Permission: PUBLIC

Request Body

{
  "grant": "ddf8a5994d6a2768203f15606621e3fa0e968438765e22061721a03d336d4039",
  "new_password": "newpass1234"
}
Field Type Required Validation
grant string grant یک‌بارمصرف از verify-code
new_password string حداقل ۸ کاراکتر

Response 200

{
  "success": true,
  "data": { "message": "رمز عبور با موفقیت تغییر یافت" }
}

Error Codes

Code HTTP Description
ERR_VALIDATION_001 422 grant ارسال نشده یا رمز کمتر از ۸ کاراکتر
ERR_AUTH_002 400 grant نامعتبر، منقضی یا مصرف‌شده
ERR_NOT_FOUND_001 404 کاربری با این شماره یافت نشد
ERR_RATE_LIMIT_001 429 بیش از ۵ درخواست در ۶۰ دقیقه (per-IP)