diff --git a/ci.json b/ci.json index 4c9267b..5a3ee2f 100644 --- a/ci.json +++ b/ci.json @@ -9,7 +9,7 @@ "discordCompletelyIncorrectReportWebhookURL": "http://127.0.0.1:8081/webhook/CompletelyIncorrectReport", "discordNeuralBlockRejectWebhookURL": "http://127.0.0.1:8081/webhook/NeuralBlockReject", "neuralBlockURL": "http://127.0.0.1:8081/NeuralBlock", - "userCounterURL": "https://127.0.0.1:8081/UserCounter", + "userCounterURL": "http://127.0.0.1:8081/UserCounter", "behindProxy": true, "postgres": { "user": "ci_db_user", @@ -71,5 +71,10 @@ "statusCode": 200 } }, + "patreon": { + "clientId": "testClientID", + "clientSecret": "testClientSecret", + "redirectUri": "http://127.0.0.1/fake/callback" + }, "minReputationToSubmitFiller": -1 } diff --git a/package-lock.json b/package-lock.json index 00e507e..1656aa3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,8 +30,10 @@ "@types/mocha": "^9.1.1", "@types/node": "^18.0.3", "@types/pg": "^8.6.5", + "@types/sinon": "^10.0.13", "@typescript-eslint/eslint-plugin": "^5.30.6", "@typescript-eslint/parser": "^5.30.6", + "axios-mock-adapter": "^1.21.2", "eslint": "^8.19.0", "mocha": "^10.0.0", "nodemon": "^2.0.19", @@ -963,6 +965,21 @@ "@types/node": "*" } }, + "node_modules/@types/sinon": { + "version": "10.0.13", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-10.0.13.tgz", + "integrity": "sha512-UVjDqJblVNQYvVNUsj0PuYYw0ELRmgt1Nt5Vk0pT5f16ROGfcKJY8o1HVuMOJOpD727RrGB9EGvoaTQE5tgxZQ==", + "dev": true, + "dependencies": { + "@types/sinonjs__fake-timers": "*" + } + }, + "node_modules/@types/sinonjs__fake-timers": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.2.tgz", + "integrity": "sha512-9GcLXF0/v3t80caGs5p2rRfkB+a8VBGLJZVih6CNFkx8IZ994wiKKLSRs9nuFwk1HevWs/1mnUmkApGrSGsShA==", + "dev": true + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "5.30.6", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.30.6.tgz", @@ -1339,6 +1356,19 @@ "form-data": "^4.0.0" } }, + "node_modules/axios-mock-adapter": { + "version": "1.21.2", + "resolved": "https://registry.npmjs.org/axios-mock-adapter/-/axios-mock-adapter-1.21.2.tgz", + "integrity": "sha512-jzyNxU3JzB2XVhplZboUcF0YDs7xuExzoRSHXPHr+UQajaGmcTqvkkUADgkVI2WkGlpZ1zZlMVdcTMU0ejV8zQ==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "is-buffer": "^2.0.5" + }, + "peerDependencies": { + "axios": ">= 0.17.0" + } + }, "node_modules/babel-runtime": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", @@ -3094,6 +3124,29 @@ "node": ">=8" } }, + "node_modules/is-buffer": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "engines": { + "node": ">=4" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -6453,6 +6506,21 @@ "@types/node": "*" } }, + "@types/sinon": { + "version": "10.0.13", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-10.0.13.tgz", + "integrity": "sha512-UVjDqJblVNQYvVNUsj0PuYYw0ELRmgt1Nt5Vk0pT5f16ROGfcKJY8o1HVuMOJOpD727RrGB9EGvoaTQE5tgxZQ==", + "dev": true, + "requires": { + "@types/sinonjs__fake-timers": "*" + } + }, + "@types/sinonjs__fake-timers": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.2.tgz", + "integrity": "sha512-9GcLXF0/v3t80caGs5p2rRfkB+a8VBGLJZVih6CNFkx8IZ994wiKKLSRs9nuFwk1HevWs/1mnUmkApGrSGsShA==", + "dev": true + }, "@typescript-eslint/eslint-plugin": { "version": "5.30.6", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.30.6.tgz", @@ -6698,6 +6766,16 @@ "form-data": "^4.0.0" } }, + "axios-mock-adapter": { + "version": "1.21.2", + "resolved": "https://registry.npmjs.org/axios-mock-adapter/-/axios-mock-adapter-1.21.2.tgz", + "integrity": "sha512-jzyNxU3JzB2XVhplZboUcF0YDs7xuExzoRSHXPHr+UQajaGmcTqvkkUADgkVI2WkGlpZ1zZlMVdcTMU0ejV8zQ==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.3", + "is-buffer": "^2.0.5" + } + }, "babel-runtime": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", @@ -7975,6 +8053,12 @@ "binary-extensions": "^2.0.0" } }, + "is-buffer": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", + "dev": true + }, "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", diff --git a/package.json b/package.json index 511aa19..0f505b0 100644 --- a/package.json +++ b/package.json @@ -39,8 +39,10 @@ "@types/mocha": "^9.1.1", "@types/node": "^18.0.3", "@types/pg": "^8.6.5", + "@types/sinon": "^10.0.13", "@typescript-eslint/eslint-plugin": "^5.30.6", "@typescript-eslint/parser": "^5.30.6", + "axios-mock-adapter": "^1.21.2", "eslint": "^8.19.0", "mocha": "^10.0.0", "nodemon": "^2.0.19", diff --git a/src/routes/verifyToken.ts b/src/routes/verifyToken.ts index 59910ef..2b18b5a 100644 --- a/src/routes/verifyToken.ts +++ b/src/routes/verifyToken.ts @@ -12,11 +12,19 @@ interface VerifyTokenRequest extends Request { } } +export const validatelicenseKeyRegex = (token: string) => + new RegExp(/[A-Za-z0-9]{40}/).test(token); + export async function verifyTokenRequest(req: VerifyTokenRequest, res: Response): Promise { const { query: { licenseKey } } = req; if (!licenseKey) { return res.status(400).send("Invalid request"); + } else if (!validatelicenseKeyRegex(licenseKey)) { + // fast check for invalid licence key + return res.status(200).send({ + allowed: false + }); } const licenseRegex = new RegExp(/[a-zA-Z0-9]{40}|[A-Z0-9-]{35}/); if (!licenseRegex.test(licenseKey)) { diff --git a/test.json b/test.json index 5692e76..16ace5c 100644 --- a/test.json +++ b/test.json @@ -59,5 +59,10 @@ "statusCode": 200 } }, + "patreon": { + "clientId": "testClientID", + "clientSecret": "testClientSecret", + "redirectUri": "http://127.0.0.1/fake/callback" + }, "minReputationToSubmitFiller": -1 } diff --git a/test/cases/tokenUtils.ts b/test/cases/tokenUtils.ts new file mode 100644 index 0000000..bd0b90a --- /dev/null +++ b/test/cases/tokenUtils.ts @@ -0,0 +1,68 @@ +import assert from "assert"; +import { config } from "../../src/config"; +import axios from "axios"; +import * as tokenUtils from "../../src/utils/tokenUtils"; +import MockAdapter from "axios-mock-adapter"; +import { validatelicenseKeyRegex } from "../../src/routes/verifyToken"; +let mock: MockAdapter; + +const validateToken = validatelicenseKeyRegex; +const fakePatreonIdentity = { + data: {}, + links: {}, + included: [ + { + attributes: { + is_monthly: true, + currently_entitled_amount_cents: 100, + patron_status: "active_patron", + }, + id: "id", + type: "campaign" + } + ], +}; + +describe("tokenUtils test", function() { + before(function() { + mock = new MockAdapter(axios, { onNoMatch: "throwException" }); + mock.onPost("https://www.patreon.com/api/oauth2/token").reply(200, { + access_token: "test_access_token", + refresh_token: "test_refresh_token", + expires_in: 3600, + }); + mock.onGet(/identity/).reply(200, fakePatreonIdentity); + }); + + it("Should be able to create patreon token", function (done) { + if (!config?.patreon) this.skip(); + tokenUtils.createAndSaveToken(tokenUtils.TokenType.patreon, "test_code").then((licenseKey) => { + assert.ok(validateToken(licenseKey)); + done(); + }); + }); + it("Should be able to create local token", (done) => { + tokenUtils.createAndSaveToken(tokenUtils.TokenType.local).then((licenseKey) => { + assert.ok(validateToken(licenseKey)); + done(); + }); + }); + it("Should be able to get patreon identity", function (done) { + if (!config?.patreon) this.skip(); + tokenUtils.getPatreonIdentity("fake_access_token").then((result) => { + assert.deepEqual(result, fakePatreonIdentity); + done(); + }); + }); + it("Should be able to refresh token", function (done) { + if (!config?.patreon) this.skip(); + tokenUtils.refreshToken(tokenUtils.TokenType.patreon, "fake-licence-Key", "fake_refresh_token").then((result) => { + assert.strictEqual(result, true); + done(); + }); + }); + + after(function () { + mock.restore(); + }); +}); \ No newline at end of file