Files
clinicpro/config/packages/security.yaml
T
hamedandClaude Opus 4.8 af125572c9 feat(doctor): complete IRIMC import feature — claim flow, least-privilege importer, unique import key
- Extract import logic from AdminApiController into DoctorImportService
  (thin DoctorImportController keeps the same route/contract)
- Surrogate users get marker role ROLE_UNCLAIMED_DOCTOR (+ backfill command
  app:doctors:backfill-surrogate-role) enabling safe deletion after claim
- DB-level UNIQUE (source, medical_system_code) + concurrent-import retry
- Doctor profile claim flow (climed.md): shahkar + PersonInfo identity checks
  via existing ApiIrService, Persian name normalization (PersianText),
  pessimistic-lock race protection, DoctorClaimRequest audit table
  (national code hashed, mobile masked), doctor_claim rate limiter,
  public claim-info endpoint, welcome SMS
- Admin support tools: manual transfer endpoint + paginated doctor-claims
  audit list + owner_status filter/fields in admin doctors list
- Least privilege: system owner now gets ROLE_IMPORTER (ROLE_ADMIN stripped),
  import endpoint accepts ADMIN|IMPORTER, isStaff includes IMPORTER
- Headless crawler login: X-Service-Token header bypasses captcha only
  (rate limit + password checks intact; empty env = no bypass)
- docs: doctor-claim.md (new), doctor-import.md, admin.md, doctor.md
- tests: DoctorImportTest (6), DoctorClaimTest (11), PersianTextTest (5)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 11:39:15 +03:30

105 lines
5.0 KiB
YAML

security:
password_hashers:
App\Auth\Entity\User:
algorithm: auto
Symfony\Component\Security\Core\User\InMemoryUser:
algorithm: bcrypt
providers:
app_user_provider:
entity:
class: App\Auth\Entity\User
property: mobileNumber
api_doc_provider:
memory:
users:
'%env(API_DOC_USERNAME)%':
password: '%env(API_DOC_PASSWORD)%'
roles: ['ROLE_ADMIN']
firewalls:
dev:
pattern: ^/(_profiler|_wdt|assets|build)/
security: false
health:
pattern: ^/health$
security: false
api_doc:
pattern: ^/api/doc
http_basic:
realm: "ClinicPro API Documentation"
provider: api_doc_provider
public_endpoints:
pattern: ^/(api/v1/altcha/(challenge|config)$|api/v1/user/(send-code|verify-code|register|otp-login|reset-password)|oauth/token$|session/token|api/v1/categorys/|api/v1/doctors$|api/v1/clinics$|api/v1/clinic/doctor-list/|api/v1/clinic/[^/]+/addresses$|api/v1/clinic-pro/doctor-addresses/|api/v1/appointment-slots|api/v1/appointment-settings/month-availability/|api/v1/comments/|api/v1/rate/[^/]+$|api/v1/specialties|api/v1/blogs$|api/v1/tags$|api/v1/clinic-invitation/|api/v1/pre-registration$|api/v1/doctor/[^/]+/claim-info$)
stateless: true
security: false
payment_callback:
pattern: ^/api/v1/(payment/(callback|pay|order)/|subscription-payment/callback/)
stateless: true
security: false
api:
pattern: ^/(api|oauth|file/upload)/
stateless: true
provider: app_user_provider
custom_authenticators:
- App\Auth\Security\PasswordAuthenticator
jwt: ~
access_control:
- { path: ^/health$, roles: PUBLIC_ACCESS }
- { path: ^/api/doc, roles: ROLE_ADMIN }
- { path: ^/api/v1/user/send-code, roles: PUBLIC_ACCESS }
- { path: ^/api/v1/user/verify-code, roles: PUBLIC_ACCESS }
- { path: ^/api/v1/user/register, roles: PUBLIC_ACCESS }
- { path: ^/api/v1/user/login, roles: PUBLIC_ACCESS }
- { path: ^/api/v1/user/otp-login, roles: PUBLIC_ACCESS }
- { path: ^/api/v1/user/reset-password, roles: PUBLIC_ACCESS }
- { path: ^/api/v1/appointment-slots, roles: PUBLIC_ACCESS }
- { path: ^/api/v1/appointment-settings/month-availability/, roles: PUBLIC_ACCESS }
- { path: ^/api/v1/comments/, roles: PUBLIC_ACCESS }
- { path: ^/api/v1/site-context$, methods: [GET], roles: PUBLIC_ACCESS }
- { path: ^/api/v1/specialties, methods: [GET], roles: PUBLIC_ACCESS }
- path: '^/api/v1/rate/[^/]+$'
methods: [GET]
roles: PUBLIC_ACCESS
- { path: ^/api/v1/blogs$, roles: PUBLIC_ACCESS }
- path: '^/api/v1/blog/[^/]+$'
methods: [GET]
roles: PUBLIC_ACCESS
- { path: ^/oauth/token$, roles: PUBLIC_ACCESS }
- { path: ^/oauth/token/refresh$, roles: PUBLIC_ACCESS }
- { path: ^/session/token, roles: PUBLIC_ACCESS }
- { path: ^/api/v1/payment/callback/, roles: PUBLIC_ACCESS }
- { path: ^/api/v1/payment/pay/, roles: PUBLIC_ACCESS }
- { path: ^/api/v1/payment/order/, roles: PUBLIC_ACCESS }
- { path: ^/api/v1/subscription-payment/callback/, roles: PUBLIC_ACCESS }
- { path: ^/api/v1/categorys/, roles: PUBLIC_ACCESS }
- { path: ^/api/v1/doctors$, roles: PUBLIC_ACCESS }
- path: '^/api/v1/doctor/[^/]+$'
methods: [GET]
roles: PUBLIC_ACCESS
- { path: ^/api/v1/clinic/doctor-list/, roles: PUBLIC_ACCESS }
- { path: '^/api/v1/clinic/[^/]+/addresses$', roles: PUBLIC_ACCESS }
- { path: ^/api/v1/clinic-pro/doctor-addresses/, roles: PUBLIC_ACCESS }
- { path: ^/api/v1/clinics$, roles: PUBLIC_ACCESS }
- path: '^/api/v1/clinic/[^/]+$'
methods: [GET]
roles: PUBLIC_ACCESS
- { path: ^/api/v1/clinic-invitation/, roles: PUBLIC_ACCESS }
- { path: ^/api/v1/user/\d+$, methods: [DELETE], roles: ROLE_ADMIN }
- { path: ^/api, roles: IS_AUTHENTICATED_FULLY }
- { path: ^/oauth/userinfo, roles: IS_AUTHENTICATED_FULLY }
- { path: ^/oauth/logout, roles: IS_AUTHENTICATED_FULLY }
when@test:
security:
password_hashers:
App\Auth\Entity\User:
algorithm: auto
cost: 4