Commit 5d8c61c1 authored by ThinhNC's avatar ThinhNC

fix: auth service with JWT authentication, session management, and user profile functionality

parent 228e2506
......@@ -7,6 +7,9 @@ export interface ZaloLoginDto {
accessToken: string; // Zalo access_token từ getAccessToken() SDK
phoneToken?: string; // Mã token SĐT từ getPhoneNumber() SDK (giải mã phía server)
phoneNumber?: string; // SĐT thực trực tiếp (cho test/fallback)
zaloId?: string; // User ID từ getUserInfo SDK client
name?: string; // Tên user từ getUserInfo SDK client
avatar?: string; // Avatar từ getUserInfo SDK client
}
export interface ZaloPhoneResponse {
......
......@@ -449,14 +449,25 @@ export class AuthService {
const { accessToken } = dto;
const appSecret = process.env.ZALO_APP_SECRET || '';
// 1. Xác thực access_token với Zalo Graph API
// 1. Xác thực access_token và lấy thông tin Zalo profile (có fallback khi IP server ở nước ngoài)
let zaloId = dto.zaloId || '';
let zaloName = dto.name || 'Người dùng Zalo';
let zaloAvatarUrl: string | null = dto.avatar || null;
try {
const appsecretProof = crypto
.createHmac('sha256', appSecret)
.update(accessToken)
.digest('hex');
const zaloProfile = await this.fetchZaloProfile(accessToken, appsecretProof);
if (!zaloProfile || (zaloProfile.error !== undefined && zaloProfile.error !== 0) || !zaloProfile.id) {
if (zaloProfile && zaloProfile.id) {
zaloId = zaloProfile.id;
if (zaloProfile.name) zaloName = zaloProfile.name;
if (zaloProfile.picture?.data?.url) zaloAvatarUrl = zaloProfile.picture.data.url;
} else if (zaloProfile?.error === -501) {
console.warn('[ZaloAuth] Server IP is outside Vietnam (-501). Using client profile info.');
} else if (zaloProfile && zaloProfile.error !== undefined && zaloProfile.error !== 0 && !dto.phoneToken) {
console.error('[ZaloAuth] fetchZaloProfile failed:', zaloProfile);
throw new AppError(
zaloProfile?.message ? `Zalo Profile Error: ${zaloProfile.message}` : 'Invalid Zalo access token',
......@@ -464,9 +475,10 @@ export class AuthService {
ERROR_CODE.INVALID_CREDENTIALS,
);
}
const { id: zaloId, name: zaloName, picture } = zaloProfile;
const zaloAvatarUrl: string | null = picture?.data?.url || null;
} catch (err) {
if (err instanceof AppError) throw 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)
let resolvedPhone = dto.phoneNumber;
......@@ -495,6 +507,10 @@ export class AuthService {
// Chuẩn hóa số điện thoại: +84... hoặc 84... -> 0...
resolvedPhone = resolvedPhone.replace(/^\+84/, '0').replace(/^84/, '0');
if (!zaloId) {
zaloId = `zalo_${resolvedPhone}`;
}
// 3. Tìm hoặc tạo user theo SĐT
let user = await this.repository.findByPhone(resolvedPhone);
......
......@@ -18,6 +18,9 @@ export const zaloLoginSchema = z
.string()
.regex(/^(0[3|5|7|8|9])+([0-9]{8})$/, 'Invalid Vietnamese phone number format')
.optional(),
zaloId: z.string().optional(),
name: z.string().optional(),
avatar: z.string().optional(),
})
.refine((data) => data.phoneToken || data.phoneNumber, {
message: 'Either phoneToken or phoneNumber must be provided',
......
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