fix: added new identifier field for non-uuid IDs

This commit is contained in:
Daniel Hougaard
2024-09-30 22:51:42 +04:00
parent 7e820745a4
commit 1a0896475c
4 changed files with 55 additions and 78 deletions
@@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
import { Knex } from "knex"; import { Knex } from "knex";
import { TableName } from "../schemas"; import { TableName } from "../schemas";
@@ -6,31 +5,10 @@ import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> { export async function up(knex: Knex): Promise<void> {
if (await knex.schema.hasTable(TableName.SecretSharing)) { if (await knex.schema.hasTable(TableName.SecretSharing)) {
await knex.schema.alterTable(TableName.SecretSharing, (t) => { await knex.schema.alterTable(TableName.SecretSharing, (t) => {
// Add a new column t.string("identifier", 36).nullable();
t.string("new_id", 36).nullable();
});
// Copy data from old column to new column t.unique("identifier");
await knex(TableName.SecretSharing).update({ t.index("identifier");
// @ts-ignore
new_id: knex.raw("id::text")
});
await knex.schema.alterTable(TableName.SecretSharing, (t) => {
// Make the new column not nullable
t.string("new_id", 36).notNullable().alter();
// Drop the old primary key
t.dropPrimary();
// Drop the old id column
t.dropColumn("id");
// Rename the new column to 'id'
t.renameColumn("new_id", "id");
// Set the new column as primary key
t.primary(["id"]);
}); });
} }
} }
@@ -38,34 +16,8 @@ export async function up(knex: Knex): Promise<void> {
export async function down(knex: Knex): Promise<void> { export async function down(knex: Knex): Promise<void> {
if (await knex.schema.hasTable(TableName.SecretSharing)) { if (await knex.schema.hasTable(TableName.SecretSharing)) {
await knex.schema.alterTable(TableName.SecretSharing, (t) => { await knex.schema.alterTable(TableName.SecretSharing, (t) => {
// Add a new UUID column // If rolled back, all secrets created with this new structure will stop working.
t.uuid("new_id").nullable(); t.dropColumn("identifier");
});
// Copy data from string id to UUID, ensuring valid UUID format
await knex(TableName.SecretSharing).update({
// @ts-ignore
new_id: knex.raw("id::uuid")
});
await knex.schema.alterTable(TableName.SecretSharing, (t) => {
// Make the new column not nullable
t.uuid("new_id").notNullable().alter();
// Drop the old primary key
t.dropPrimary();
// Drop the old id column
t.dropColumn("id");
// Rename the new column to 'id'
t.renameColumn("new_id", "id");
// Set the new column as primary key
t.primary(["id"]);
// Set the id column to use a UUID default
t.uuid("id").defaultTo(knex.raw("gen_random_uuid()")).notNullable().alter();
}); });
} }
} }
+2 -1
View File
@@ -10,6 +10,7 @@ import { zodBuffer } from "@app/lib/zod";
import { TImmutableDBKeys } from "./models"; import { TImmutableDBKeys } from "./models";
export const SecretSharingSchema = z.object({ export const SecretSharingSchema = z.object({
id: z.string().uuid(),
encryptedValue: z.string().nullable().optional(), encryptedValue: z.string().nullable().optional(),
iv: z.string().nullable().optional(), iv: z.string().nullable().optional(),
tag: z.string().nullable().optional(), tag: z.string().nullable().optional(),
@@ -25,7 +26,7 @@ export const SecretSharingSchema = z.object({
lastViewedAt: z.date().nullable().optional(), lastViewedAt: z.date().nullable().optional(),
password: z.string().nullable().optional(), password: z.string().nullable().optional(),
encryptedSecret: zodBuffer.nullable().optional(), encryptedSecret: zodBuffer.nullable().optional(),
id: z.string() identifier: z.string().nullable().optional()
}); });
export type TSecretSharing = z.infer<typeof SecretSharingSchema>; export type TSecretSharing = z.infer<typeof SecretSharingSchema>;
@@ -82,21 +82,11 @@ export const secretSharingDALFactory = (db: TDbClient) => {
} }
}; };
const create = async (data: Omit<TSecretSharing, "createdAt" | "updatedAt">, tx?: Knex) => {
try {
const [res] = await (tx || db)(TableName.SecretSharing).insert(data).returning("*");
return res;
} catch (error) {
throw new DatabaseError({ error, name: "Create Shared Secret" });
}
};
return { return {
...sharedSecretOrm, ...sharedSecretOrm,
countAllUserOrgSharedSecrets, countAllUserOrgSharedSecrets,
pruneExpiredSharedSecrets, pruneExpiredSharedSecrets,
softDeleteById, softDeleteById,
findActiveSharedSecrets, findActiveSharedSecrets
create
}; };
}; };
@@ -1,6 +1,7 @@
import crypto from "node:crypto"; import crypto from "node:crypto";
import bcrypt from "bcrypt"; import bcrypt from "bcrypt";
import { z } from "zod";
import { TSecretSharing } from "@app/db/schemas"; import { TSecretSharing } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
@@ -27,6 +28,8 @@ type TSecretSharingServiceFactoryDep = {
export type TSecretSharingServiceFactory = ReturnType<typeof secretSharingServiceFactory>; export type TSecretSharingServiceFactory = ReturnType<typeof secretSharingServiceFactory>;
const isUuidV4 = (uuid: string) => z.string().uuid().safeParse(uuid).success;
export const secretSharingServiceFactory = ({ export const secretSharingServiceFactory = ({
permissionService, permissionService,
secretSharingDAL, secretSharingDAL,
@@ -76,7 +79,7 @@ export const secretSharingServiceFactory = ({
const hashedPassword = password ? await bcrypt.hash(password, 10) : null; const hashedPassword = password ? await bcrypt.hash(password, 10) : null;
const newSharedSecret = await secretSharingDAL.create({ const newSharedSecret = await secretSharingDAL.create({
id, identifier: id,
iv: null, iv: null,
tag: null, tag: null,
encryptedValue: null, encryptedValue: null,
@@ -91,7 +94,7 @@ export const secretSharingServiceFactory = ({
accessType accessType
}); });
return { id: `${newSharedSecret.id}${hashedHex}` }; return { id: `${newSharedSecret.identifier}${hashedHex}` };
}; };
const createPublicSharedSecret = async ({ const createPublicSharedSecret = async ({
@@ -126,7 +129,7 @@ export const secretSharingServiceFactory = ({
const hashedPassword = password ? await bcrypt.hash(password, 10) : null; const hashedPassword = password ? await bcrypt.hash(password, 10) : null;
const newSharedSecret = await secretSharingDAL.create({ const newSharedSecret = await secretSharingDAL.create({
id, identifier: id,
encryptedValue: null, encryptedValue: null,
iv: null, iv: null,
tag: null, tag: null,
@@ -139,7 +142,7 @@ export const secretSharingServiceFactory = ({
accessType accessType
}); });
return { id: `${newSharedSecret.id}${hashedHex}` }; return { id: `${newSharedSecret.identifier}${hashedHex}` };
}; };
const getSharedSecrets = async ({ const getSharedSecrets = async ({
@@ -183,22 +186,44 @@ export const secretSharingServiceFactory = ({
const $decrementSecretViewCount = async (sharedSecret: TSecretSharing, sharedSecretId: string) => { const $decrementSecretViewCount = async (sharedSecret: TSecretSharing, sharedSecretId: string) => {
const { expiresAfterViews } = sharedSecret; const { expiresAfterViews } = sharedSecret;
const isUuid = isUuidV4(sharedSecretId);
if (expiresAfterViews) { if (expiresAfterViews) {
// decrement view count if view count expiry set // decrement view count if view count expiry set
await secretSharingDAL.updateById(sharedSecretId, { $decr: { expiresAfterViews: 1 } });
if (isUuid) {
await secretSharingDAL.updateById(sharedSecretId, { $decr: { expiresAfterViews: 1 } });
} else {
await secretSharingDAL.update({ identifier: sharedSecretId }, { $decr: { expiresAfterViews: 1 } });
}
} }
await secretSharingDAL.updateById(sharedSecretId, { if (isUuid) {
lastViewedAt: new Date() await secretSharingDAL.updateById(sharedSecretId, {
}); lastViewedAt: new Date()
});
} else {
await secretSharingDAL.update(
{ identifier: sharedSecretId },
{
lastViewedAt: new Date()
}
);
}
}; };
/** Get's passwordless secret. validates all secret's requested (must be fresh). */ /** Get's password-less secret. validates all secret's requested (must be fresh). */
const getSharedSecretById = async ({ sharedSecretId, hashedHex, orgId, password }: TGetActiveSharedSecretByIdDTO) => { const getSharedSecretById = async ({ sharedSecretId, hashedHex, orgId, password }: TGetActiveSharedSecretByIdDTO) => {
const sharedSecret = await secretSharingDAL.findOne({ const sharedSecret = isUuidV4(sharedSecretId)
id: sharedSecretId, ? await secretSharingDAL.findOne({
hashedHex id: sharedSecretId,
}); hashedHex
})
: await secretSharingDAL.findOne({
hashedHex,
identifier: sharedSecretId
});
if (!sharedSecret) if (!sharedSecret)
throw new NotFoundError({ throw new NotFoundError({
message: "Shared secret not found" message: "Shared secret not found"
@@ -269,7 +294,16 @@ export const secretSharingServiceFactory = ({
const { actor, actorId, orgId, actorAuthMethod, actorOrgId, sharedSecretId } = deleteSharedSecretInput; const { actor, actorId, orgId, actorAuthMethod, actorOrgId, sharedSecretId } = deleteSharedSecretInput;
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
if (!permission) throw new ForbiddenRequestError({ name: "User does not belong to the specified organization" }); if (!permission) throw new ForbiddenRequestError({ name: "User does not belong to the specified organization" });
const deletedSharedSecret = await secretSharingDAL.deleteById(sharedSecretId); const deletedSharedSecret = await secretSharingDAL.deleteById(sharedSecretId);
const sharedSecret = isUuidV4(sharedSecretId)
? await secretSharingDAL.findById(sharedSecretId)
: await secretSharingDAL.findOne({ identifier: sharedSecretId });
if (sharedSecret.orgId && sharedSecret.orgId !== orgId)
throw new ForbiddenRequestError({ message: "User does not have permission to delete shared secret" });
return deletedSharedSecret; return deletedSharedSecret;
}; };