Merge branch 'dev' into working

This commit is contained in:
Suphonchai Phoonsawat 2026-10-26 09:02:13 +07:00
commit aad6f155fe
17 changed files with 2003 additions and 569 deletions

1263
lib/lib-tmpl.ts Executable file

File diff suppressed because it is too large Load diff

112
lib/lib-utils.ts Normal file
View file

@ -0,0 +1,112 @@
import { createHmac, createPrivateKey, createPublicKey, timingSafeEqual, verify, sign } from 'node:crypto';
/**
* คำนวณ HMAC-SHA256 จาก string หรือ JSON Object
*/
export function generateHmacSha256(data: string | object, secretKey: string): string {
const payload = typeof data === 'string' ? data : JSON.stringify(data);
return createHmac('sha256', secretKey)
.update(payload, 'utf8')
.digest('hex');
}
/**
* ตรวจสอบความถูกต้องของ HMAC (ใช้ timingSafeEqual เพื่อป้องกัน Timing Attacks)
*/
export function verifyHmacSha256(data: string | object, secretKey: string, expectedHmac: string): boolean {
const calculatedHmac = generateHmacSha256(data, secretKey);
const a = Buffer.from(calculatedHmac, 'hex');
const b = Buffer.from(expectedHmac, 'hex');
if (a.length !== b.length) {
return false;
}
return timingSafeEqual(a, b);
}
// ==========================================
// ตัวอย่างการใช้งาน
// ==========================================
/*
const secret = 'my-super-secret-key';
const jsonData = {
userId: 12345,
action: 'transfer',
amount: 500
};
// 1. สร้าง HMAC
const hmacSignature = generateHmacSha256(jsonData, secret);
console.log('HMAC Signature (Hex):', hmacSignature);
// 2. ตรวจสอบ HMAC
const isValid = verifyHmacSha256(jsonData, secret, hmacSignature);
console.log('Is Signature Valid?:', isValid); // true
*/
/**
* ฟังก์ชันสำหรับลงนามดิจิทัล (Sign) ข้อมูลด้วย Ed25519 Private Key (Hex)
* @param message ข้อความดิบที่ต้องการเซ็น (string หรือ Buffer)
* @param privateKeyHex คีย์ลับในรูปแบบ Hex String (ความยาว 64 ตัวอักษร)
* @returns ลายเซ็น (Signature) ในรูปแบบ Hex String (ความยาว 128 ตัวอักษร)
*/
export function signDataEd25519(message: string | Buffer, privateKeyHex: string): string {
const rawKeyBuffer = Buffer.from(privateKeyHex, 'hex');
// ตัดเอาเฉพาะ 32 bytes (Seed) กรณีที่ Hex ยาว 64 bytes (ที่ต่อกับ Public Key มา)
const seed = rawKeyBuffer.length === 64 ? rawKeyBuffer.subarray(0, 32) : rawKeyBuffer;
// PKCS#8 Header สำหรับ Ed25519 Private Key
const pkcs8Header = Buffer.from('302e020100300506032b657004220420', 'hex');
const pkcs8Buffer = Buffer.concat([pkcs8Header, seed]);
const privateKey = createPrivateKey({
key: pkcs8Buffer,
format: 'der',
type: 'pkcs8',
});
const dataBuffer = typeof message === 'string' ? Buffer.from(message, 'utf-8') : message;
const signatureBuffer = sign(null, dataBuffer, privateKey);
return signatureBuffer.toString('hex');
}
/**
* ฟังก์ชันสำหรับตรวจสอบลายเซ็น (Verify) ด้วย Ed25519 Public Key (Hex)
* @param message ข้อความดิบที่ใช้ในการเซ็น (string หรือ Buffer)
* @param signatureHex ลายเซ็นในรูปแบบ Hex String
* @param publicKeyHex คีย์สาธารณะในรูปแบบ Hex String (ความยาว 64 ตัวอักษร)
* @returns ผลลัพธ์ความถูกต้อง (true = ผ่าน, false = ไม่ผ่าน)
*/
export function verifyDataEd25519(message: string | Buffer, signatureHex: string, publicKeyHex: string): boolean {
try {
const rawPublicKey = Buffer.from(publicKeyHex, 'hex');
// SPKI Header สำหรับ Ed25519 Public Key (32 bytes)
const spkiHeader = Buffer.from('302a300506032b6570032100', 'hex');
const spkiBuffer = Buffer.concat([spkiHeader, rawPublicKey]);
const publicKey = createPublicKey({
key: spkiBuffer,
format: 'der',
type: 'spki',
});
const dataBuffer = typeof message === 'string' ? Buffer.from(message, 'utf-8') : message;
const signatureBuffer = Buffer.from(signatureHex, 'hex');
return verify(null, dataBuffer, publicKey, signatureBuffer);
} catch (error) {
return false;
}
}
/**
* ฟังก์ชันสำหรับเรียงคีย์ของ Object ตามตัวอักษรเพื่อไม่ให้โครงสร้าง JSON เพี้ยน
*/
export function sortObjectKeys(obj: Record<string, unknown>): Record<string, unknown> {
return Object.keys(obj)
.sort()
.reduce((result: Record<string, unknown>, key: string) => {
result[key] = obj[key];
return result;
}, {});
}

View file

@ -94,6 +94,20 @@ export const openApiSpec = {
format: "binary",
description: "ไฟล์ .docx ที่มี Content Control",
},
secret: {
type: "string",
description:
"ค่า secret สำหรับ validate HMAC-SHA256 กับข้อมูลใน custom property" ,
example:
"super_secret",
},
public_key: {
type: "string",
description:
"Public key ในรูปแบบ HEX <br/> openssl genpkey -algorithm ED25519 -out private.pem <br/> openssl pkey -in private.pem -outform DER | tail -c 32 | xxd -p -c 32 > private.hex <br/>openssl pkey -in private.pem -pubout -outform DER | tail -c 32 | xxd -p -c 32 > public.hex",
example:
"66b09b4a1b66271d5728c592c2409657e1c070a7bf371bf9f6ed457855270546",
},
},
},
},
@ -142,9 +156,23 @@ export const openApiSpec = {
dataJson: {
type: "string",
description:
"JSON เป็น string ตรง ๆ (ทางเลือกแทนการแนบไฟล์ data)",
"JSON เป็น string ตรง ๆ (ทางเลือกแทนการแนบไฟล์ data)<br/>{\"info:\"{\"ticket_data\":\"JSON String\"},\"dc:\"{..},\"dc:\"{\"blackyear\": \"๙๐/๒๕๖๙\",...}}",
example:
'{"notname":"คำสั่งเรียก ให้ทำคำให้การ","blackno":"คดีหมายเลขดำที่","blackyear":"๑๐/๒๕๖๙"}',
'',
},
secret: {
type: "string",
description:
"ค่า secret สำหรับทดสอบการทำ HMAC-SHA256",
example:
'super_secret',
},
private_key: {
type: "string",
description:
"Private key ในรูปแบบ HEX <br/> openssl genpkey -algorithm ED25519 -out private.pem <br/> openssl pkey -in private.pem -outform DER | tail -c 32 | xxd -p -c 32 > private.hex <br/>openssl pkey -in private.pem -pubout -outform DER | tail -c 32 | xxd -p -c 32 > public.hex",
example:
'8f2077b57b39399b6a7828f35da03a63ade8e3a36a6bf437b091a8bdb3d18ee1',
},
},
},