mirror of
https://github.com/ajayyy/SponsorBlockServer.git
synced 2025-12-10 13:37:01 +03:00
add token tests
This commit is contained in:
@@ -44,5 +44,7 @@ export async function generateTokenRequest(req: GenerateTokenRequest, res: Respo
|
|||||||
</h1>
|
</h1>
|
||||||
`);
|
`);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
return res.sendStatus(403);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,7 +4,6 @@ import { config } from "../config";
|
|||||||
import { privateDB } from "../databases/databases";
|
import { privateDB } from "../databases/databases";
|
||||||
import { Logger } from "../utils/logger";
|
import { Logger } from "../utils/logger";
|
||||||
import { getPatreonIdentity, PatronStatus, refreshToken, TokenType } from "../utils/tokenUtils";
|
import { getPatreonIdentity, PatronStatus, refreshToken, TokenType } from "../utils/tokenUtils";
|
||||||
import FormData from "form-data";
|
|
||||||
|
|
||||||
interface VerifyTokenRequest extends Request {
|
interface VerifyTokenRequest extends Request {
|
||||||
query: {
|
query: {
|
||||||
@@ -13,7 +12,7 @@ interface VerifyTokenRequest extends Request {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const validatelicenseKeyRegex = (token: string) =>
|
export const validatelicenseKeyRegex = (token: string) =>
|
||||||
new RegExp(/[A-Za-z0-9]{40}/).test(token);
|
new RegExp(/[A-Za-z0-9]{40}|[A-Za-z0-9-]{35}/).test(token);
|
||||||
|
|
||||||
export async function verifyTokenRequest(req: VerifyTokenRequest, res: Response): Promise<Response> {
|
export async function verifyTokenRequest(req: VerifyTokenRequest, res: Response): Promise<Response> {
|
||||||
const { query: { licenseKey } } = req;
|
const { query: { licenseKey } } = req;
|
||||||
@@ -26,12 +25,6 @@ export async function verifyTokenRequest(req: VerifyTokenRequest, res: Response)
|
|||||||
allowed: false
|
allowed: false
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const licenseRegex = new RegExp(/[a-zA-Z0-9]{40}|[A-Z0-9-]{35}/);
|
|
||||||
if (!licenseRegex.test(licenseKey)) {
|
|
||||||
return res.status(200).send({
|
|
||||||
allowed: false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const tokens = (await privateDB.prepare("get", `SELECT "accessToken", "refreshToken", "expiresIn" from "oauthLicenseKeys" WHERE "licenseKey" = ?`
|
const tokens = (await privateDB.prepare("get", `SELECT "accessToken", "refreshToken", "expiresIn" from "oauthLicenseKeys" WHERE "licenseKey" = ?`
|
||||||
, [licenseKey])) as {accessToken: string, refreshToken: string, expiresIn: number};
|
, [licenseKey])) as {accessToken: string, refreshToken: string, expiresIn: number};
|
||||||
@@ -42,6 +35,7 @@ export async function verifyTokenRequest(req: VerifyTokenRequest, res: Response)
|
|||||||
refreshToken(TokenType.patreon, licenseKey, tokens.refreshToken).catch(Logger.error);
|
refreshToken(TokenType.patreon, licenseKey, tokens.refreshToken).catch(Logger.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* istanbul ignore else */
|
||||||
if (identity) {
|
if (identity) {
|
||||||
const membership = identity.included?.[0]?.attributes;
|
const membership = identity.included?.[0]?.attributes;
|
||||||
const allowed = !!membership && ((membership.patron_status === PatronStatus.active && membership.currently_entitled_amount_cents > 0)
|
const allowed = !!membership && ((membership.patron_status === PatronStatus.active && membership.currently_entitled_amount_cents > 0)
|
||||||
@@ -73,20 +67,13 @@ export async function verifyTokenRequest(req: VerifyTokenRequest, res: Response)
|
|||||||
async function checkAllGumroadProducts(licenseKey: string): Promise<boolean> {
|
async function checkAllGumroadProducts(licenseKey: string): Promise<boolean> {
|
||||||
for (const link of config.gumroad.productPermalinks) {
|
for (const link of config.gumroad.productPermalinks) {
|
||||||
try {
|
try {
|
||||||
const formData = new FormData();
|
const result = await axios.post("https://api.gumroad.com/v2/licenses/verify", {
|
||||||
formData.append("product_permalink", link);
|
params: { product_permalink: link, license_key: licenseKey }
|
||||||
formData.append("license_key", licenseKey);
|
|
||||||
|
|
||||||
const result = await axios.request({
|
|
||||||
url: "https://api.gumroad.com/v2/licenses/verify",
|
|
||||||
data: formData,
|
|
||||||
method: "POST",
|
|
||||||
headers: formData.getHeaders()
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const allowed = result.status === 200 && result.data?.success;
|
const allowed = result.status === 200 && result.data?.success;
|
||||||
if (allowed) return allowed;
|
if (allowed) return allowed;
|
||||||
} catch (e) {
|
} catch (e) /* istanbul ignore next */ {
|
||||||
Logger.error(`Gumroad fetch for ${link} failed: ${e}`);
|
Logger.error(`Gumroad fetch for ${link} failed: ${e}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
178
test/cases/generateVerifyToken.ts
Normal file
178
test/cases/generateVerifyToken.ts
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
import assert from "assert";
|
||||||
|
import { config } from "../../src/config";
|
||||||
|
import axios from "axios";
|
||||||
|
import { createAndSaveToken, TokenType } from "../../src/utils/tokenUtils";
|
||||||
|
import MockAdapter from "axios-mock-adapter";
|
||||||
|
let mock: MockAdapter;
|
||||||
|
import * as patreon from "../mocks/patreonMock";
|
||||||
|
import * as gumroad from "../mocks/gumroadMock";
|
||||||
|
import { client } from "../utils/httpClient";
|
||||||
|
import { validatelicenseKeyRegex } from "../../src/routes/verifyToken";
|
||||||
|
|
||||||
|
const generateEndpoint = "/api/generateToken";
|
||||||
|
const getGenerateToken = (type: string, code: string | null, adminUserID: string | null) => client({
|
||||||
|
url: `${generateEndpoint}/${type}`,
|
||||||
|
params: { code, adminUserID }
|
||||||
|
});
|
||||||
|
|
||||||
|
const verifyEndpoint = "/api/verifyToken";
|
||||||
|
const getVerifyToken = (licenseKey: string | null) => client({
|
||||||
|
url: verifyEndpoint,
|
||||||
|
params: { licenseKey }
|
||||||
|
});
|
||||||
|
|
||||||
|
let patreonLicense: string;
|
||||||
|
let localLicense: string;
|
||||||
|
const gumroadLicense = gumroad.generateLicense();
|
||||||
|
|
||||||
|
const extractLicenseKey = (data: string) => {
|
||||||
|
const regex = /([A-Za-z0-9]{40})/;
|
||||||
|
const match = data.match(regex);
|
||||||
|
if (!match) throw new Error("Failed to extract license key");
|
||||||
|
return match[1];
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("generateToken test", function() {
|
||||||
|
|
||||||
|
before(function() {
|
||||||
|
mock = new MockAdapter(axios, { onNoMatch: "throwException" });
|
||||||
|
mock.onPost("https://www.patreon.com/api/oauth2/token").reply(200, patreon.fakeOauth);
|
||||||
|
});
|
||||||
|
|
||||||
|
after(function () {
|
||||||
|
mock.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should be able to create patreon token for active patron", function (done) {
|
||||||
|
mock.onGet(/identity/).reply(200, patreon.activeIdentity);
|
||||||
|
if (!config?.patreon) this.skip();
|
||||||
|
getGenerateToken("patreon", "patreon_code", "").then(res => {
|
||||||
|
patreonLicense = extractLicenseKey(res.data);
|
||||||
|
assert.ok(validatelicenseKeyRegex(patreonLicense));
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should be able to create new local token", function (done) {
|
||||||
|
createAndSaveToken(TokenType.local).then((licenseKey) => {
|
||||||
|
assert.ok(validatelicenseKeyRegex(licenseKey));
|
||||||
|
localLicense = licenseKey;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should return 400 if missing code parameter", function (done) {
|
||||||
|
getGenerateToken("patreon", null, "").then(res => {
|
||||||
|
assert.strictEqual(res.status, 400);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should return 403 if missing adminuserID parameter", function (done) {
|
||||||
|
getGenerateToken("local", "fake-code", null).then(res => {
|
||||||
|
assert.strictEqual(res.status, 403);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should return 403 for invalid adminuserID parameter", function (done) {
|
||||||
|
getGenerateToken("local", "fake-code", "fakeAdminID").then(res => {
|
||||||
|
assert.strictEqual(res.status, 403);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("verifyToken static tests", function() {
|
||||||
|
it("Should fast reject invalid token", function (done) {
|
||||||
|
getVerifyToken("00000").then(res => {
|
||||||
|
assert.strictEqual(res.status, 200);
|
||||||
|
assert.ok(!res.data.allowed);
|
||||||
|
done();
|
||||||
|
}).catch(err => done(err));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should return 400 if missing code token", function (done) {
|
||||||
|
getVerifyToken(null).then(res => {
|
||||||
|
assert.strictEqual(res.status, 400);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("verifyToken mock tests", function() {
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
mock = new MockAdapter(axios, { onNoMatch: "throwException" });
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function () {
|
||||||
|
mock.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should accept current patron", function (done) {
|
||||||
|
if (!config?.patreon) this.skip();
|
||||||
|
mock.onGet(/identity/).reply(200, patreon.activeIdentity);
|
||||||
|
getVerifyToken(patreonLicense).then(res => {
|
||||||
|
assert.strictEqual(res.status, 200);
|
||||||
|
assert.ok(res.data.allowed);
|
||||||
|
done();
|
||||||
|
}).catch(err => done(err));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should reject nonexistent patron", function (done) {
|
||||||
|
if (!config?.patreon) this.skip();
|
||||||
|
mock.onGet(/identity/).reply(200, patreon.invalidIdentity);
|
||||||
|
getVerifyToken(patreonLicense).then(res => {
|
||||||
|
assert.strictEqual(res.status, 200);
|
||||||
|
assert.ok(!res.data.allowed);
|
||||||
|
done();
|
||||||
|
}).catch(err => done(err));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should accept qualitying former patron", function (done) {
|
||||||
|
if (!config?.patreon) this.skip();
|
||||||
|
mock.onGet(/identity/).reply(200, patreon.formerIdentitySucceed);
|
||||||
|
getVerifyToken(patreonLicense).then(res => {
|
||||||
|
assert.strictEqual(res.status, 200);
|
||||||
|
assert.ok(res.data.allowed);
|
||||||
|
done();
|
||||||
|
}).catch(err => done(err));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should reject unqualitifed former patron", function (done) {
|
||||||
|
if (!config?.patreon) this.skip();
|
||||||
|
mock.onGet(/identity/).reply(200, patreon.formerIdentityFail);
|
||||||
|
getVerifyToken(patreonLicense).then(res => {
|
||||||
|
assert.strictEqual(res.status, 200);
|
||||||
|
assert.ok(!res.data.allowed);
|
||||||
|
done();
|
||||||
|
}).catch(err => done(err));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should accept real gumroad key", function (done) {
|
||||||
|
mock.onPost("https://api.gumroad.com/v2/licenses/verify").reply(200, gumroad.licenseSuccess);
|
||||||
|
getVerifyToken(gumroadLicense).then(res => {
|
||||||
|
assert.strictEqual(res.status, 200);
|
||||||
|
assert.ok(res.data.allowed);
|
||||||
|
done();
|
||||||
|
}).catch(err => done(err));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should reject fake gumroad key", function (done) {
|
||||||
|
mock.onPost("https://api.gumroad.com/v2/licenses/verify").reply(200, gumroad.licenseFail);
|
||||||
|
getVerifyToken(gumroadLicense).then(res => {
|
||||||
|
assert.strictEqual(res.status, 200);
|
||||||
|
assert.ok(!res.data.allowed);
|
||||||
|
done();
|
||||||
|
}).catch(err => done(err));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should validate local license", function (done) {
|
||||||
|
getVerifyToken(localLicense).then(res => {
|
||||||
|
assert.strictEqual(res.status, 200);
|
||||||
|
assert.ok(res.data.allowed);
|
||||||
|
done();
|
||||||
|
}).catch(err => done(err));
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -8,27 +8,12 @@ let mock: MockAdapter;
|
|||||||
import * as patreon from "../mocks/patreonMock";
|
import * as patreon from "../mocks/patreonMock";
|
||||||
|
|
||||||
const validateToken = validatelicenseKeyRegex;
|
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() {
|
describe("tokenUtils test", function() {
|
||||||
before(function() {
|
before(function() {
|
||||||
mock = new MockAdapter(axios, { onNoMatch: "throwException" });
|
mock = new MockAdapter(axios, { onNoMatch: "throwException" });
|
||||||
mock.onPost("https://www.patreon.com/api/oauth2/token").reply(200, patreon.fakeOauth);
|
mock.onPost("https://www.patreon.com/api/oauth2/token").reply(200, patreon.fakeOauth);
|
||||||
mock.onGet(/identity/).reply(200, patreon.fakeIdentity);
|
mock.onGet(/identity/).reply(200, patreon.activeIdentity);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Should be able to create patreon token", function (done) {
|
it("Should be able to create patreon token", function (done) {
|
||||||
@@ -47,7 +32,7 @@ describe("tokenUtils test", function() {
|
|||||||
it("Should be able to get patreon identity", function (done) {
|
it("Should be able to get patreon identity", function (done) {
|
||||||
if (!config?.patreon) this.skip();
|
if (!config?.patreon) this.skip();
|
||||||
tokenUtils.getPatreonIdentity("fake_access_token").then((result) => {
|
tokenUtils.getPatreonIdentity("fake_access_token").then((result) => {
|
||||||
assert.deepEqual(result, patreon.fakeIdentity);
|
assert.deepEqual(result, patreon.activeIdentity);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
22
test/mocks/gumroadMock.ts
Normal file
22
test/mocks/gumroadMock.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
export const licenseSuccess = {
|
||||||
|
success: true,
|
||||||
|
uses: 4,
|
||||||
|
purchase: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const licenseFail = {
|
||||||
|
success: false,
|
||||||
|
message: "That license does not exist for the provided product."
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const subCode = (length = 8) => {
|
||||||
|
const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||||
|
let result = "";
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
result += characters[(Math.floor(Math.random() * characters.length))];
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const generateLicense = (): string => `${subCode()}-${subCode()}-${subCode()}-${subCode()}`;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
export const fakeIdentity = {
|
export const activeIdentity = {
|
||||||
data: {},
|
data: {},
|
||||||
links: {},
|
links: {},
|
||||||
included: [
|
included: [
|
||||||
@@ -14,6 +14,44 @@ export const fakeIdentity = {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const invalidIdentity = {
|
||||||
|
data: {},
|
||||||
|
links: {},
|
||||||
|
included: [{}],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const formerIdentitySucceed = {
|
||||||
|
data: {},
|
||||||
|
links: {},
|
||||||
|
included: [
|
||||||
|
{
|
||||||
|
attributes: {
|
||||||
|
is_monthly: true,
|
||||||
|
campaign_lifetime_support_cents: 500,
|
||||||
|
patron_status: "former_patron",
|
||||||
|
},
|
||||||
|
id: "id",
|
||||||
|
type: "campaign"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const formerIdentityFail = {
|
||||||
|
data: {},
|
||||||
|
links: {},
|
||||||
|
included: [
|
||||||
|
{
|
||||||
|
attributes: {
|
||||||
|
is_monthly: true,
|
||||||
|
campaign_lifetime_support_cents: 1,
|
||||||
|
patron_status: "former_patron",
|
||||||
|
},
|
||||||
|
id: "id",
|
||||||
|
type: "campaign"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
export const fakeOauth = {
|
export const fakeOauth = {
|
||||||
access_token: "test_access_token",
|
access_token: "test_access_token",
|
||||||
refresh_token: "test_refresh_token",
|
refresh_token: "test_refresh_token",
|
||||||
|
|||||||
Reference in New Issue
Block a user