fix: requested changes

This commit is contained in:
Daniel Hougaard
2024-10-01 00:16:18 +04:00
parent 9c33251c44
commit b65842f5c1
8 changed files with 28 additions and 63 deletions
@@ -10,6 +10,11 @@ export async function up(knex: Knex): Promise<void> {
t.string("encryptedValue").nullable().alter(); t.string("encryptedValue").nullable().alter();
t.binary("encryptedSecret").nullable(); t.binary("encryptedSecret").nullable();
t.string("hashedHex").nullable().alter();
t.string("identifier", 64).nullable();
t.unique("identifier");
t.index("identifier");
}); });
} }
} }
@@ -17,11 +22,9 @@ 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) => {
t.string("iv").notNullable().alter();
t.string("tag").notNullable().alter();
t.string("encryptedValue").notNullable().alter();
t.dropColumn("encryptedSecret"); t.dropColumn("encryptedSecret");
t.dropColumn("identifier");
}); });
} }
} }
@@ -1,23 +0,0 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
if (await knex.schema.hasTable(TableName.SecretSharing)) {
await knex.schema.alterTable(TableName.SecretSharing, (t) => {
t.string("identifier", 36).nullable();
t.unique("identifier");
t.index("identifier");
});
}
}
export async function down(knex: Knex): Promise<void> {
if (await knex.schema.hasTable(TableName.SecretSharing)) {
await knex.schema.alterTable(TableName.SecretSharing, (t) => {
// If rolled back, all secrets created with this new structure will stop working.
t.dropColumn("identifier");
});
}
}
+1 -1
View File
@@ -14,7 +14,7 @@ export const SecretSharingSchema = z.object({
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(),
hashedHex: z.string(), hashedHex: z.string().nullable().optional(),
expiresAt: z.date(), expiresAt: z.date(),
userId: z.string().uuid().nullable().optional(), userId: z.string().uuid().nullable().optional(),
orgId: z.string().uuid().nullable().optional(), orgId: z.string().uuid().nullable().optional(),
@@ -58,7 +58,7 @@ export const registerSecretSharingRouter = async (server: FastifyZodProvider) =>
id: z.string() id: z.string()
}), }),
body: z.object({ body: z.object({
hashedHex: z.string().min(1), hashedHex: z.string().min(1).optional(),
password: z.string().optional() password: z.string().optional()
}), }),
response: { response: {
@@ -72,10 +72,7 @@ export const secretSharingServiceFactory = ({
const encryptedSecret = encryptWithRoot(Buffer.from(secretValue)); const encryptedSecret = encryptWithRoot(Buffer.from(secretValue));
// This will be 36 characters long, due to encoding it to base64. const id = crypto.randomBytes(32).toString("hex");
const id = crypto.randomBytes(27).toString("base64url");
const hashedHex = crypto.createHash("sha256").update(id).digest("base64url").substring(0, 13);
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({
@@ -84,7 +81,6 @@ export const secretSharingServiceFactory = ({
tag: null, tag: null,
encryptedValue: null, encryptedValue: null,
encryptedSecret, encryptedSecret,
hashedHex,
name, name,
password: hashedPassword, password: hashedPassword,
expiresAt: new Date(expiresAt), expiresAt: new Date(expiresAt),
@@ -94,7 +90,9 @@ export const secretSharingServiceFactory = ({
accessType accessType
}); });
return { id: `${newSharedSecret.identifier}${hashedHex}` }; const idToReturn = `${Buffer.from(newSharedSecret.identifier!, "hex").toString("base64url")}`;
return { id: idToReturn };
}; };
const createPublicSharedSecret = async ({ const createPublicSharedSecret = async ({
@@ -124,8 +122,7 @@ export const secretSharingServiceFactory = ({
const encryptWithRoot = kmsService.encryptWithRootKey(); const encryptWithRoot = kmsService.encryptWithRootKey();
const encryptedSecret = encryptWithRoot(Buffer.from(secretValue)); const encryptedSecret = encryptWithRoot(Buffer.from(secretValue));
const id = crypto.randomBytes(27).toString("base64url"); const id = crypto.randomBytes(32).toString("hex");
const hashedHex = crypto.createHash("sha256").update(id).digest("base64url").substring(0, 13);
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({
@@ -133,16 +130,14 @@ export const secretSharingServiceFactory = ({
encryptedValue: null, encryptedValue: null,
iv: null, iv: null,
tag: null, tag: null,
hashedHex,
encryptedSecret, encryptedSecret,
password: hashedPassword, password: hashedPassword,
expiresAt: new Date(expiresAt), expiresAt: new Date(expiresAt),
expiresAfterViews, expiresAfterViews,
accessType accessType
}); });
return { id: `${newSharedSecret.identifier}${hashedHex}` }; return { id: `${Buffer.from(newSharedSecret.identifier!, "hex").toString("base64url")}` };
}; };
const getSharedSecrets = async ({ const getSharedSecrets = async ({
@@ -220,8 +215,7 @@ export const secretSharingServiceFactory = ({
hashedHex hashedHex
}) })
: await secretSharingDAL.findOne({ : await secretSharingDAL.findOne({
hashedHex, identifier: Buffer.from(sharedSecretId, "base64url").toString("hex")
identifier: sharedSecretId
}); });
if (!sharedSecret) if (!sharedSecret)
@@ -295,12 +289,12 @@ export const secretSharingServiceFactory = ({
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 sharedSecret = isUuidV4(sharedSecretId) const sharedSecret = isUuidV4(sharedSecretId)
? await secretSharingDAL.findById(sharedSecretId) ? await secretSharingDAL.findById(sharedSecretId)
: await secretSharingDAL.findOne({ identifier: sharedSecretId }); : await secretSharingDAL.findOne({ identifier: sharedSecretId });
const deletedSharedSecret = await secretSharingDAL.deleteById(sharedSecretId);
if (sharedSecret.orgId && sharedSecret.orgId !== orgId) if (sharedSecret.orgId && sharedSecret.orgId !== orgId)
throw new ForbiddenRequestError({ message: "User does not have permission to delete shared secret" }); throw new ForbiddenRequestError({ message: "User does not have permission to delete shared secret" });
@@ -28,7 +28,7 @@ export type TCreatePublicSharedSecretDTO = {
export type TGetActiveSharedSecretByIdDTO = { export type TGetActiveSharedSecretByIdDTO = {
sharedSecretId: string; sharedSecretId: string;
hashedHex: string; hashedHex?: string;
orgId?: string; orgId?: string;
password?: string; password?: string;
}; };
@@ -8,7 +8,7 @@ export const secretSharingKeys = {
allSharedSecrets: () => ["sharedSecrets"] as const, allSharedSecrets: () => ["sharedSecrets"] as const,
specificSharedSecrets: ({ offset, limit }: { offset: number; limit: number }) => specificSharedSecrets: ({ offset, limit }: { offset: number; limit: number }) =>
[...secretSharingKeys.allSharedSecrets(), { offset, limit }] as const, [...secretSharingKeys.allSharedSecrets(), { offset, limit }] as const,
getSecretById: (arg: { id: string; hashedHex: string; password?: string }) => [ getSecretById: (arg: { id: string; hashedHex: string | null; password?: string }) => [
"shared-secret", "shared-secret",
arg arg
] ]
@@ -46,7 +46,7 @@ export const useGetActiveSharedSecretById = ({
password password
}: { }: {
sharedSecretId: string; sharedSecretId: string;
hashedHex: string; hashedHex: string | null;
password?: string; password?: string;
}) => { }) => {
return useQuery<TViewSharedSecretResponse>( return useQuery<TViewSharedSecretResponse>(
@@ -55,7 +55,7 @@ export const useGetActiveSharedSecretById = ({
const { data } = await apiRequest.post<TViewSharedSecretResponse>( const { data } = await apiRequest.post<TViewSharedSecretResponse>(
`/api/v1/secret-sharing/public/${sharedSecretId}`, `/api/v1/secret-sharing/public/${sharedSecretId}`,
{ {
hashedHex, ...(hashedHex && { hashedHex }),
password password
} }
); );
@@ -63,7 +63,7 @@ export const useGetActiveSharedSecretById = ({
return data; return data;
}, },
{ {
enabled: Boolean(sharedSecretId) && Boolean(hashedHex) enabled: Boolean(sharedSecretId)
} }
); );
}; };
@@ -15,14 +15,6 @@ const extractDetailsFromUrl = (router: NextRouter) => {
const idString = id as string; const idString = id as string;
if (!idString) {
return {
id: "",
hashedHex: "",
key: null
};
}
if (urlEncodedKey) { if (urlEncodedKey) {
const [hashedHex, key] = urlEncodedKey ? urlEncodedKey.toString().split("-") : ["", ""]; const [hashedHex, key] = urlEncodedKey ? urlEncodedKey.toString().split("-") : ["", ""];
@@ -33,13 +25,9 @@ const extractDetailsFromUrl = (router: NextRouter) => {
}; };
} }
// get the first 36 characters as id and the rest as hex
const idPart = idString.substring(0, 36);
const hexPart = idString.substring(36);
return { return {
id: idPart || "", id: idString,
hashedHex: hexPart || "", hashedHex: null,
key: null key: null
}; };
}; };
@@ -65,6 +53,9 @@ export const ViewSecretPublicPage = () => {
((error as AxiosError)?.response?.data as { message: string })?.message === ((error as AxiosError)?.response?.data as { message: string })?.message ===
"Invalid credentials"; "Invalid credentials";
console.log("data", fetchSecret);
console.log("err", error);
const shouldShowPasswordPrompt = const shouldShowPasswordPrompt =
isInvalidCredential || (fetchSecret?.isPasswordProtected && !fetchSecret.secret); isInvalidCredential || (fetchSecret?.isPasswordProtected && !fetchSecret.secret);
const isValidatingPassword = Boolean(password) && isFetching; const isValidatingPassword = Boolean(password) && isFetching;