Better token generation

This commit is contained in:
Ajay
2023-08-03 00:58:01 -04:00
parent 99cb22a5e6
commit dfa4578d28
5 changed files with 66 additions and 27 deletions

View File

@@ -185,6 +185,7 @@ addDefaults(config, {
gumroad: { gumroad: {
productPermalinks: ["sponsorblock"] productPermalinks: ["sponsorblock"]
}, },
tokenSeed: "",
minUserIDLength: 30 minUserIDLength: 30
}); });
loadFromEnv(config); loadFromEnv(config);

View File

@@ -7,6 +7,7 @@ interface GenerateTokenRequest extends Request {
query: { query: {
code: string; code: string;
adminUserID?: string; adminUserID?: string;
total?: string;
}, },
params: { params: {
type: TokenType; type: TokenType;
@@ -14,31 +15,41 @@ interface GenerateTokenRequest extends Request {
} }
export async function generateTokenRequest(req: GenerateTokenRequest, res: Response): Promise<Response> { export async function generateTokenRequest(req: GenerateTokenRequest, res: Response): Promise<Response> {
const { query: { code, adminUserID }, params: { type } } = req; const { query: { code, adminUserID, total }, params: { type } } = req;
const adminUserIDHash = adminUserID ? (await getHashCache(adminUserID)) : null; const adminUserIDHash = adminUserID ? (await getHashCache(adminUserID)) : null;
if (!code || !type) { if (!type || (!code && type === TokenType.patreon)) {
return res.status(400).send("Invalid request"); return res.status(400).send("Invalid request");
} }
if (type === TokenType.patreon || (type === TokenType.local && adminUserIDHash === config.adminUserID)) { if (type === TokenType.patreon
const licenseKey = await createAndSaveToken(type, code); || ([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 */ /* istanbul ignore else */
if (licenseKey) { if (licenseKey) {
return res.status(200).send(` if (type === TokenType.patreon) {
<h1> return res.status(200).send(`
Your license key: <h1>
</h1> Your license key:
<p> </h1>
<b> <p>
${licenseKey} <b>
</b> ${licenseKey[0]}
</p> </b>
<p> </p>
Copy this into the textbox in the other tab <p>
</p> Copy this into the textbox in the other tab
`); </p>
`);
} else if (type === TokenType.free) {
return res.status(200).send({
licenseKey: licenseKey[0]
});
} else {
return res.status(200).send(licenseKey.join("<br/>"));
}
} else { } else {
return res.status(401).send(` return res.status(401).send(`
<h1> <h1>

View File

@@ -4,6 +4,7 @@ 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 { getHash } from "../utils/getHash";
interface VerifyTokenRequest extends Request { interface VerifyTokenRequest extends Request {
query: { query: {
@@ -12,7 +13,9 @@ interface VerifyTokenRequest extends Request {
} }
export const validateLicenseKeyRegex = (token: string) => 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<Response> { export async function verifyTokenRequest(req: VerifyTokenRequest, res: Response): Promise<Response> {
const { query: { licenseKey } } = req; 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" = ?` 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};
if (tokens) { if (tokens) {

View File

@@ -97,6 +97,7 @@ export interface SBSConfig {
gumroad: { gumroad: {
productPermalinks: string[], productPermalinks: string[],
}, },
tokenSeed: string,
minUserIDLength: number minUserIDLength: number
} }

View File

@@ -4,10 +4,13 @@ import { privateDB } from "../databases/databases";
import { Logger } from "./logger"; import { Logger } from "./logger";
import FormData from "form-data"; import FormData from "form-data";
import { randomInt } from "node:crypto"; import { randomInt } from "node:crypto";
import { getHash } from "./getHash";
export enum TokenType { export enum TokenType {
patreon = "patreon", patreon = "patreon",
local = "local", local = "local",
free = "free",
gift = "gift",
gumroad = "gumroad" gumroad = "gumroad"
} }
@@ -28,7 +31,7 @@ export interface PatreonIdentityData {
}> }>
} }
export async function createAndSaveToken(type: TokenType, code?: string): Promise<string> { export async function createAndSaveToken(type: TokenType, code?: string, total = 1): Promise<string[]> {
switch(type) { switch(type) {
case TokenType.patreon: { case TokenType.patreon: {
const domain = "https://www.patreon.com"; const domain = "https://www.patreon.com";
@@ -48,7 +51,7 @@ export async function createAndSaveToken(type: TokenType, code?: string): Promis
}); });
if (result.status === 200) { if (result.status === 200) {
const licenseKey = generateToken(); const licenseKey = generateLicenseKey("P");
const time = Date.now(); const time = Date.now();
await privateDB.prepare("run", `INSERT INTO "licenseKeys"("licenseKey", "time", "type") VALUES(?, ?, ?)`, [licenseKey, time, type]); 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]); , [licenseKey, result.data.access_token, result.data.refresh_token, result.data.expires_in]);
return licenseKey; return [licenseKey];
} }
break; break;
} catch (e) /* istanbul ignore next */ { } catch (e) /* istanbul ignore next */ {
@@ -64,13 +67,16 @@ export async function createAndSaveToken(type: TokenType, code?: string): Promis
return null; return null;
} }
} }
case TokenType.free:
case TokenType.gift:
case TokenType.local: { case TokenType.local: {
const licenseKey = generateToken(); const prefix = type === TokenType.free ? "F" : (type === TokenType.gift ? "G" : "A");
const time = Date.now(); 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 licenseKeys;
return licenseKey;
} }
} }
return null; return null;
@@ -109,7 +115,12 @@ export async function refreshToken(type: TokenType, licenseKey: string, refreshT
return false; 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"; const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
let result = ""; let result = "";