From dfa4578d283471f0fea08323a38a90b5df660482 Mon Sep 17 00:00:00 2001 From: Ajay Date: Thu, 3 Aug 2023 00:58:01 -0400 Subject: [PATCH] Better token generation --- src/config.ts | 1 + src/routes/generateToken.ts | 45 +++++++++++++++++++++++-------------- src/routes/verifyToken.ts | 17 +++++++++++++- src/types/config.model.ts | 1 + src/utils/tokenUtils.ts | 29 ++++++++++++++++-------- 5 files changed, 66 insertions(+), 27 deletions(-) diff --git a/src/config.ts b/src/config.ts index cabd7de..c5dd299 100644 --- a/src/config.ts +++ b/src/config.ts @@ -185,6 +185,7 @@ addDefaults(config, { gumroad: { productPermalinks: ["sponsorblock"] }, + tokenSeed: "", minUserIDLength: 30 }); loadFromEnv(config); diff --git a/src/routes/generateToken.ts b/src/routes/generateToken.ts index d617ecc..6190ebd 100644 --- a/src/routes/generateToken.ts +++ b/src/routes/generateToken.ts @@ -7,6 +7,7 @@ interface GenerateTokenRequest extends Request { query: { code: string; adminUserID?: string; + total?: string; }, params: { type: TokenType; @@ -14,31 +15,41 @@ interface GenerateTokenRequest extends Request { } export async function generateTokenRequest(req: GenerateTokenRequest, res: Response): Promise { - const { query: { code, adminUserID }, params: { type } } = req; + const { query: { code, adminUserID, total }, params: { type } } = req; const adminUserIDHash = adminUserID ? (await getHashCache(adminUserID)) : null; - if (!code || !type) { + if (!type || (!code && type === TokenType.patreon)) { return res.status(400).send("Invalid request"); } - if (type === TokenType.patreon || (type === TokenType.local && adminUserIDHash === config.adminUserID)) { - const licenseKey = await createAndSaveToken(type, code); + if (type === TokenType.patreon + || ([TokenType.local, TokenType.gift].includes(type) && adminUserIDHash === config.adminUserID) + || type === TokenType.free) { + const licenseKey = await createAndSaveToken(type, code, adminUserIDHash === config.adminUserID ? parseInt(total) : 1); /* istanbul ignore else */ if (licenseKey) { - return res.status(200).send(` -

- Your license key: -

-

- - ${licenseKey} - -

-

- Copy this into the textbox in the other tab -

- `); + if (type === TokenType.patreon) { + return res.status(200).send(` +

+ Your license key: +

+

+ + ${licenseKey[0]} + +

+

+ Copy this into the textbox in the other tab +

+ `); + } else if (type === TokenType.free) { + return res.status(200).send({ + licenseKey: licenseKey[0] + }); + } else { + return res.status(200).send(licenseKey.join("
")); + } } else { return res.status(401).send(`

diff --git a/src/routes/verifyToken.ts b/src/routes/verifyToken.ts index 369a1a7..c206377 100644 --- a/src/routes/verifyToken.ts +++ b/src/routes/verifyToken.ts @@ -4,6 +4,7 @@ import { config } from "../config"; import { privateDB } from "../databases/databases"; import { Logger } from "../utils/logger"; import { getPatreonIdentity, PatronStatus, refreshToken, TokenType } from "../utils/tokenUtils"; +import { getHash } from "../utils/getHash"; interface VerifyTokenRequest extends Request { query: { @@ -12,7 +13,9 @@ interface VerifyTokenRequest extends Request { } export const validateLicenseKeyRegex = (token: string) => - new RegExp(/[A-Za-z0-9]{40}|[A-Za-z0-9-]{35}/).test(token); + new RegExp(/[A-Za-z0-9]{40}|[A-Za-z0-9-]{35}|[A-Za-z0-9-]{5}-[A-Za-z0-9-]{5}/).test(token); + +const isLocalLicenseKey = (token: string) => /[A-Za-z0-9-]{5}-[A-Za-z0-9-]{5}/.test(token); export async function verifyTokenRequest(req: VerifyTokenRequest, res: Response): Promise { const { query: { licenseKey } } = req; @@ -27,6 +30,18 @@ export async function verifyTokenRequest(req: VerifyTokenRequest, res: Response) }); } + if (isLocalLicenseKey(licenseKey)) { + const parts = licenseKey.split("-"); + const code = parts[0]; + const givenResult = parts[1]; + + if (getHash(config.tokenSeed + code, 1).startsWith(givenResult)) { + return res.status(200).send({ + allowed: true + }); + } + } + const tokens = (await privateDB.prepare("get", `SELECT "accessToken", "refreshToken", "expiresIn" from "oauthLicenseKeys" WHERE "licenseKey" = ?` , [licenseKey])) as {accessToken: string, refreshToken: string, expiresIn: number}; if (tokens) { diff --git a/src/types/config.model.ts b/src/types/config.model.ts index 184eab2..3d7d40d 100644 --- a/src/types/config.model.ts +++ b/src/types/config.model.ts @@ -97,6 +97,7 @@ export interface SBSConfig { gumroad: { productPermalinks: string[], }, + tokenSeed: string, minUserIDLength: number } diff --git a/src/utils/tokenUtils.ts b/src/utils/tokenUtils.ts index e936b45..1a44eaa 100644 --- a/src/utils/tokenUtils.ts +++ b/src/utils/tokenUtils.ts @@ -4,10 +4,13 @@ import { privateDB } from "../databases/databases"; import { Logger } from "./logger"; import FormData from "form-data"; import { randomInt } from "node:crypto"; +import { getHash } from "./getHash"; export enum TokenType { patreon = "patreon", local = "local", + free = "free", + gift = "gift", gumroad = "gumroad" } @@ -28,7 +31,7 @@ export interface PatreonIdentityData { }> } -export async function createAndSaveToken(type: TokenType, code?: string): Promise { +export async function createAndSaveToken(type: TokenType, code?: string, total = 1): Promise { switch(type) { case TokenType.patreon: { const domain = "https://www.patreon.com"; @@ -48,7 +51,7 @@ export async function createAndSaveToken(type: TokenType, code?: string): Promis }); if (result.status === 200) { - const licenseKey = generateToken(); + const licenseKey = generateLicenseKey("P"); const time = Date.now(); await privateDB.prepare("run", `INSERT INTO "licenseKeys"("licenseKey", "time", "type") VALUES(?, ?, ?)`, [licenseKey, time, type]); @@ -56,7 +59,7 @@ export async function createAndSaveToken(type: TokenType, code?: string): Promis , [licenseKey, result.data.access_token, result.data.refresh_token, result.data.expires_in]); - return licenseKey; + return [licenseKey]; } break; } catch (e) /* istanbul ignore next */ { @@ -64,13 +67,16 @@ export async function createAndSaveToken(type: TokenType, code?: string): Promis return null; } } + case TokenType.free: + case TokenType.gift: case TokenType.local: { - const licenseKey = generateToken(); - const time = Date.now(); + const prefix = type === TokenType.free ? "F" : (type === TokenType.gift ? "G" : "A"); + const licenseKeys = []; + for (let i = 0; i < total; i++) { + licenseKeys.push(generateLicenseKey(prefix)); + } - await privateDB.prepare("run", `INSERT INTO "licenseKeys"("licenseKey", "time", "type") VALUES(?, ?, ?)`, [licenseKey, time, type]); - - return licenseKey; + return licenseKeys; } } return null; @@ -109,7 +115,12 @@ export async function refreshToken(type: TokenType, licenseKey: string, refreshT return false; } -function generateToken(length = 40): string { +function generateLicenseKey(prefix: string): string { + const code = `${prefix}${generateRandomCode()}`; + return `${code}-${getHash(config.tokenSeed + code, 1).slice(0, 5)}`; +} + +function generateRandomCode(length = 4): string { const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; let result = "";