mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-08 11:27:47 +00:00
feat(infisical-pg): added secret version api, fixed pass reset flow, smtp connection issue fixed
This commit is contained in:
@@ -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 };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
+3
-10
@@ -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) => {
|
||||||
|
|||||||
@@ -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()
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
};
|
};
|
||||||
|
|||||||
+4
-1
@@ -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", {
|
||||||
|
|||||||
+4
-1
@@ -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(),
|
||||||
|
|||||||
Reference in New Issue
Block a user