feat(infisical-pg): added secret version api, fixed pass reset flow, smtp connection issue fixed

This commit is contained in:
Akhil Mohan
2024-01-27 12:38:23 +05:30
parent 31c0bf6831
commit 5373cb6afb
18 changed files with 244 additions and 39 deletions
+2
View File
@@ -8,6 +8,7 @@ import { registerSecretApprovalRequestRouter } from "./secret-approval-request-r
import { registerSecretRotationProviderRouter } from "./secret-rotation-provider-router"; import { registerSecretRotationProviderRouter } from "./secret-rotation-provider-router";
import { registerSecretRotationRouter } from "./secret-rotation-router"; import { registerSecretRotationRouter } from "./secret-rotation-router";
import { registerSecretScanningRouter } from "./secret-scanning-router"; import { registerSecretScanningRouter } from "./secret-scanning-router";
import { registerSecretVersionRouter } from "./secret-version-router";
import { registerSnapshotRouter } from "./snapshot-router"; import { registerSnapshotRouter } from "./snapshot-router";
import { registerTrustedIpRouter } from "./trusted-ip-router"; import { registerTrustedIpRouter } from "./trusted-ip-router";
@@ -34,4 +35,5 @@ export const registerV1EERoutes = async (server: FastifyZodProvider) => {
await server.register(registerSamlRouter, { prefix: "/sso" }); await server.register(registerSamlRouter, { prefix: "/sso" });
await server.register(registerSecretScanningRouter, { prefix: "/secret-scanning" }); await server.register(registerSecretScanningRouter, { prefix: "/secret-scanning" });
await server.register(registerSecretRotationRouter, { prefix: "/secret-rotations" }); await server.register(registerSecretRotationRouter, { prefix: "/secret-rotations" });
await server.register(registerSecretVersionRouter, { prefix: "/secret" });
}; };
@@ -0,0 +1,37 @@
import { z } from "zod";
import { SecretVersionsSchema } from "@app/db/schemas";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
export const registerSecretVersionRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/:secretId/secret-versions",
method: "GET",
schema: {
params: z.object({
secretId: z.string()
}),
querystring: z.object({
offset: z.coerce.number(),
limit: z.coerce.number()
}),
response: {
200: z.object({
secretVersions: SecretVersionsSchema.omit({ secretBlindIndex: true }).array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const secretVersions = await server.services.secret.getSecretVersions({
actor: req.permission.type,
actorId: req.permission.id,
limit: req.query.limit,
offset: req.query.offset,
secretId: req.params.secretId
});
return { secretVersions };
}
});
};
@@ -6,12 +6,12 @@ import { logger } from "@app/lib/logger";
import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue"; import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue";
import { TOrgDalFactory } from "@app/services/org/org-dal"; import { TOrgDalFactory } from "@app/services/org/org-dal";
import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service"; import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service";
import { TUserDalFactory } from "@app/services/user/user-dal";
import { TSecretScanningDalFactory } from "../secret-scanning-dal"; import { TSecretScanningDalFactory } from "../secret-scanning-dal";
import { import {
scanContentAndGetFindings, scanContentAndGetFindings,
scanFullRepoContentAndGetFindings} from "./secret-scanning-fns"; scanFullRepoContentAndGetFindings
} from "./secret-scanning-fns";
import { import {
SecretMatch, SecretMatch,
TScanFullRepoEventPayload, TScanFullRepoEventPayload,
@@ -23,7 +23,6 @@ type TSecretScanningQueueFactoryDep = {
secretScanningDal: TSecretScanningDalFactory; secretScanningDal: TSecretScanningDalFactory;
smtpService: Pick<TSmtpService, "sendMail">; smtpService: Pick<TSmtpService, "sendMail">;
orgMembershipDal: Pick<TOrgDalFactory, "findMembership">; orgMembershipDal: Pick<TOrgDalFactory, "findMembership">;
userDal: Pick<TUserDalFactory, "find">;
}; };
export type TSecretScanningQueueFactory = ReturnType<typeof secretScanningQueueFactory>; export type TSecretScanningQueueFactory = ReturnType<typeof secretScanningQueueFactory>;
@@ -33,7 +32,6 @@ export const secretScanningQueueFactory = ({
secretScanningDal, secretScanningDal,
smtpService, smtpService,
orgMembershipDal: orgMemberDal, orgMembershipDal: orgMemberDal,
userDal
}: TSecretScanningQueueFactoryDep) => { }: TSecretScanningQueueFactoryDep) => {
const startFullRepoScan = async (payload: TScanFullRepoEventPayload) => { const startFullRepoScan = async (payload: TScanFullRepoEventPayload) => {
await queueService.queue(QueueName.SecretFullRepoScan, QueueJobs.SecretScan, payload, { await queueService.queue(QueueName.SecretFullRepoScan, QueueJobs.SecretScan, payload, {
@@ -69,12 +67,7 @@ export const secretScanningQueueFactory = ({
orgId: organizationId, orgId: organizationId,
role: OrgMembershipRole.Admin role: OrgMembershipRole.Admin
}); });
const userEmails = await userDal.find({ return adminsOfWork.map((userObject) => userObject.email);
$in: {
id: adminsOfWork.map(({ userId }) => userId).filter(Boolean) as string[]
}
});
return userEmails.map((userObject) => userObject.email);
}; };
queueService.start(QueueName.SecretPushEventScan, async (job) => { queueService.start(QueueName.SecretPushEventScan, async (job) => {
-1
View File
@@ -251,7 +251,6 @@ export const registerRoutes = async (
const apiKeyService = apiKeyServiceFactory({ apiKeyDal, userDal }); const apiKeyService = apiKeyServiceFactory({ apiKeyDal, userDal });
const secretScanningQueue = secretScanningQueueFactory({ const secretScanningQueue = secretScanningQueueFactory({
userDal,
smtpService, smtpService,
secretScanningDal, secretScanningDal,
queueService, queueService,
@@ -31,6 +31,8 @@ export const sapPubSchema = SecretApprovalPoliciesSchema.merge(
export const secretRawSchema = z.object({ export const secretRawSchema = z.object({
id: z.string(), id: z.string(),
_id: z.string(), _id: z.string(),
workspace: z.string(),
environment: z.string(),
version: z.number(), version: z.number(),
type: z.string(), type: z.string(),
secretKey: z.string(), secretKey: z.string(),
@@ -1,8 +1,9 @@
import { z } from "zod"; import { z } from "zod";
import { BackupPrivateKeySchema } from "@app/db/schemas"; import { BackupPrivateKeySchema, UsersSchema } from "@app/db/schemas";
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { validateSignUpAuthorization } from "@app/services/auth/auth-fns";
import { AuthMode } from "@app/services/auth/auth-type"; import { AuthMode } from "@app/services/auth/auth-type";
export const registerPasswordRouter = async (server: FastifyZodProvider) => { export const registerPasswordRouter = async (server: FastifyZodProvider) => {
@@ -66,6 +67,58 @@ export const registerPasswordRouter = async (server: FastifyZodProvider) => {
} }
}); });
server.route({
method: "POST",
url: "/email/password-reset",
schema: {
body: z.object({
email: z.string().email().trim()
}),
response: {
200: z.object({
message: z.string()
})
}
},
handler: async (req) => {
await server.services.password.sendPasswordResetEmail(req.body.email);
return {
message: "If an account exists with this email, a password reset link has been sent"
};
}
});
server.route({
method: "POST",
url: "/email/password-reset-verify",
schema: {
body: z.object({
email: z.string().email().trim(),
code: z.string().trim()
}),
response: {
200: z.object({
message: z.string(),
user: UsersSchema,
token: z.string()
})
}
},
handler: async (req) => {
const { token, user } = await server.services.password.verifyPasswordResetEmail(
req.body.email,
req.body.code
);
return {
message: "Successfully verified email",
user,
token
};
}
});
server.route({ server.route({
method: "POST", method: "POST",
url: "/backup-private-key", url: "/backup-private-key",
@@ -87,9 +140,10 @@ export const registerPasswordRouter = async (server: FastifyZodProvider) => {
} }
}, },
handler: async (req) => { handler: async (req) => {
const token = validateSignUpAuthorization(req.headers.authorization as string, "",false)!
const backupPrivateKey = await server.services.password.createBackupPrivateKey({ const backupPrivateKey = await server.services.password.createBackupPrivateKey({
...req.body, ...req.body,
userId: req.permission.id userId: token.userId,
}); });
if (!backupPrivateKey) throw new Error("Failed to create backup key"); if (!backupPrivateKey) throw new Error("Failed to create backup key");
@@ -100,7 +154,6 @@ export const registerPasswordRouter = async (server: FastifyZodProvider) => {
server.route({ server.route({
method: "GET", method: "GET",
url: "/backup-private-key", url: "/backup-private-key",
onRequest: verifyAuth([AuthMode.JWT]),
schema: { schema: {
response: { response: {
200: z.object({ 200: z.object({
@@ -110,8 +163,9 @@ export const registerPasswordRouter = async (server: FastifyZodProvider) => {
} }
}, },
handler: async (req) => { handler: async (req) => {
const token = validateSignUpAuthorization(req.headers.authorization as string, "",false)!
const backupPrivateKey = await server.services.password.getBackupPrivateKeyOfUser( const backupPrivateKey = await server.services.password.getBackupPrivateKeyOfUser(
req.permission.id token.userId
); );
if (!backupPrivateKey) throw new Error("Failed to find backup key"); if (!backupPrivateKey) throw new Error("Failed to find backup key");
@@ -121,8 +175,7 @@ export const registerPasswordRouter = async (server: FastifyZodProvider) => {
server.route({ server.route({
method: "POST", method: "POST",
url: "/email/password-reset", url: "/password-reset",
onRequest: verifyAuth([AuthMode.JWT]),
schema: { schema: {
body: z.object({ body: z.object({
protectedKey: z.string().trim(), protectedKey: z.string().trim(),
@@ -141,9 +194,10 @@ export const registerPasswordRouter = async (server: FastifyZodProvider) => {
} }
}, },
handler: async (req) => { handler: async (req) => {
const token = validateSignUpAuthorization(req.headers.authorization as string, "",false)!
await server.services.password.resetPasswordByBackupKey({ await server.services.password.resetPasswordByBackupKey({
...req.body, ...req.body,
userId: req.permission.id userId: token.userId,
}); });
return { message: "Successfully updated backup private key" }; return { message: "Successfully updated backup private key" };
@@ -65,7 +65,7 @@ export const registerSignupRouter = async (server: FastifyZodProvider) => {
encryptedPrivateKeyTag: z.string().trim(), encryptedPrivateKeyTag: z.string().trim(),
salt: z.string().trim(), salt: z.string().trim(),
verifier: z.string().trim(), verifier: z.string().trim(),
organizationName: z.string().trim(), organizationName: z.string().trim().min(1),
providerAuthToken: z.string().trim().optional().nullish(), providerAuthToken: z.string().trim().optional().nullish(),
attributionSource: z.string().trim().optional() attributionSource: z.string().trim().optional()
}), }),
+2 -1
View File
@@ -21,7 +21,7 @@ export const validateProviderAuthToken = (providerToken: string, email: string)
if (decodedToken.email !== email) throw new Error("Invalid auth credentials"); if (decodedToken.email !== email) throw new Error("Invalid auth credentials");
}; };
export const validateSignUpAuthorization = async (token: string, userId: string) => { export const validateSignUpAuthorization = (token: string, userId: string, validate = true) => {
const appCfg = getConfig(); const appCfg = getConfig();
const [AUTH_TOKEN_TYPE, AUTH_TOKEN_VALUE] = <[string, string]>token?.split(" ", 2) ?? [ const [AUTH_TOKEN_TYPE, AUTH_TOKEN_VALUE] = <[string, string]>token?.split(" ", 2) ?? [
null, null,
@@ -45,6 +45,7 @@ export const validateSignUpAuthorization = async (token: string, userId: string)
AUTH_TOKEN_VALUE, AUTH_TOKEN_VALUE,
appCfg.JWT_AUTH_SECRET appCfg.JWT_AUTH_SECRET
) as AuthModeProviderSignUpTokenPayload; ) as AuthModeProviderSignUpTokenPayload;
if (!validate) return decodedToken;
if (decodedToken.authTokenType !== AuthTokenType.SIGNUP_TOKEN) throw new UnauthorizedError(); if (decodedToken.authTokenType !== AuthTokenType.SIGNUP_TOKEN) throw new UnauthorizedError();
if (decodedToken.userId !== userId) throw new UnauthorizedError(); if (decodedToken.userId !== userId) throw new UnauthorizedError();
+35 -3
View File
@@ -10,7 +10,13 @@ import {
TOrgMembershipsUpdate TOrgMembershipsUpdate
} from "@app/db/schemas"; } from "@app/db/schemas";
import { DatabaseError } from "@app/lib/errors"; import { DatabaseError } from "@app/lib/errors";
import { ormify, withTransaction } from "@app/lib/knex"; import {
buildFindFilter,
selectAllTableCols,
TFindFilter,
TFindOpt,
withTransaction
} from "@app/lib/knex";
export type TOrgDalFactory = ReturnType<typeof orgDalFactory>; export type TOrgDalFactory = ReturnType<typeof orgDalFactory>;
@@ -109,7 +115,7 @@ export const orgDalFactory = (db: TDbClient) => {
// MEMBERSHIP OPERATIONS // MEMBERSHIP OPERATIONS
// -------------------- // --------------------
const orgMembershipOrm = ormify(db, TableName.OrgMembership); // const orgMembershipOrm = ormify(db, TableName.OrgMembership);
const createMembership = async (data: TOrgMembershipsInsert, tx?: Knex) => { const createMembership = async (data: TOrgMembershipsInsert, tx?: Knex) => {
try { try {
@@ -160,6 +166,32 @@ export const orgDalFactory = (db: TDbClient) => {
} }
}; };
const findMembership = async (
filter: TFindFilter<TOrgMemberships>,
{ offset, limit, sort, tx }: TFindOpt<TOrgMemberships> = {}
) => {
try {
const query = (tx || db)(TableName.OrgMembership)
.where(buildFindFilter(filter))
.join(TableName.Users, `${TableName.Users}.id`, `${TableName.OrgMembership}.userId`)
.select(
selectAllTableCols(TableName.OrgMembership),
db.ref("email").withSchema(TableName.Users)
);
if (limit) query.limit(limit);
if (offset) query.offset(offset);
if (sort) {
query.orderBy(
sort.map(([column, order, nulls]) => ({ column: column as string, order, nulls }))
);
}
const res = await query;
return res;
} catch (error) {
throw new DatabaseError({ error, name: "Find one" });
}
};
return withTransaction(db, { return withTransaction(db, {
findAllOrgMembers, findAllOrgMembers,
findOrgById, findOrgById,
@@ -167,7 +199,7 @@ export const orgDalFactory = (db: TDbClient) => {
create, create,
updateById, updateById,
deleteById, deleteById,
findMembership: orgMembershipOrm.find, findMembership,
createMembership, createMembership,
updateMembershipById, updateMembershipById,
deleteMembershipById, deleteMembershipById,
@@ -1,6 +1,6 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { OrgMembershipStatus, ProjectMembershipRole } from "@app/db/schemas"; import { OrgMembershipStatus, ProjectMembershipRole, TableName } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { import {
@@ -142,7 +142,11 @@ export const projectMembershipServiceFactory = ({
); );
const orgMembers = await orgDal.findMembership({ const orgMembers = await orgDal.findMembership({
orgId: project.orgId, orgId: project.orgId,
$in: { id: members.map(({ orgMembershipId }) => orgMembershipId) } $in: {
[`${TableName.OrgMembership}.id` as "id"]: members.map(
({ orgMembershipId }) => orgMembershipId
)
}
}); });
if (orgMembers.length !== members.length) if (orgMembers.length !== members.length)
throw new BadRequestError({ message: "Some users are not part of org" }); throw new BadRequestError({ message: "Some users are not part of org" });
@@ -180,7 +184,7 @@ export const projectMembershipServiceFactory = ({
await smtpService.sendMail({ await smtpService.sendMail({
template: SmtpTemplates.WorkspaceInvite, template: SmtpTemplates.WorkspaceInvite,
subjectLine: "Infisical workspace invitation", subjectLine: "Infisical workspace invitation",
recipients: orgMembers.map(({ userId }) => userId).filter(Boolean) as string[], recipients: orgMembers.map(({ email }) => email).filter(Boolean) as string[],
substitutions: { substitutions: {
inviterFirstName: sender.firstName, inviterFirstName: sender.firstName,
inviterEmail: sender.email, inviterEmail: sender.email,
+6 -1
View File
@@ -197,7 +197,10 @@ export const interpolateSecrets = ({
return expandSecrets; return expandSecrets;
}; };
export const decryptSecretRaw = (secret: TSecrets, key: string) => { export const decryptSecretRaw = (
secret: TSecrets & { workspace: string; environment: string },
key: string
) => {
const secretKey = decryptSymmetric128BitHexKeyUTF8({ const secretKey = decryptSymmetric128BitHexKeyUTF8({
ciphertext: secret.secretKeyCiphertext, ciphertext: secret.secretKeyCiphertext,
iv: secret.secretKeyIV, iv: secret.secretKeyIV,
@@ -225,6 +228,8 @@ export const decryptSecretRaw = (secret: TSecrets, key: string) => {
return { return {
secretKey, secretKey,
workspace: secret.workspace,
environment: secret.environment,
secretValue, secretValue,
secretComment, secretComment,
version: secret.version, version: secret.version,
@@ -38,6 +38,7 @@ import {
TGetASecretRawDTO, TGetASecretRawDTO,
TGetSecretsDTO, TGetSecretsDTO,
TGetSecretsRawDTO, TGetSecretsRawDTO,
TGetSecretVersionsDTO,
TListSecretVersionDTO, TListSecretVersionDTO,
TUpdateBulkSecretDTO, TUpdateBulkSecretDTO,
TUpdateSecretDTO, TUpdateSecretDTO,
@@ -326,7 +327,7 @@ export const secretServiceFactory = ({
await snapshotService.performSnapshot(folderId); await snapshotService.performSnapshot(folderId);
await secretQueueService.syncSecrets({ secretPath: path, projectId, environment }); await secretQueueService.syncSecrets({ secretPath: path, projectId, environment });
// TODO(akhilmhdh-pg): licence check, posthog service and snapshot // TODO(akhilmhdh-pg): licence check, posthog service and snapshot
return { ...secret[0], tags }; return { ...secret[0], environment, workspace: projectId, tags };
}; };
const updateSecret = async ({ const updateSecret = async ({
@@ -419,7 +420,7 @@ export const secretServiceFactory = ({
await snapshotService.performSnapshot(folderId); await snapshotService.performSnapshot(folderId);
await secretQueueService.syncSecrets({ secretPath: path, projectId, environment }); await secretQueueService.syncSecrets({ secretPath: path, projectId, environment });
// TODO(akhilmhdh-pg): licence check, posthog service and snapshot // TODO(akhilmhdh-pg): licence check, posthog service and snapshot
return updatedSecret[0]; return { ...updatedSecret[0], workspace: projectId, environment };
}; };
const deleteSecret = async ({ const deleteSecret = async ({
@@ -474,7 +475,7 @@ export const secretServiceFactory = ({
await secretQueueService.syncSecrets({ secretPath: path, projectId, environment }); await secretQueueService.syncSecrets({ secretPath: path, projectId, environment });
// TODO(akhilmhdh-pg): licence check, posthog service and snapshot // TODO(akhilmhdh-pg): licence check, posthog service and snapshot
return deletedSecret[0]; return { ...deletedSecret[0], workspace: projectId, environment };
}; };
const getSecrets = async ({ const getSecrets = async ({
@@ -515,9 +516,12 @@ export const secretServiceFactory = ({
secretDal, secretDal,
folderDal folderDal
}); });
return { secrets, imports: importedSecrets }; return {
secrets: secrets.map((el) => ({ ...el, workspace: projectId, environment })),
imports: importedSecrets
};
} }
return { secrets }; return { secrets: secrets.map((el) => ({ ...el, workspace: projectId, environment })) };
}; };
const getASecret = async ({ const getASecret = async ({
@@ -572,14 +576,18 @@ export const secretServiceFactory = ({
for (let i = importedSecrets.length - 1; i >= 0; i -= 1) { for (let i = importedSecrets.length - 1; i >= 0; i -= 1) {
for (let j = 0; j < importedSecrets[i].secrets.length; j += 1) { for (let j = 0; j < importedSecrets[i].secrets.length; j += 1) {
if (secretBlindIndex === importedSecrets[i].secrets[j].secretBlindIndex) { if (secretBlindIndex === importedSecrets[i].secrets[j].secretBlindIndex) {
return importedSecrets[i].secrets[j]; return {
...importedSecrets[i].secrets[j],
workspace: projectId,
environment: importedSecrets[i].environment.slug
};
} }
} }
} }
} }
if (!secret) throw new BadRequestError({ message: "Secret not found" }); if (!secret) throw new BadRequestError({ message: "Secret not found" });
return secret; return { ...secret, workspace: projectId, environment };
}; };
const createManySecret = async ({ const createManySecret = async ({
@@ -813,7 +821,12 @@ export const secretServiceFactory = ({
secrets: secrets.map((el) => decryptSecretRaw(el, botKey)), secrets: secrets.map((el) => decryptSecretRaw(el, botKey)),
imports: (imports || [])?.map(({ secrets: importedSecrets, ...el }) => ({ imports: (imports || [])?.map(({ secrets: importedSecrets, ...el }) => ({
...el, ...el,
secrets: importedSecrets.map((sec) => decryptSecretRaw(sec, botKey)) secrets: importedSecrets.map((sec) =>
decryptSecretRaw(
{ ...sec, environment: el.environment.slug, workspace: projectId },
botKey
)
)
})) }))
}; };
}; };
@@ -953,6 +966,36 @@ export const secretServiceFactory = ({
return decryptSecretRaw(secret, botKey); return decryptSecretRaw(secret, botKey);
}; };
const getSecretVersions = async ({
actorId,
actor,
limit = 20,
offset = 0,
secretId
}: TGetSecretVersionsDTO) => {
const secret = await secretDal.findById(secretId);
if (!secret) throw new BadRequestError({ message: "Failed to find secret" });
const folder = await folderDal.findById(secret.folderId);
if (!folder) throw new BadRequestError({ message: "Failed to find secret" });
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
folder.projectId
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.SecretRollback
);
const secretVersions = await secretVersionDal.find(
{ secretId },
{ offset, limit, sort: [["createdAt", "desc"]] }
);
return secretVersions;
};
return { return {
createSecret, createSecret,
deleteSecret, deleteSecret,
@@ -968,6 +1011,7 @@ export const secretServiceFactory = ({
updateSecretRaw, updateSecretRaw,
deleteSecretRaw, deleteSecretRaw,
listSecretVersionsBySecretId, listSecretVersionsBySecretId,
getSecretVersions,
// external services function // external services function
fnSecretBulkDelete, fnSecretBulkDelete,
fnSecretBulkUpdate, fnSecretBulkUpdate,
@@ -167,6 +167,12 @@ export type TDeleteSecretRawDTO = TProjectPermission & {
type: SecretType; type: SecretType;
}; };
export type TGetSecretVersionsDTO = Omit<TProjectPermission, "projectId"> & {
limit?: number;
offset?: number;
secretId: string;
};
export type TFnSecretBulkInsert = { export type TFnSecretBulkInsert = {
folderId: string; folderId: string;
tx?: Knex; tx?: Knex;
+10
View File
@@ -74,6 +74,16 @@ services:
- NEXT_PUBLIC_ENV=development - NEXT_PUBLIC_ENV=development
- INFISICAL_TELEMETRY_ENABLED=false - INFISICAL_TELEMETRY_ENABLED=false
smtp-server:
container_name: infisical-dev-smtp-server
image: lytrax/mailhog:latest # https://github.com/mailhog/MailHog/issues/353#issuecomment-821137362
restart: always
logging:
driver: "none" # disable saving logs
ports:
- 1025:1025 # SMTP server
- 8025:8025 # Web UI
volumes: volumes:
postgres-data: postgres-data:
driver: local driver: local
+8
View File
@@ -1,3 +1,11 @@
export enum ProjectMembershipRole {
Admin = "admin",
Member = "member",
Custom = "custom",
Viewer = "viewer",
NoAccess = "no-access"
}
export type TGetProjectRolesDTO = { export type TGetProjectRolesDTO = {
workspaceId?: string; workspaceId?: string;
}; };
@@ -15,7 +15,10 @@ const generalPermissionSchema = z
export const formSchema = z.object({ export const formSchema = z.object({
name: z.string().trim(), name: z.string().trim(),
description: z.string().trim().optional(), description: z.string().trim().optional(),
slug: z.string().trim(), slug: z
.string()
.trim()
.refine((val) => val !== "custom", { message: "Cannot use custom as its a keyword" }),
permissions: z permissions: z
.object({ .object({
workspace: z workspace: z
@@ -53,6 +53,7 @@ import {
useUpdateUserWorkspaceRole, useUpdateUserWorkspaceRole,
useUploadWsKey useUploadWsKey
} from "@app/hooks/api"; } from "@app/hooks/api";
import { ProjectMembershipRole } from "@app/hooks/api/roles/types";
const addMemberFormSchema = z.object({ const addMemberFormSchema = z.object({
orgMembershipId: z.string().trim() orgMembershipId: z.string().trim()
@@ -158,7 +159,6 @@ export const MemberListTab = () => {
() => members?.find(({ user: u }) => userId === u?.id)?.role === "owner", () => members?.find(({ user: u }) => userId === u?.id)?.role === "owner",
[userId, members] [userId, members]
); );
console.log(members);
const findRoleFromId = useCallback( const findRoleFromId = useCallback(
(roleId: string) => { (roleId: string) => {
@@ -171,7 +171,9 @@ export const MemberListTab = () => {
if (!currentOrg?.id) return; if (!currentOrg?.id) return;
try { try {
const isCustomRole = !["admin", "member", "viewer"].includes(role); const isCustomRole = !Object.values(ProjectMembershipRole).includes(
role as ProjectMembershipRole
);
if (isCustomRole && subscription && !subscription?.rbac) { if (isCustomRole && subscription && !subscription?.rbac) {
handlePopUpOpen("upgradePlan", { handlePopUpOpen("upgradePlan", {
@@ -28,7 +28,10 @@ const PERMISSION_ACTIONS = ["read", "create", "edit", "delete"] as const;
export const formSchema = z.object({ export const formSchema = z.object({
name: z.string().trim(), name: z.string().trim(),
description: z.string().trim().optional(), description: z.string().trim().optional(),
slug: z.string().trim(), slug: z
.string()
.trim()
.refine((val) => val !== "custom", { message: "Cannot use custom as its a keyword" }),
permissions: z permissions: z
.object({ .object({
secrets: z.record(multiEnvPermissionSchema).optional(), secrets: z.record(multiEnvPermissionSchema).optional(),