Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 2 additions & 6 deletions packages/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,8 @@
"clean": true,
"platform": "browser"
},
"dependencies": {
"crypto-js": "^4.2.0"
},
"devDependencies": {
"@types/crypto-js": "^4.2.0"
},
"dependencies": {},
"devDependencies": {},
"peerDependencies": {},
"keywords": [
"ton",
Expand Down
35 changes: 24 additions & 11 deletions packages/api/src/utils/verify-signature.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import * as CryptoJS from 'crypto-js';

/**
* Verifies the HMAC-SHA256 signature of a payload
* @param payload - Raw JSON string or object to verify
Expand All @@ -12,39 +10,54 @@ import * as CryptoJS from 'crypto-js';
* import { verifySignature } from "@ton-pay/api";
*
* // With raw string
* app.post("/webhook", (req, res) => {
* app.post("/webhook", async (req, res) => {
* const signature = req.headers["x-tonpay-signature"] as string;
* const payload = JSON.stringify(req.body);
*
* if (!verifySignature(payload, signature, YOUR_API_SECRET)) {
* if (!await verifySignature(payload, signature, YOUR_API_SECRET)) {
* return res.status(401).json({ error: "Invalid signature" });
* }
*
* res.status(200).json({ received: true });
* });
*
* // With object (will be stringified automatically)
* app.post("/webhook", (req, res) => {
* app.post("/webhook", async (req, res) => {
* const signature = req.headers["x-tonpay-signature"] as string;
*
* if (!verifySignature(req.body, signature, YOUR_API_SECRET)) {
* if (!await verifySignature(req.body, signature, YOUR_API_SECRET)) {
* return res.status(401).json({ error: "Invalid signature" });
* }
*
* res.status(200).json({ received: true });
* });
* ```
*/
export function verifySignature(
export async function verifySignature(
payload: string | object,
signature: string,
apiSecret: string,
): boolean {
): Promise<boolean> {
const encoder = new TextEncoder();
const payloadString =
typeof payload === 'string' ? payload : JSON.stringify(payload);

const hmac = CryptoJS.HmacSHA256(payloadString, apiSecret);
const expectedSignature = `sha256=${hmac.toString(CryptoJS.enc.Hex)}`;
const key = await crypto.subtle.importKey(
'raw',
encoder.encode(apiSecret),
{ name: 'HMAC', hash: 'SHA-256' },
false,
['sign'],
);

const sig = await crypto.subtle.sign(
'HMAC',
key,
encoder.encode(payloadString),
);
const hex = Array.from(new Uint8Array(sig))
.map((b) => b.toString(16).padStart(2, '0'))
.join('');

return signature === expectedSignature;
return signature === `sha256=${hex}`;
}
1 change: 0 additions & 1 deletion packages/api/tsup.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,5 @@ export default defineConfig({
sourcemap: true,
clean: true,
platform: 'browser',
noExternal: ['crypto-js'],
tsconfig: './tsconfig.json',
});