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: {
productPermalinks: ["sponsorblock"]
},
tokenSeed: "",
minUserIDLength: 30
});
loadFromEnv(config);

View file

@ -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<Response> {
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) {
if (type === TokenType.patreon) {
return res.status(200).send(`
<h1>
Your license key:
</h1>
<p>
<b>
${licenseKey}
${licenseKey[0]}
</b>
</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 {
return res.status(401).send(`
<h1>

View file

@ -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<Response> {
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) {

View file

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

View file

@ -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<string> {
export async function createAndSaveToken(type: TokenType, code?: string, total = 1): Promise<string[]> {
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 = "";