Ports "تکمیل پروفایل" end to end (D22). API - POST customer/me/complete-profile — three required document ids, the customer taken from the token, verification step set to the reference's LIVE value (approved outright; its `= 1` line is commented out at :83 in favour of `= 2` at :85). Both SMS messages byte-identical and non-blocking per D20. - POST customer/me/documents — the reference let the app upload through the same file/create the admin used, inside its auth:api-customer group (routes/api.php:95); the target's copy is admin-guarded, so the page could not have uploaded at all. Reuses AdminUploadsService unchanged, so both paths share one size limit, MIME whitelist and naming scheme (UPL-01). - PROFILE_ADMIN_MOBILES replaces the reference's four hardcoded personal numbers; blank means nobody is notified and the upload still succeeds. Three reference behaviours deliberately NOT ported, all in ISSUES.md: - SEC-14: `$customer->update($request->all())` mass-assigned the whole request body, letting the app write its own credit limit, making charge, gold debt, active flag or mobile. Replaced with a three-field whitelist. - SEC-15: four real personal mobiles hardcoded in the source. - APP-03: refresh()'s unterminated setInterval page reload. Apps (both, template+script byte-identical, per-app styling only) - CompleteProfileView + SingleImageUploader - route /complete_profile, redirecting home once the documents are in, as the reference's guard did (router/index.ts:266) - the entry point on the profile screen, shown only while they are missing (profile.vue:96-102) Also (D23): JWT_CUSTOMER_SECRET now warns loudly at boot when unset. It falls back to JWT_SECRET, which signs app and admin tokens with the same key and leaves the `typ` check as the only barrier — and an expert's app token carries their AdminUser id in `sub`. Warn rather than refuse to boot (D19, lowest risk); DELIVERY.md lists it as the first pre-release step (REL-03). Verified: build 5/5 · jest 45/45 · complete-profile 38/38 (new) · expert login 36/36 · apps smoke 43/43 · admin smoke 43/43 · B2B scan 34 ok/0 broken · typecheck clean on all four apps. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
40 lines
1.4 KiB
TypeScript
40 lines
1.4 KiB
TypeScript
import { IsNotEmpty, IsString, MaxLength } from 'class-validator';
|
|
|
|
/**
|
|
* Body of POST customer/me/complete-profile.
|
|
*
|
|
* The three ids are the reference's exactly
|
|
* (henza_server Major/CustomerController::complete:73-79):
|
|
*
|
|
* 'business_license_image_id' => 'required',
|
|
* 'shop_image_id' => 'required',
|
|
* 'ncode_image_id' => 'required',
|
|
*
|
|
* and the PWA sends those same three keys (completeProfile.vue:136-140, :193).
|
|
* They are kept verbatim rather than camelCased so the app's payload is
|
|
* byte-identical to the reference's.
|
|
*
|
|
* Deliberately NOT accepted: anything else. The reference did
|
|
* `$customer->update($request->all())` (:87), so ANY column the caller named —
|
|
* `gram_level`, `percent`, `active`, `remain` — was written straight onto the
|
|
* customer row from the app. That is a privilege-escalation and money hole, not
|
|
* a feature, so only these three ids are read (whitelist, not mass-assign).
|
|
* Recorded in ISSUES.md as SEC-14.
|
|
*/
|
|
export class CompleteProfileDto {
|
|
@IsString()
|
|
@IsNotEmpty({ message: 'تصویر جواز کسب الزامی است' })
|
|
@MaxLength(64)
|
|
business_license_image_id!: string;
|
|
|
|
@IsString()
|
|
@IsNotEmpty({ message: 'تصویر سردر مغازه الزامی است' })
|
|
@MaxLength(64)
|
|
shop_image_id!: string;
|
|
|
|
@IsString()
|
|
@IsNotEmpty({ message: 'تصویر کارت ملی الزامی است' })
|
|
@MaxLength(64)
|
|
ncode_image_id!: string;
|
|
}
|