feat(backend): Adding accessType on secret sharing

This commit is contained in:
Alfonso Hernandez
2024-07-19 03:15:38 +02:00
parent 27e14bcafe
commit e45585a909
7 changed files with 84 additions and 15 deletions
@@ -0,0 +1,23 @@
import { Knex } from "knex";
import { SecretSharingAccessType } from "@app/lib/types";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
const hasColumn = await knex.schema.hasColumn(TableName.SecretSharing, "accessType");
if (!hasColumn) {
await knex.schema.table(TableName.SecretSharing, (table) => {
table.string("accessType", 20).notNullable().defaultTo(SecretSharingAccessType.Anyone);
});
}
}
export async function down(knex: Knex): Promise<void> {
const hasColumn = await knex.schema.hasColumn(TableName.SecretSharing, "accessType");
if (hasColumn) {
await knex.schema.table(TableName.SecretSharing, (table) => {
table.dropColumn("accessType");
});
}
}
+3
View File
@@ -5,6 +5,8 @@
import { z } from "zod"; import { z } from "zod";
import { SecretSharingAccessType } from "@app/lib/types";
import { TImmutableDBKeys } from "./models"; import { TImmutableDBKeys } from "./models";
export const SecretSharingSchema = z.object({ export const SecretSharingSchema = z.object({
@@ -16,6 +18,7 @@ export const SecretSharingSchema = z.object({
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(),
accessType: z.nativeEnum(SecretSharingAccessType).default(SecretSharingAccessType.Organization),
createdAt: z.date(), createdAt: z.date(),
updatedAt: z.date(), updatedAt: z.date(),
expiresAfterViews: z.number().nullable().optional() expiresAfterViews: z.number().nullable().optional()
+5
View File
@@ -47,3 +47,8 @@ export enum EnforcementLevel {
Hard = "hard", Hard = "hard",
Soft = "soft" Soft = "soft"
} }
export enum SecretSharingAccessType {
Anyone = "anyone",
Organization = "organization"
}
+2 -1
View File
@@ -737,7 +737,8 @@ export const registerRoutes = async (
const secretSharingService = secretSharingServiceFactory({ const secretSharingService = secretSharingServiceFactory({
permissionService, permissionService,
secretSharingDAL secretSharingDAL,
orgDAL
}); });
const secretApprovalRequestService = secretApprovalRequestServiceFactory({ const secretApprovalRequestService = secretApprovalRequestServiceFactory({
@@ -1,6 +1,7 @@
import { z } from "zod"; import { z } from "zod";
import { SecretSharingSchema } from "@app/db/schemas"; import { SecretSharingSchema } from "@app/db/schemas";
import { SecretSharingAccessType } from "@app/lib/types";
import { import {
publicEndpointLimit, publicEndpointLimit,
publicSecretShareCreationLimit, publicSecretShareCreationLimit,
@@ -55,14 +56,18 @@ export const registerSecretSharingRouter = async (server: FastifyZodProvider) =>
iv: true, iv: true,
tag: true, tag: true,
expiresAt: true, expiresAt: true,
expiresAfterViews: true expiresAfterViews: true,
accessType: true
}).extend({
orgName: z.string().optional()
}) })
} }
}, },
handler: async (req) => { handler: async (req) => {
const sharedSecret = await req.server.services.secretSharing.getActiveSharedSecretByIdAndHashedHex( const sharedSecret = await req.server.services.secretSharing.getActiveSharedSecretByIdAndHashedHex(
req.params.id, req.params.id,
req.query.hashedHex req.query.hashedHex,
req.permission?.orgId
); );
if (!sharedSecret) return undefined; if (!sharedSecret) return undefined;
return { return {
@@ -70,7 +75,9 @@ export const registerSecretSharingRouter = async (server: FastifyZodProvider) =>
iv: sharedSecret.iv, iv: sharedSecret.iv,
tag: sharedSecret.tag, tag: sharedSecret.tag,
expiresAt: sharedSecret.expiresAt, expiresAt: sharedSecret.expiresAt,
expiresAfterViews: sharedSecret.expiresAfterViews expiresAfterViews: sharedSecret.expiresAfterViews,
accessType: sharedSecret.accessType,
orgName: sharedSecret.orgName
}; };
} }
}); });
@@ -88,7 +95,8 @@ export const registerSecretSharingRouter = async (server: FastifyZodProvider) =>
tag: z.string(), tag: z.string(),
hashedHex: z.string(), hashedHex: z.string(),
expiresAt: z.string(), expiresAt: z.string(),
expiresAfterViews: z.number() expiresAfterViews: z.number(),
accessType: z.nativeEnum(SecretSharingAccessType).default(SecretSharingAccessType.Organization)
}), }),
response: { response: {
200: z.object({ 200: z.object({
@@ -97,14 +105,15 @@ export const registerSecretSharingRouter = async (server: FastifyZodProvider) =>
} }
}, },
handler: async (req) => { handler: async (req) => {
const { encryptedValue, iv, tag, hashedHex, expiresAt, expiresAfterViews } = req.body; const { encryptedValue, iv, tag, hashedHex, expiresAt, expiresAfterViews, accessType } = req.body;
const sharedSecret = await req.server.services.secretSharing.createPublicSharedSecret({ const sharedSecret = await req.server.services.secretSharing.createPublicSharedSecret({
encryptedValue, encryptedValue,
iv, iv,
tag, tag,
hashedHex, hashedHex,
expiresAt: new Date(expiresAt), expiresAt: new Date(expiresAt),
expiresAfterViews expiresAfterViews,
accessType
}); });
return { id: sharedSecret.id }; return { id: sharedSecret.id };
} }
@@ -123,7 +132,8 @@ export const registerSecretSharingRouter = async (server: FastifyZodProvider) =>
tag: z.string(), tag: z.string(),
hashedHex: z.string(), hashedHex: z.string(),
expiresAt: z.string(), expiresAt: z.string(),
expiresAfterViews: z.number() expiresAfterViews: z.number(),
accessType: z.nativeEnum(SecretSharingAccessType).default(SecretSharingAccessType.Organization)
}), }),
response: { response: {
200: z.object({ 200: z.object({
@@ -145,7 +155,8 @@ export const registerSecretSharingRouter = async (server: FastifyZodProvider) =>
tag, tag,
hashedHex, hashedHex,
expiresAt: new Date(expiresAt), expiresAt: new Date(expiresAt),
expiresAfterViews expiresAfterViews,
accessType: req.body.accessType
}); });
return { id: sharedSecret.id }; return { id: sharedSecret.id };
} }
@@ -1,6 +1,8 @@
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { BadRequestError, UnauthorizedError } from "@app/lib/errors"; import { BadRequestError, UnauthorizedError } from "@app/lib/errors";
import { SecretSharingAccessType } from "@app/lib/types";
import { TOrgDALFactory } from "../org/org-dal";
import { TSecretSharingDALFactory } from "./secret-sharing-dal"; import { TSecretSharingDALFactory } from "./secret-sharing-dal";
import { import {
TCreatePublicSharedSecretDTO, TCreatePublicSharedSecretDTO,
@@ -12,13 +14,15 @@ import {
type TSecretSharingServiceFactoryDep = { type TSecretSharingServiceFactoryDep = {
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">; permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
secretSharingDAL: TSecretSharingDALFactory; secretSharingDAL: TSecretSharingDALFactory;
orgDAL: TOrgDALFactory;
}; };
export type TSecretSharingServiceFactory = ReturnType<typeof secretSharingServiceFactory>; export type TSecretSharingServiceFactory = ReturnType<typeof secretSharingServiceFactory>;
export const secretSharingServiceFactory = ({ export const secretSharingServiceFactory = ({
permissionService, permissionService,
secretSharingDAL secretSharingDAL,
orgDAL
}: TSecretSharingServiceFactoryDep) => { }: TSecretSharingServiceFactoryDep) => {
const createSharedSecret = async (createSharedSecretInput: TCreateSharedSecretDTO) => { const createSharedSecret = async (createSharedSecretInput: TCreateSharedSecretDTO) => {
const { const {
@@ -30,6 +34,7 @@ export const secretSharingServiceFactory = ({
encryptedValue, encryptedValue,
iv, iv,
tag, tag,
accessType,
hashedHex, hashedHex,
expiresAt, expiresAt,
expiresAfterViews expiresAfterViews
@@ -62,13 +67,14 @@ export const secretSharingServiceFactory = ({
expiresAt, expiresAt,
expiresAfterViews, expiresAfterViews,
userId: actorId, userId: actorId,
orgId orgId,
accessType
}); });
return { id: newSharedSecret.id }; return { id: newSharedSecret.id };
}; };
const createPublicSharedSecret = async (createSharedSecretInput: TCreatePublicSharedSecretDTO) => { const createPublicSharedSecret = async (createSharedSecretInput: TCreatePublicSharedSecretDTO) => {
const { encryptedValue, iv, tag, hashedHex, expiresAt, expiresAfterViews } = createSharedSecretInput; const { encryptedValue, iv, tag, hashedHex, expiresAt, expiresAfterViews, accessType } = createSharedSecretInput;
if (new Date(expiresAt) < new Date()) { if (new Date(expiresAt) < new Date()) {
throw new BadRequestError({ message: "Expiration date cannot be in the past" }); throw new BadRequestError({ message: "Expiration date cannot be in the past" });
} }
@@ -92,7 +98,8 @@ export const secretSharingServiceFactory = ({
tag, tag,
hashedHex, hashedHex,
expiresAt, expiresAt,
expiresAfterViews expiresAfterViews,
accessType
}); });
return { id: newSharedSecret.id }; return { id: newSharedSecret.id };
}; };
@@ -105,9 +112,21 @@ export const secretSharingServiceFactory = ({
return userSharedSecrets; return userSharedSecrets;
}; };
const getActiveSharedSecretByIdAndHashedHex = async (sharedSecretId: string, hashedHex: string) => { const getActiveSharedSecretByIdAndHashedHex = async (sharedSecretId: string, hashedHex: string, orgId?: string) => {
const sharedSecret = await secretSharingDAL.findOne({ id: sharedSecretId, hashedHex }); const sharedSecret = await secretSharingDAL.findOne({ id: sharedSecretId, hashedHex });
if (!sharedSecret) return; if (!sharedSecret) return;
const orgName = sharedSecret.orgId ? (await orgDAL.findOrgById(sharedSecret.orgId))?.name : "";
// Support organization level access for secret sharing
if (sharedSecret.accessType === SecretSharingAccessType.Organization && orgId !== sharedSecret.orgId) {
return {
...sharedSecret,
encryptedValue: "",
iv: "",
tag: "",
orgName
};
}
if (sharedSecret.expiresAt && sharedSecret.expiresAt < new Date()) { if (sharedSecret.expiresAt && sharedSecret.expiresAt < new Date()) {
return; return;
} }
@@ -118,7 +137,10 @@ export const secretSharingServiceFactory = ({
} }
await secretSharingDAL.updateById(sharedSecretId, { $decr: { expiresAfterViews: 1 } }); await secretSharingDAL.updateById(sharedSecretId, { $decr: { expiresAfterViews: 1 } });
} }
return sharedSecret; if (sharedSecret.accessType === SecretSharingAccessType.Organization) {
return { ...sharedSecret, orgName };
}
return { ...sharedSecret, orgName: undefined };
}; };
const deleteSharedSecretById = async (deleteSharedSecretInput: TDeleteSharedSecretDTO) => { const deleteSharedSecretById = async (deleteSharedSecretInput: TDeleteSharedSecretDTO) => {
@@ -1,3 +1,5 @@
import { SecretSharingAccessType } from "@app/lib/types";
import { ActorAuthMethod, ActorType } from "../auth/auth-type"; import { ActorAuthMethod, ActorType } from "../auth/auth-type";
export type TSharedSecretPermission = { export type TSharedSecretPermission = {
@@ -6,6 +8,7 @@ export type TSharedSecretPermission = {
actorAuthMethod: ActorAuthMethod; actorAuthMethod: ActorAuthMethod;
actorOrgId: string; actorOrgId: string;
orgId: string; orgId: string;
accessType?: SecretSharingAccessType;
}; };
export type TCreatePublicSharedSecretDTO = { export type TCreatePublicSharedSecretDTO = {
@@ -15,6 +18,7 @@ export type TCreatePublicSharedSecretDTO = {
hashedHex: string; hashedHex: string;
expiresAt: Date; expiresAt: Date;
expiresAfterViews: number; expiresAfterViews: number;
accessType: SecretSharingAccessType;
}; };
export type TCreateSharedSecretDTO = TSharedSecretPermission & TCreatePublicSharedSecretDTO; export type TCreateSharedSecretDTO = TSharedSecretPermission & TCreatePublicSharedSecretDTO;