Files
baodev0andClaude Sonnet 5 12d5b4951c feat: popup quảng cáo, đặt lại mật khẩu bằng token 80 ký tự, 2FA email, vá lỗ hổng bảo mật
- Popup/banner quảng cáo cấu hình từ admin (loại, link, lịch hiển thị)
- Đặt lại mật khẩu: token 80 ký tự kèm check digit Luhn thay cho token+email lộ trong URL, chỉ lưu bản băm, một lần dùng, hết hạn 60 phút
- Xác thực 2 lớp qua email khi đăng nhập (mã 6 số, ghi nhớ thiết bị 90 ngày)
- ApiAuthCheck chặn ngay tài khoản bị khoá dù JWT còn hạn (trước đây dùng được tới hết TTL)
- Handler trả 404 thay vì 500 khi ModelNotFoundException không bọc HttpException
- Test ClientTamperingTest: xác nhận IDOR/giả mạo dữ liệu từ client đều bị chặn

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-13 13:23:33 +07:00

91 lines
6.3 KiB
PHP

<?php
namespace Database\Seeders\EmailTemplates;
use App\Support\EmailTemplateShell as T;
final class AccountTemplates
{
/**
* @return array<int, array{scenario: string, name: string, subject: string, body: string}>
*/
public static function all(): array
{
return [
[
'scenario' => 'verify_account_welcome',
'name' => 'Mã xác thực tài khoản',
'subject' => '[{{app_name}}] Mã xác thực tài khoản của bạn',
'body' => T::title('Xác thực tài khoản')
. T::greeting('{{full_name}}')
. T::paragraph('Cảm ơn Quý khách đã đăng ký tài khoản tại <strong>{{app_name}}</strong>. Vui lòng nhập mã xác thực dưới đây để hoàn tất đăng ký:')
. T::highlight('{{verify_code}}')
. T::paragraph('Mã xác thực có hiệu lực trong <strong>5 phút</strong>. Nếu Quý khách không thực hiện yêu cầu này, vui lòng bỏ qua email.')
. T::note('Không chia sẻ mã này cho bất kỳ ai, kể cả nhân viên Thế Giới Số.'),
],
[
'scenario' => 'customer_password_reset_link',
'name' => 'Link đặt lại mật khẩu khách hàng',
'subject' => 'Yêu cầu đặt lại mật khẩu',
'body' => T::title('Đặt lại mật khẩu')
. T::greeting('{{full_name}}')
. T::paragraph('Chúng tôi nhận được yêu cầu đặt lại mật khẩu cho tài khoản của Quý khách. Vui lòng bấm nút bên dưới để tạo mật khẩu mới. Link có hiệu lực trong <strong>{{expire_minutes}} phút</strong> và chỉ dùng được một lần.')
. T::button('{{reset_url}}', 'ĐẶT LẠI MẬT KHẨU')
. T::paragraph('Nếu Quý khách không yêu cầu, vui lòng bỏ qua email này &mdash; mật khẩu hiện tại vẫn giữ nguyên. Mọi thắc mắc xin liên hệ <strong>support@tgs.com.vn</strong> hoặc <strong>(028) 7309. 7379</strong>.')
. T::note('Không chia sẻ link này cho bất kỳ ai, kể cả nhân viên Thế Giới Số.'),
],
[
'scenario' => 'customer_two_factor_code',
'name' => 'Mã xác thực 2 lớp (2FA)',
'subject' => '[{{app_name}}] Mã xác thực {{otp_purpose}}',
'body' => T::title('Mã xác thực 2 lớp')
. T::greeting('{{full_name}}')
. T::paragraph('Quý khách đang thực hiện <strong>{{otp_purpose}}</strong>. Vui lòng nhập mã dưới đây để tiếp tục:')
. T::highlight('{{verify_code}}')
. T::paragraph('Mã có hiệu lực trong <strong>{{expire_minutes}} phút</strong> và chỉ dùng được một lần. Nếu Quý khách không thực hiện thao tác này, vui lòng đổi mật khẩu ngay và liên hệ <strong>support@tgs.com.vn</strong>.')
. T::note('Không chia sẻ mã này cho bất kỳ ai, kể cả nhân viên Thế Giới Số.'),
],
[
'scenario' => 'password_reset_admin',
'name' => 'Admin cấp lại mật khẩu',
'subject' => 'Mật khẩu của bạn đã được cập nhật — {{app_name}}',
'body' => T::title('Mật khẩu đã được cập nhật')
. T::greeting('{{full_name}}')
. T::paragraph('Quản trị viên của <strong>{{app_name}}</strong> vừa cấp lại mật khẩu mới cho tài khoản của Quý khách. Vui lòng dùng thông tin bên dưới để đăng nhập:')
. T::infoTable([
'Tài khoản' => '{{email}}',
'Mật khẩu mới' => '{{password}}',
])
. T::button('{{login_url}}', 'ĐĂNG NHẬP NGAY')
. T::note('Vui lòng đổi mật khẩu ngay sau khi đăng nhập. Nếu Quý khách không yêu cầu thao tác này, hãy liên hệ ngay <strong>support@tgs.com.vn</strong>.'),
],
[
'scenario' => 'customer_welcome',
'name' => 'Chào mừng khách hàng mới',
'subject' => 'Chào mừng Quý khách đến với {{app_name}}',
'body' => T::title('Chào mừng đến với {{app_name}}')
. T::greeting('{{full_name}}')
. T::paragraph('Cảm ơn Quý khách đã tin tưởng lựa chọn <strong>{{app_name}}</strong>. Tài khoản của Quý khách đã được kích hoạt và sẵn sàng sử dụng.')
. T::paragraph('Tại cổng quản lý, Quý khách có thể đặt mua dịch vụ Hosting, Cloud Server, tên miền, theo dõi hoá đơn và gửi yêu cầu hỗ trợ bất cứ lúc nào.')
. T::button('{{login_url}}', 'ĐĂNG NHẬP NGAY')
. T::supportLine(),
],
[
'scenario' => 'ticket_created',
'name' => 'Ticket hỗ trợ mới',
'subject' => '[#{{ticket_num}}] Yêu cầu hỗ trợ của bạn đã được tiếp nhận',
'body' => T::title('Yêu cầu hỗ trợ đã được tiếp nhận')
. T::greeting('{{full_name}}')
. T::paragraph('Chúng tôi đã nhận được yêu cầu hỗ trợ của Quý khách và sẽ phản hồi trong thời gian sớm nhất. Thông tin ticket:')
. T::infoTable([
'Mã ticket' => '#{{ticket_num}}',
'Tiêu đề' => '{{subject}}',
], '{{room_row}}' . T::row('Trạng thái', '<span style="color: #16a34a;">Đang mở</span>'))
. T::button('{{ticket_url}}', 'XEM CHI TIẾT TICKET')
. T::paragraph('Đội ngũ hỗ trợ của <strong>{{app_name}}</strong> sẽ liên hệ với Quý khách qua email hoặc cập nhật trực tiếp trên hệ thống.')
. T::autoNotice(),
],
];
}
}