-web-service

This commit is contained in:
2025-11-11 15:11:56 +07:00
parent 9ad26fa5ef
commit fcf59ce5db
21 changed files with 537 additions and 253 deletions

View File

@@ -1,34 +1,23 @@
import bcrypt from 'bcrypt'
import { GeneralService } from '../share/generalservice.js'
import { generateToken } from '../utils/token.js'
// ===================================================
// 📦 LoginService Class
// ===================================================
export class LoginService {
// ===================================================
// Zone 1⃣ : Declaration & Constructor
// ===================================================
constructor() {
this.generalService = new GeneralService()
}
// ===================================================
// 🔹 Verify Login — Username/Password
// ===================================================
async verifyLogin(database, username, password) {
this.generalService.devhint(2, 'loginservice.js', `verifyLogin() start for username=${username}`)
// Zone 1⃣ : Declaration
let user = null
let token = null
// Zone 2⃣ : Query user
let sql = `
SELECT usrseq, usrnam, usrrol, usrpwd, usrthinam, usrthilstnam
FROM ${database}.usrmst
SELECT usrseq, usrnam, usrorg, usrrol, usrpwd, usrthinam, usrthilstnam
FROM nuttakit.usrmst
WHERE usrnam = $1
`
let params = [username] // ✅ ห้ามลืมเด็ดขาด
let params = [username]
const rows = await this.generalService.executeQueryParam(database, sql, params)
this.generalService.devhint(3, 'loginservice.js', `query done, found=${rows.length}`)
@@ -37,41 +26,41 @@ export class LoginService {
return null
}
// Zone 3⃣ : Validate password
user = rows[0]
const match = await bcrypt.compare(password, user.usrpwd)
if (!match) {
if (match === false) {
this.generalService.devhint(2, 'loginservice.js', 'password mismatch')
return null
}
// Zone 4⃣ : Generate JWT Token
token = generateToken({
id: user.usrseq,
name: user.usrnam,
realname: user.usrthinam,
lastname: user.usrthilstnam,
role: user.usrrol,
organization: database
organization: user.usrorg
})
this.generalService.devhint(2, 'loginservice.js', 'token generated successfully')
// Zone 5⃣ : Return Raw Result
delete user.usrseq
delete user.usrnam
delete user.usrrol
delete user.usrpwd
delete user.usrorg
return {
token,
...user
}
}
// ===================================================
// 🔹 Login ผ่าน Biometric
// ===================================================
async loginWithBiometric(database, biometric_id) {
this.generalService.devhint(2, 'loginservice.js', `loginWithBiometric() start for biometric_id=${biometric_id}`)
// Zone 1⃣ : Declaration
let sql = ''
let params = []
// Zone 2⃣ : Query
sql = `
SELECT usrid, usrnam, usrrol
FROM ${database}.usrmst
@@ -84,7 +73,6 @@ export class LoginService {
return null
}
// Zone 3⃣ : Generate Token
const user = rows[0]
const token = generateToken({
id: user.usrid,
@@ -97,17 +85,13 @@ export class LoginService {
return { token, user }
}
// ===================================================
// 🔹 Register Biometric (หลัง login)
// ===================================================
async registerBiometric(database, usrid, biometric_id) {
this.generalService.devhint(2, 'loginservice.js', `registerBiometric() start user=${usrid}`)
// Zone 1⃣ : Declaration
let sql = ''
let params = []
// Zone 2⃣ : Query
sql = `
UPDATE ${database}.usrmst
SET biometric_id = $1

View File

@@ -0,0 +1,17 @@
import { generateOTP } from '../utils/otp.js'
import { sendMockOtpMail } from '../utils/mailer.js'
import { saveOtp, verifyOtp, removeOtp } from '../utils/redis.js'
import { sendError } from '../utils/response.js'
export class OtpService {
async sendOtp(email) {
try {
const otp = generateOTP()
await saveOtp(email, otp)
await sendMockOtpMail(email, otp)
return { email, otp}
} catch (error) {
return sendError('ไม่สามารถส่ง OTP ได้', 'Failed to send OTP')
}
}
}

View File

@@ -0,0 +1,29 @@
import Redis from 'ioredis';
import crypto from 'crypto';
import { sendError } from '../utils/response.js';
import { GeneralService } from '../share/generalservice.js';
export class OTPVerifyService {
constructor() {
this.redis = new Redis();
this.generalService = new GeneralService();
}
async verifyOtp(email, otp) {
const storedOtp = await this.redis.get(`otp:${email}`);
if (!storedOtp || storedOtp !== otp) {
throw sendError('รหัส OTP ไม่ถูกต้องหรือหมดอายุ', 'Invalid OTP');
}
await this.redis.del(`otp:${email}`);
const resetToken = crypto.randomBytes(32).toString('hex');
await this.redis.set(`reset:${email}`, resetToken, 'EX', 600); // TTL 10 นาที
this.generalService.devhint(1, 'otpverifyservice.js', `OTP Verified → Reset Token issued (${email})`);
return {
resetToken:resetToken
};
}
}

View File

@@ -0,0 +1,88 @@
import Redis from 'ioredis';
import bcrypt from 'bcrypt';
import crypto from 'crypto';
import nodemailer from 'nodemailer';
import { GeneralService } from '../share/generalservice.js';
import { sendError } from '../utils/response.js';
export class RegisterService {
constructor() {
this.redis = new Redis();
this.generalService = new GeneralService();
}
async requestRegistration(database, email, fname, lname, password) {
let result = [];
try {
let sql = `
SELECT usrseq FROM ${database}.usrmst WHERE usrnam = $1
`;
let param = [email];
const userCheck = await this.generalService.executeQueryParam(database, sql, param);
if (userCheck.length > 0) {
this.generalService.devhint(1, 'registerservice.js', `❌ Duplicate email (${email})`);
throw sendError('อีเมลนี้ถูกใช้แล้ว', 'Email already registered');
}
const hashedPwd = await bcrypt.hash(password, 10);
const token = crypto.randomBytes(32).toString('hex');
const payload = JSON.stringify({ fname, lname, hashedPwd, token, database });
await this.redis.set(`verify:${email}`, payload, 'EX', 86400); // 24h
const verifyUrl = `http://localhost:1012/login/verify-email?token=${token}&email=${encodeURIComponent(email)}&organization=${database}`;
await this.sendVerifyEmail(email, verifyUrl);
this.generalService.devhint(2, 'registerservice.js', `✅ Verify link sent to ${email}`);
result = {
code: '200',
message_th: 'ส่งลิงก์ยืนยันอีเมลแล้ว',
data: {}
};
} catch (error) {
this.generalService.devhint(1, 'registerservice.js', '❌ Registration Error', error.message);
throw error;
}
return result;
}
async sendVerifyEmail(email, verifyUrl) {
try {
const transporter = nodemailer.createTransport({
service: 'gmail',
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS,
},
});
const html = `
<div style="font-family: sans-serif;">
<h2>ยืนยันการสมัครสมาชิก</h2>
<p>กรุณากดยืนยันภายใน 24 ชั่วโมง เพื่อเปิดใช้งานบัญชีของคุณ</p>
<a href="${verifyUrl}"
style="display:inline-block;background:#0078d4;color:white;
padding:10px 20px;text-decoration:none;border-radius:5px;">ยืนยันอีเมล</a>
<p style="margin-top:16px;font-size:13px;color:#555;">หากคุณไม่ได้สมัคร โปรดละเว้นอีเมลนี้</p>
</div>
`;
await transporter.sendMail({
from: `"System" <${process.env.SMTP_USER}>`,
to: email,
subject: '📩 ยืนยันอีเมลสำหรับสมัครสมาชิก',
html,
});
this.generalService.devhint(2, 'registerservice.js', `📤 Verification email sent (${email})`);
} catch (error) {
this.generalService.devhint(1, 'registerservice.js', '❌ Email Send Failed', error.message);
throw sendError('ไม่สามารถส่งอีเมลได้', 'Email send failed');
}
}
}

View File

@@ -0,0 +1,38 @@
import Redis from 'ioredis';
import bcrypt from 'bcrypt';
import { sendError } from '../utils/response.js';
import { GeneralService } from '../share/generalservice.js';
export class ResetPasswordService {
constructor() {
this.redis = new Redis();
this.generalService = new GeneralService();
}
async resetPassword(email, token, newPassword) {
let database = '';
const storedToken = await this.redis.get(`reset:${email}`);
if (!storedToken || storedToken !== token) {
throw sendError('Token ไม่ถูกต้องหรือหมดอายุ', 'Invalid or expired token');
}
await this.redis.del(`reset:${email}`);
// อัปเดตรหัสผ่านในฐานข้อมูลจริง
const hashedPwd = await bcrypt.hash(newPassword, 10);
let sql = `
UPDATE usrmst SET usrpwd = $1 WHERE usrnam = $2
`
let param = [hashedPwd, email];
await this.generalService.executeQueryParam(database, sql, param);
this.generalService.devhint(1, 'resetpasswordservice.js', `Password reset successful (${email})`);
return {
code: '200',
message: 'successful',
message_th: 'รีเซ็ตรหัสผ่านสำเร็จ'
};
}
}

View File

@@ -0,0 +1,13 @@
import { executeQueryParam } from '../share/generalservice.js'
export const userService = {
async createUser(database, usrnam, usreml) {
const sql = `
SELECT * FROM ${database}.usrmst
WHERE usrnam = $1 OR usreml = $2
`
const params = [usrnam, usreml]
const result = await executeQueryParam(sql, database, params)
return result
},
}

View File

@@ -0,0 +1,66 @@
import Redis from 'ioredis';
import { GeneralService } from '../share/generalservice.js';
import { sendError } from '../utils/response.js';
export class VerifyEmailService {
constructor() {
this.redis = new Redis();
this.generalService = new GeneralService();
}
async verifyAndCreate({ email, token, schema = 'nuttakit' }) {
// ✅ STEP 1: โหลด payload จาก Redis
const key = `verify:${email}`;
const stored = await this.redis.get(key);
if (!stored) {
throw sendError('ลิงก์หมดอายุหรือไม่ถูกต้อง', 'Verification link expired or invalid', 400);
}
let parsed;
try {
parsed = JSON.parse(stored);
} catch (ex) {
await this.redis.del(key).catch(() => {});
throw sendError('ข้อมูลการยืนยันไม่ถูกต้อง', 'Invalid verify payload', 400);
}
// ✅ STEP 2: ตรวจสอบ token
if (parsed.token !== token) {
throw sendError('Token ไม่ถูกต้อง', 'Invalid token', 400);
}
// ✅ STEP 3: ตรวจสอบว่าอีเมลนี้เคยถูกสร้างใน schema แล้วหรือยัง
const checkSql = `
SELECT usrseq FROM \${database}.usrmst WHERE usrnam = $1
`;
const checkResult = await this.generalService.executeQueryParam(schema, checkSql, [email]);
if (checkResult && checkResult.length > 0) {
await this.redis.del(key).catch(() => {});
throw sendError('อีเมลนี้ถูกใช้แล้วในองค์กรนี้', 'Email already registered in this organization', 400);
}
// ✅ STEP 4: Insert ข้อมูลลงในตารางจริง
const insertSql = `
INSERT INTO ${database}.usrmst (usrnam, usrthinam, usrthilstnam, usrpwd, usrrol)
VALUES ($1, $2, $3, $4, 'U')
`;
const params = [email, parsed.fname, parsed.lname, parsed.hashedPwd];
await this.generalService.executeQueryParam(schema, insertSql, params);
// ✅ STEP 5: ลบ Redis Key (เคลียร์ payload)
await this.redis.del(key).catch(() => {});
this.generalService.devhint(2, 'verifyemailservice.js', `✅ Account verified (${email})`);
// ✅ STEP 6: ส่งผลลัพธ์กลับ
return {
code: '200',
message_th: 'ยืนยันอีเมลสำเร็จ บัญชีถูกสร้างแล้ว',
data: {
email,
schema,
},
};
}
}