Compare commits

...

9 Commits

Author SHA1 Message Date
Tom Moor 6e07aa877f clear secret 2024-10-01 22:17:45 -04:00
Tom Moor 19d5ef5694 Fix code scanning alert no. 67: Shell command built from environment values
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2024-10-01 19:05:20 -07:00
Tom Moor b37074304a fix 2024-10-01 22:04:50 -04:00
Tom Moor 35c7cc2086 suppress on cloud 2024-10-01 21:40:09 -04:00
Tom Moor 82f9600d9e fix: last4 not written 2024-10-01 21:38:31 -04:00
Tom Moor 686f9aeb5c Merge main 2024-10-01 21:02:36 -04:00
Tom Moor a2d5598b96 wip 2024-09-30 17:46:30 -04:00
Tom Moor 53272c8c3d test 2024-09-30 07:16:03 -04:00
Tom Moor 65ff9bde3e Add hashed column for API keys 2024-09-30 07:07:38 -04:00
3 changed files with 94 additions and 14 deletions
@@ -0,0 +1,29 @@
"use strict";
const { execFileSync } = require("child_process");
const path = require("path");
/** @type {import('sequelize-cli').Migration} */
module.exports = {
async up() {
if (
process.env.NODE_ENV === "test" ||
process.env.DEPLOYMENT === "hosted"
) {
return;
}
const scriptName = path.basename(__filename);
const scriptPath = path.join(
process.cwd(),
"build",
`server/scripts/${scriptName}`
);
execFileSync("node", [scriptPath], { stdio: "inherit" });
},
async down() {
// noop
},
};
+12 -14
View File
@@ -2,7 +2,6 @@ import crypto from "crypto";
import { subMinutes } from "date-fns";
import randomstring from "randomstring";
import { InferAttributes, InferCreationAttributes, Op } from "sequelize";
import { type BuildOptions } from "sequelize";
import {
Column,
Table,
@@ -12,6 +11,7 @@ import {
ForeignKey,
IsDate,
DataType,
AfterFind,
BeforeSave,
} from "sequelize-typescript";
import { ApiKeyValidation } from "@shared/validations";
@@ -28,18 +28,6 @@ class ApiKey extends ParanoidModel<
> {
static prefix = "ol_api_";
constructor(
values?: Partial<InferCreationAttributes<ApiKey>>,
options?: BuildOptions
) {
// Temporary until last4 is backfilled and secret is removed.
if (values?.secret) {
values.last4 = values.secret.slice(-4);
}
super(values, options);
}
@Length({
min: ApiKeyValidation.minNameLength,
max: ApiKeyValidation.maxNameLength,
@@ -76,6 +64,16 @@ class ApiKey extends ParanoidModel<
// hooks
@AfterFind
public static async afterFindHook(models: ApiKey | ApiKey[]) {
const modelsArray = Array.isArray(models) ? models : [models];
for (const model of modelsArray) {
if (model?.secret) {
model.last4 = model.secret.slice(-4);
}
}
}
@BeforeValidate
public static async generateSecret(model: ApiKey) {
if (!model.hash) {
@@ -99,7 +97,7 @@ class ApiKey extends ParanoidModel<
* @param key The input string to hash
* @returns The hashed API key
*/
private static hash(key: string) {
public static hash(key: string) {
return crypto.createHash("sha256").update(key).digest("hex");
}
@@ -0,0 +1,53 @@
import "./bootstrap";
import { Transaction } from "sequelize";
import { ApiKey } from "@server/models";
import { sequelize } from "@server/storage/database";
let page = parseInt(process.argv[2], 10);
page = Number.isNaN(page) ? 0 : page;
export default async function main(exit = false, limit = 100) {
const work = async (page: number): Promise<void> => {
console.log(`Backfill apiKey hash… page ${page}`);
let apiKeys: ApiKey[] = [];
await sequelize.transaction(async (transaction) => {
apiKeys = await ApiKey.unscoped().findAll({
limit,
offset: page * limit,
order: [["createdAt", "ASC"]],
lock: Transaction.LOCK.UPDATE,
transaction,
});
for (const apiKey of apiKeys) {
try {
if (!apiKey.hash) {
console.log(`Migrating ${apiKey.id}`);
apiKey.value = apiKey.secret;
apiKey.hash = ApiKey.hash(apiKey.secret);
// @ts-expect-error secret is deprecated
apiKey.secret = null;
await apiKey.save({ transaction });
}
} catch (err) {
console.error(`Failed at ${apiKey.id}:`, err);
continue;
}
}
});
return apiKeys.length === limit ? work(page + 1) : undefined;
};
await work(page);
console.log("Backfill complete");
if (exit) {
process.exit(0);
}
}
// In the test suite we import the script rather than run via node CLI
if (process.env.NODE_ENV !== "test") {
void main(true);
}