parity(api,apps): delivery-mode gaps — master OTP, non-blocking SMS, faToEn, v2 cart cap
D20: OTP_MASTER_CODE login bypass read only from the environment, disabled when unset, audited on every use, with a boot banner while armed. SMS no longer aborts the boot in production — it warns and declines silently, so no message can stop an operation. D19: port Utility::faTOen onto admin-entered mobiles (before the uniqueness lookup, which could previously be bypassed with Persian digits), and the create-only welcome SMS, byte-identical to the reference and fire-and-forget. user-app-v2 was missing the cart stock ceiling entirely: capQty/maxQty, the un-orderable guard and the qty clamp are ported, leaving v2's own design intact. The two cart stores are now byte-identical. isVariantOrderable and the orderable-first default selection ported too. jest 45/45; typecheck clean on api, admin-panel, user-app, user-app-v2. [ref: henza_server/app/Http/Controllers/Adm/AdminController.php:333,348,364,378-387] [ref: henza_server/app/Models/Utility.php:26] Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
2194b3d4d9
commit
460cf276b6
@@ -4,6 +4,7 @@ import * as bcrypt from 'bcryptjs';
|
||||
import { PrismaService } from '../../prisma/prisma.service';
|
||||
import { TableService } from '../admin-table/table.service';
|
||||
import type { TableQueryDto } from '../admin-table/table-query.dto';
|
||||
import { faToEn } from '../customer-auth/digits';
|
||||
|
||||
/**
|
||||
* Admin users and the permission matrix — legacy views/user/*, served by
|
||||
@@ -171,7 +172,11 @@ export class AdminUsersService {
|
||||
const firstName = String(data.fname ?? data.name ?? '').trim();
|
||||
const lastName = String(data.lname ?? '').trim();
|
||||
const fullName = [firstName, lastName].filter(Boolean).join(' ');
|
||||
const mobile = String(data.mobile ?? '').trim();
|
||||
// D19 gap 2 — normalise Persian digits, as the reference does with
|
||||
// Utility::faTOen (AdminController.php:333,364). Done BEFORE the
|
||||
// uniqueness lookup below: otherwise ۰۹۱۲… and 0912… are different strings
|
||||
// and the same number could be registered twice.
|
||||
const mobile = faToEn(String(data.mobile ?? '').trim());
|
||||
const password = String(data.password ?? '');
|
||||
const role = data.role as AdminRole | undefined;
|
||||
|
||||
@@ -260,7 +265,8 @@ export class AdminUsersService {
|
||||
}
|
||||
|
||||
if (data.mobile !== undefined) {
|
||||
const mobile = String(data.mobile);
|
||||
// Normalised before the clash check, for the same reason as create().
|
||||
const mobile = faToEn(String(data.mobile));
|
||||
if (mobile !== existing.mobile) {
|
||||
const clash = await this.prisma.adminUser.findUnique({ where: { mobile } });
|
||||
if (clash) throw new BadRequestException('این شماره موبایل قبلاً ثبت شده است');
|
||||
|
||||
Reference in New Issue
Block a user