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 { ...@@ -7,6 +7,9 @@ export interface ZaloLoginDto {
accessToken: string; // Zalo access_token từ getAccessToken() SDK accessToken: string; // Zalo access_token từ getAccessToken() SDK
phoneToken?: string; // Mã token SĐT từ getPhoneNumber() SDK (giải mã phía server) 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) 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 { export interface ZaloPhoneResponse {
......
...@@ -449,25 +449,37 @@ export class AuthService { ...@@ -449,25 +449,37 @@ export class AuthService {
const { accessToken } = dto; const { accessToken } = dto;
const appSecret = process.env.ZALO_APP_SECRET || ''; 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)
const appsecretProof = crypto let zaloId = dto.zaloId || '';
.createHmac('sha256', appSecret) let zaloName = dto.name || 'Người dùng Zalo';
.update(accessToken) let zaloAvatarUrl: string | null = dto.avatar || null;
.digest('hex');
const zaloProfile = await this.fetchZaloProfile(accessToken, appsecretProof); try {
if (!zaloProfile || (zaloProfile.error !== undefined && zaloProfile.error !== 0) || !zaloProfile.id) { const appsecretProof = crypto
console.error('[ZaloAuth] fetchZaloProfile failed:', zaloProfile); .createHmac('sha256', appSecret)
throw new AppError( .update(accessToken)
zaloProfile?.message ? `Zalo Profile Error: ${zaloProfile.message}` : 'Invalid Zalo access token', .digest('hex');
401,
ERROR_CODE.INVALID_CREDENTIALS, const zaloProfile = await this.fetchZaloProfile(accessToken, appsecretProof);
); 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',
401,
ERROR_CODE.INVALID_CREDENTIALS,
);
}
} catch (err) {
if (err instanceof AppError) throw err;
console.warn('[ZaloAuth] fetchZaloProfile caught error:', err);
} }
const { id: zaloId, name: zaloName, picture } = zaloProfile;
const zaloAvatarUrl: string | null = picture?.data?.url || null;
// 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)
let resolvedPhone = dto.phoneNumber; let resolvedPhone = dto.phoneNumber;
...@@ -495,6 +507,10 @@ export class AuthService { ...@@ -495,6 +507,10 @@ export class AuthService {
// 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');
if (!zaloId) {
zaloId = `zalo_${resolvedPhone}`;
}
// 3. Tìm hoặc tạo user theo SĐT // 3. Tìm hoặc tạo user theo SĐT
let user = await this.repository.findByPhone(resolvedPhone); let user = await this.repository.findByPhone(resolvedPhone);
......
...@@ -18,6 +18,9 @@ export const zaloLoginSchema = z ...@@ -18,6 +18,9 @@ export const zaloLoginSchema = z
.string() .string()
.regex(/^(0[3|5|7|8|9])+([0-9]{8})$/, 'Invalid Vietnamese phone number format') .regex(/^(0[3|5|7|8|9])+([0-9]{8})$/, 'Invalid Vietnamese phone number format')
.optional(), .optional(),
zaloId: z.string().optional(),
name: z.string().optional(),
avatar: z.string().optional(),
}) })
.refine((data) => data.phoneToken || data.phoneNumber, { .refine((data) => data.phoneToken || data.phoneNumber, {
message: 'Either phoneToken or phoneNumber must be provided', 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