Commit 73855b03 authored by ThinhNC's avatar ThinhNC

fix: auth service logic and validation schemas for user management

parent 5d8c61c1
...@@ -480,56 +480,60 @@ export class AuthService { ...@@ -480,56 +480,60 @@ export class AuthService {
console.warn('[ZaloAuth] fetchZaloProfile caught error:', err); console.warn('[ZaloAuth] fetchZaloProfile caught error:', err);
} }
// 2. Lấy và chuẩn hóa số điện thoại (từ phoneToken hoặc phoneNumber) // 2. Lấy và chuẩn hóa số điện thoại (từ phoneToken hoặc phoneNumber nếu có thể giải mã)
let resolvedPhone = dto.phoneNumber; let resolvedPhone = dto.phoneNumber;
if (dto.phoneToken) { if (dto.phoneToken) {
try {
const phoneResponse = await this.fetchZaloPhoneNumber(accessToken, dto.phoneToken, appSecret); const phoneResponse = await this.fetchZaloPhoneNumber(accessToken, dto.phoneToken, appSecret);
if ( if (phoneResponse && phoneResponse.data?.number) {
!phoneResponse ||
(phoneResponse.error !== undefined && phoneResponse.error !== 0) ||
!phoneResponse.data?.number
) {
console.error('[ZaloAuth] fetchZaloPhoneNumber failed:', phoneResponse);
throw new AppError(
phoneResponse?.message ? `Zalo Phone Error: ${phoneResponse.message}` : 'Failed to decode phone number from Zalo token',
401,
ERROR_CODE.INVALID_CREDENTIALS,
);
}
resolvedPhone = phoneResponse.data.number; resolvedPhone = phoneResponse.data.number;
} else if (phoneResponse?.error === -501) {
console.warn('[ZaloAuth] Zalo Phone API limited by IP location (-501). Authenticating via Zalo ID.');
} else if (phoneResponse && phoneResponse.error !== undefined && phoneResponse.error !== 0) {
console.warn('[ZaloAuth] fetchZaloPhoneNumber returned error:', phoneResponse);
}
} catch (err) {
console.warn('[ZaloAuth] fetchZaloPhoneNumber caught error:', err);
} }
if (!resolvedPhone) {
throw new AppError('Phone number is required', 400, ERROR_CODE.VALIDATION_ERROR);
} }
if (resolvedPhone) {
// Chuẩn hóa số điện thoại: +84... hoặc 84... -> 0... // Chuẩn hóa số điện thoại: +84... hoặc 84... -> 0...
resolvedPhone = resolvedPhone.replace(/^\+84/, '0').replace(/^84/, '0'); resolvedPhone = resolvedPhone.replace(/^\+84/, '0').replace(/^84/, '0');
}
// Đảm bảo có Zalo ID làm mã định danh
if (!zaloId) { if (!zaloId) {
if (resolvedPhone) {
zaloId = `zalo_${resolvedPhone}`; zaloId = `zalo_${resolvedPhone}`;
} else {
throw new AppError('Unable to identify Zalo user. Please grant basic permissions.', 400, ERROR_CODE.VALIDATION_ERROR);
}
} }
// 3. Tìm hoặc tạo user theo SĐT // 3. Tìm hoặc tạo user
let user = await this.repository.findByPhone(resolvedPhone); // A. Tìm theo liên kết mạng xã hội Zalo ID trước
let user: any = await this.repository.findBySocial('zalo', zaloId);
// B. Nếu chưa tìm thấy theo Zalo ID và có SĐT, tìm theo SĐT
if (!user && resolvedPhone) {
user = await this.repository.findByPhone(resolvedPhone);
if (user) { if (user) {
// User đã tồn tại — liên kết Zalo ID nếu chưa có
const existing = await this.repository.findBySocial('zalo', zaloId);
if (!existing) {
await this.repository.linkSocialAccount(user.id, 'zalo', zaloId); await this.repository.linkSocialAccount(user.id, 'zalo', zaloId);
} }
} else { }
// User chưa tồn tại — tạo mới từ Zalo profile
// C. Nếu user chưa tồn tại, tạo mới
if (!user) {
const role = await this.repository.findRoleByName(SYSTEM_ROLES.USER); const role = await this.repository.findRoleByName(SYSTEM_ROLES.USER);
if (!role) { if (!role) {
throw new AppError('Default role not found', 500, ERROR_CODE.INTERNAL_SERVER_ERROR); throw new AppError('Default role not found', 500, ERROR_CODE.INTERNAL_SERVER_ERROR);
} }
user = await this.repository.createSocialUser({ user = await this.repository.createSocialUser({
fullName: zaloName || undefined, fullName: zaloName || 'Người dùng Zalo',
avatarUrl: zaloAvatarUrl || undefined, avatarUrl: zaloAvatarUrl || undefined,
phoneNumber: resolvedPhone, phoneNumber: resolvedPhone || undefined,
roleId: role.id, roleId: role.id,
provider: 'zalo', provider: 'zalo',
providerUserId: zaloId, providerUserId: zaloId,
......
...@@ -22,9 +22,9 @@ export const zaloLoginSchema = z ...@@ -22,9 +22,9 @@ export const zaloLoginSchema = z
name: z.string().optional(), name: z.string().optional(),
avatar: z.string().optional(), avatar: z.string().optional(),
}) })
.refine((data) => data.phoneToken || data.phoneNumber, { .refine((data) => data.phoneToken || data.phoneNumber || data.zaloId, {
message: 'Either phoneToken or phoneNumber must be provided', message: 'Either phoneToken, phoneNumber, or zaloId must be provided',
path: ['phoneToken'], path: ['accessToken'],
}); });
export const refreshSchema = z.object({ export const refreshSchema = z.object({
...@@ -135,4 +135,3 @@ export const sessionQuerySchema = z.object({ ...@@ -135,4 +135,3 @@ export const sessionQuerySchema = z.object({
export const revokeOtherSessionsSchema = z.object({ export const revokeOtherSessionsSchema = z.object({
refreshToken: z.string().optional(), refreshToken: z.string().optional(),
}); });
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment