diff --git a/__mocks__/@tsndr/cloudflare-worker-jwt.js b/__mocks__/@tsndr/cloudflare-worker-jwt.js new file mode 100644 index 00000000..d0b39154 --- /dev/null +++ b/__mocks__/@tsndr/cloudflare-worker-jwt.js @@ -0,0 +1,11 @@ +"use strict"; + +import jest from 'jest'; + +const jwt = Object.create({}); + +jwt.sign = jest.fn().mockImplementation(() => { + return "asdasd"; +}); + +export default jwt \ No newline at end of file diff --git a/__mocks__/@tsndr/cloudflare-worker-jwt.ts b/__mocks__/@tsndr/cloudflare-worker-jwt.ts new file mode 100644 index 00000000..c12cedbd --- /dev/null +++ b/__mocks__/@tsndr/cloudflare-worker-jwt.ts @@ -0,0 +1,15 @@ +"use strict"; + +type cloudflareWorkerJwtType = { + sign: () => void; +}; + +const cloudflareWorkerJwt:cloudflareWorkerJwtType = jest.createMockFromModule("jsonwebtoken"); + +function sign() { + return "asd"; +} + +cloudflareWorkerJwt.sign = sign; + +export default cloudflareWorkerJwt; diff --git a/package-lock.json b/package-lock.json index 7f88fd0c..0e1e8e09 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,12 +18,14 @@ "devDependencies": { "@cloudflare/workers-types": "^4.20221111.1", "@types/jest": "^29.2.5", + "@types/jsonwebtoken": "^9.0.2", "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.2", "@typescript-eslint/eslint-plugin": "^5.47.1", "@typescript-eslint/parser": "^5.47.1", "eslint": "^8.31.0", "jest": "^29.3.1", + "jsonwebtoken": "^9.0.1", "ngrok": "^5.0.0-beta.2", "pre-commit": "^1.2.2", "prettier": "^2.8.1", @@ -1880,6 +1882,15 @@ "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", "dev": true }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-drE6uz7QBKq1fYqqoFKTDRdFCPHd5TCub75BM+D+cMx7NU9hUz7SESLfC2fSCXVFMO5Yj8sOWHuGqPgjc+fz0Q==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/keyv": { "version": "3.1.4", "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", @@ -2571,6 +2582,12 @@ "node": "*" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "dev": true + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -3073,6 +3090,15 @@ "node": ">=12" } }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/electron-to-chromium": { "version": "1.4.284", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz", @@ -4952,6 +4978,43 @@ "node": ">=6" } }, + "node_modules/jsonwebtoken": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.1.tgz", + "integrity": "sha512-K8wx7eJ5TPvEjuiVSkv167EVboBDv9PZdDoF7BgeQnBLVvZWW9clr2PsQHVJDTKaEIH5JBIwHujGcHp7GgI2eg==", + "dev": true, + "dependencies": { + "jws": "^3.2.2", + "lodash": "^4.17.21", + "ms": "^2.1.1", + "semver": "^7.3.8" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "dev": true, + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "dev": true, + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "node_modules/keyv": { "version": "4.5.2", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.2.tgz", @@ -5013,6 +5076,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, "node_modules/lodash.clonedeep": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", @@ -8372,6 +8441,15 @@ "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", "dev": true }, + "@types/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-drE6uz7QBKq1fYqqoFKTDRdFCPHd5TCub75BM+D+cMx7NU9hUz7SESLfC2fSCXVFMO5Yj8sOWHuGqPgjc+fz0Q==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/keyv": { "version": "3.1.4", "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", @@ -8855,6 +8933,12 @@ "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", "dev": true }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "dev": true + }, "buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -9219,6 +9303,15 @@ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz", "integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==" }, + "ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, "electron-to-chromium": { "version": "1.4.284", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz", @@ -10641,6 +10734,39 @@ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true }, + "jsonwebtoken": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.1.tgz", + "integrity": "sha512-K8wx7eJ5TPvEjuiVSkv167EVboBDv9PZdDoF7BgeQnBLVvZWW9clr2PsQHVJDTKaEIH5JBIwHujGcHp7GgI2eg==", + "dev": true, + "requires": { + "jws": "^3.2.2", + "lodash": "^4.17.21", + "ms": "^2.1.1", + "semver": "^7.3.8" + } + }, + "jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "dev": true, + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "dev": true, + "requires": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "keyv": { "version": "4.5.2", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.2.tgz", @@ -10687,6 +10813,12 @@ "p-locate": "^5.0.0" } }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, "lodash.clonedeep": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", diff --git a/package.json b/package.json index c2b1425e..3230da25 100644 --- a/package.json +++ b/package.json @@ -22,12 +22,14 @@ "devDependencies": { "@cloudflare/workers-types": "^4.20221111.1", "@types/jest": "^29.2.5", + "@types/jsonwebtoken": "^9.0.2", "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.2", "@typescript-eslint/eslint-plugin": "^5.47.1", "@typescript-eslint/parser": "^5.47.1", "eslint": "^8.31.0", "jest": "^29.3.1", + "jsonwebtoken": "^9.0.1", "ngrok": "^5.0.0-beta.2", "pre-commit": "^1.2.2", "prettier": "^2.8.1", diff --git a/src/controllers/verifyCommand.ts b/src/controllers/verifyCommand.ts index 668010f3..ac67b7f6 100644 --- a/src/controllers/verifyCommand.ts +++ b/src/controllers/verifyCommand.ts @@ -13,7 +13,6 @@ export async function verifyCommand( env: env ) { const token = await generateUniqueToken(); - const response = await sendUserDiscordData( token, userId, @@ -24,7 +23,9 @@ export async function verifyCommand( ); if (response?.status === 201 || response?.status === 200) { const verificationSiteURL = config(env).VERIFICATION_SITE_URL; + const message = `${verificationSiteURL}/discord?token=${token}\n${VERIFICATION_STRING}`; + return discordEphemeralResponse(message); } else { return discordEphemeralResponse(RETRY_COMMAND); diff --git a/src/utils/generateUniqueToken.ts b/src/utils/generateUniqueToken.ts index d7fa803c..cdfbf4ec 100644 --- a/src/utils/generateUniqueToken.ts +++ b/src/utils/generateUniqueToken.ts @@ -1,3 +1,4 @@ +const crypto = require("crypto"); export const generateUniqueToken = async () => { const uuidToken = crypto.randomUUID(); const randomNumber = Math.floor(Math.random() * 1000000); diff --git a/tests/mocks/__mocks__ b/tests/mocks/__mocks__ new file mode 100644 index 00000000..e69de29b diff --git a/tests/unit/handlers/verifyCommand.test.ts b/tests/unit/handlers/verifyCommand.test.ts new file mode 100644 index 00000000..48a0f934 --- /dev/null +++ b/tests/unit/handlers/verifyCommand.test.ts @@ -0,0 +1,105 @@ +import * as response from "../../../src/constants/responses"; +import { verifyCommand } from "../../../src/controllers/verifyCommand"; +import config from "../../../config/config"; +import JSONResponse from "../../../src/utils/JsonResponse"; + +type Responsetype = { + data: DataType; + type: number; +}; + +type DataType = { + content: string; + flags: number; +}; +describe("verifyCommand", () => { + test("should return INTERNAL_SERVER_ERROR when response is not ok", async () => { + jest.mock("crypto", () => { + return { + randomUUID: jest.fn(() => "shreya"), + subtle: { digest: jest.fn(() => "123") }, + }; + }); + + jest.mock("../../../src/utils/generateUniqueToken", () => ({ + generateUniqueToken: () => Promise.resolve("jashdkjahskajhd"), + })); + + const mockResponse = response.INTERNAL_SERVER_ERROR; + jest + .spyOn(global, "fetch") + .mockImplementation(() => + Promise.resolve(new JSONResponse(mockResponse)) + ); + + const env = { + BOT_PUBLIC_KEY: "xyz", + DISCORD_GUILD_ID: "123", + DISCORD_TOKEN: "abc", + }; + + const result = await verifyCommand( + 1233434, + "sjkhdkjashdksjh", + "test user", + "sndbhsbgdj", + env + ); + + expect(global.fetch).toHaveBeenCalledWith( + `http://localhost:3000/external-accounts`, + expect.objectContaining({ + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer asd`, + }, + }) + ); + + const response_: Responsetype = await result.json(); + expect(response_.data.content).toContain(""); + }); + + test("should return JSON response when response is ok", async () => { + const mockResponse = {}; + + jest + .spyOn(global, "fetch") + .mockImplementation(() => + Promise.resolve(new JSONResponse(mockResponse)) + ); + + const env = { + BOT_PUBLIC_KEY: "xyz", + DISCORD_GUILD_ID: "123", + DISCORD_TOKEN: "abc", + }; + + const result = await verifyCommand( + 1233434, + "sjkhdkjashdksjh", + "test user", + "sndbhsbgdj", + env + ); + + const verificationSiteURL = config(env).VERIFICATION_SITE_URL; + const message = `${verificationSiteURL}/discord?token=`; + + expect(global.fetch).toHaveBeenCalledWith( + `http://localhost:3000/external-accounts`, + expect.objectContaining({ + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer asd`, + }, + }) + ); + + const response_: Responsetype = await result.json(); + + expect(response_.data.content).toContain(message); + }); +});