mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-21 09:28:14 +00:00
feat(infisical-pg): completed secret rotation with queue service
This commit is contained in:
@@ -4,7 +4,18 @@ import { z } from "zod";
|
||||
import { registerV1EERoutes } from "@app/ee/routes/v1";
|
||||
import { permissionDalFactory } from "@app/ee/services/permission/permission-dal";
|
||||
import { permissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||
import { sapApproverDalFactory } from "@app/ee/services/secret-approval-policy/sap-approver-dal";
|
||||
import { secretApprovalPolicyDalFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-dal";
|
||||
import { secretApprovalPolicyServiceFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-service";
|
||||
import { sarReviewerDalFactory } from "@app/ee/services/secret-approval-request/sar-reviewer-dal";
|
||||
import { sarSecretDalFactory } from "@app/ee/services/secret-approval-request/sar-secret-dal";
|
||||
import { secretApprovalRequestDalFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-dal";
|
||||
import { secretApprovalRequestServiceFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-service";
|
||||
import { secretRotationDalFactory } from "@app/ee/services/secret-rotation/secret-rotation-dal";
|
||||
import { secretRotationQueueFactory } from "@app/ee/services/secret-rotation/secret-rotation-queue";
|
||||
import { secretRotationServiceFactory } from "@app/ee/services/secret-rotation/secret-rotation-service";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { TQueueServiceFactory } from "@app/queue";
|
||||
import { apiKeyDalFactory } from "@app/services/api-key/api-key-dal";
|
||||
import { apiKeyServiceFactory } from "@app/services/api-key/api-key-service";
|
||||
import { authDalFactory } from "@app/services/auth/auth-dal";
|
||||
@@ -69,13 +80,14 @@ import { injectPermission } from "../plugins/auth/inject-permission";
|
||||
import { registerV1Routes } from "./v1";
|
||||
import { registerV2Routes } from "./v2";
|
||||
import { registerV3Routes } from "./v3";
|
||||
import { secretApprovalPolicyDalFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-dal";
|
||||
import { sapApproverDalFactory } from "@app/ee/services/secret-approval-policy/sap-approver-dal";
|
||||
import { secretApprovalPolicyServiceFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-service";
|
||||
|
||||
export const registerRoutes = async (
|
||||
server: FastifyZodProvider,
|
||||
{ db, smtp: smtpService }: { db: Knex; smtp: TSmtpService }
|
||||
{
|
||||
db,
|
||||
smtp: smtpService,
|
||||
queue: queueService
|
||||
}: { db: Knex; smtp: TSmtpService; queue: TQueueServiceFactory }
|
||||
) => {
|
||||
// db layers
|
||||
const userDal = userDalFactory(db);
|
||||
@@ -118,8 +130,12 @@ export const registerRoutes = async (
|
||||
const permissionDal = permissionDalFactory(db);
|
||||
const sapApproverDal = sapApproverDalFactory(db);
|
||||
const secretApprovalPolicyDal = secretApprovalPolicyDalFactory(db);
|
||||
const secretApprovalRequestDal = secretApprovalRequestDalFactory(db);
|
||||
const sarReviewerDal = sarReviewerDalFactory(db);
|
||||
const sarSecretDal = sarSecretDalFactory(db);
|
||||
|
||||
const secretRotationDal = secretRotationDalFactory(db);
|
||||
|
||||
// ee services
|
||||
const permissionService = permissionServiceFactory({ permissionDal, orgRoleDal, projectRoleDal });
|
||||
const sapService = secretApprovalPolicyServiceFactory({
|
||||
projectMembershipDal,
|
||||
@@ -128,8 +144,17 @@ export const registerRoutes = async (
|
||||
permissionService,
|
||||
secretApprovalPolicyDal
|
||||
});
|
||||
const sarService = secretApprovalRequestServiceFactory({
|
||||
permissionService,
|
||||
folderDal,
|
||||
secretDal,
|
||||
sarSecretDal,
|
||||
sarReviewerDal,
|
||||
secretVersionDal,
|
||||
secretBlindIndexDal,
|
||||
secretApprovalRequestDal
|
||||
});
|
||||
|
||||
// service layers
|
||||
const tokenService = tokenServiceFactory({ tokenDal: authTokenDal });
|
||||
const userService = userServiceFactory({ userDal });
|
||||
const loginService = authLoginServiceFactory({ userDal, smtpService, tokenService });
|
||||
@@ -211,6 +236,21 @@ export const registerRoutes = async (
|
||||
secretImportDal
|
||||
});
|
||||
const projectBotService = projectBotServiceFactory({ permissionService, projectBotDal });
|
||||
|
||||
const secretRotationQueue = secretRotationQueueFactory({
|
||||
secretRotationDal,
|
||||
queue: queueService,
|
||||
secretDal,
|
||||
secretVersionDal,
|
||||
projectBotService
|
||||
});
|
||||
const secretRotationService = secretRotationServiceFactory({
|
||||
permissionService,
|
||||
projectEnvDal,
|
||||
secretRotationDal,
|
||||
secretRotationQueue
|
||||
});
|
||||
|
||||
const integrationService = integrationServiceFactory({
|
||||
permissionService,
|
||||
folderDal,
|
||||
@@ -287,7 +327,9 @@ export const registerRoutes = async (
|
||||
identityAccessToken: identityAccessTokenService,
|
||||
identityProject: identityProjectService,
|
||||
identityUa: identityUaService,
|
||||
secretApprovalPolicy: sapService
|
||||
secretApprovalPolicy: sapService,
|
||||
secretApprovalRequest: sarService,
|
||||
secretRotation: secretRotationService
|
||||
});
|
||||
|
||||
server.decorate<FastifyZodProvider["store"]>("store", {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { SecretsSchema,SecretType } from "@app/db/schemas";
|
||||
import { SecretApprovalRequestsSchema, SecretsSchema, SecretType } from "@app/db/schemas";
|
||||
import { CommitType } from "@app/ee/services/secret-approval-request/secret-approval-request-types";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
import { AuthMode } from "@app/services/auth/auth-type";
|
||||
import { ActorType, AuthMode } from "@app/services/auth/auth-type";
|
||||
|
||||
export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||
server.route({
|
||||
@@ -102,32 +103,92 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||
secretName: z.string().trim()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
secret: SecretsSchema.omit({ secretBlindIndex: true })
|
||||
})
|
||||
200: z.union([
|
||||
z.object({
|
||||
secret: SecretsSchema.omit({ secretBlindIndex: true })
|
||||
}),
|
||||
z
|
||||
.object({ approval: SecretApprovalRequestsSchema })
|
||||
.describe("When secret protection policy is enabled")
|
||||
])
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY]),
|
||||
handler: async (req) => {
|
||||
const {
|
||||
workspaceId: projectId,
|
||||
secretPath,
|
||||
environment,
|
||||
metadata,
|
||||
type,
|
||||
secretKeyIV,
|
||||
secretKeyTag,
|
||||
secretValueIV,
|
||||
secretValueTag,
|
||||
secretCommentIV,
|
||||
secretCommentTag,
|
||||
secretKeyCiphertext,
|
||||
secretValueCiphertext,
|
||||
secretCommentCiphertext,
|
||||
skipMultilineEncoding
|
||||
} = req.body;
|
||||
if (req.body.type !== SecretType.Personal && req.permission.type === ActorType.USER) {
|
||||
const policy = await server.services.secretApprovalPolicy.getSapOfFolder({
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
secretPath,
|
||||
environment,
|
||||
projectId
|
||||
});
|
||||
if (policy) {
|
||||
const approval =
|
||||
await server.services.secretApprovalRequest.generateSecretApprovalRequest({
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
secretPath,
|
||||
environment,
|
||||
projectId,
|
||||
policy,
|
||||
data: {
|
||||
[CommitType.Create]: [
|
||||
{
|
||||
secretName: req.params.secretName,
|
||||
secretValueCiphertext,
|
||||
secretValueIV,
|
||||
secretValueTag,
|
||||
secretCommentIV,
|
||||
secretCommentTag,
|
||||
secretCommentCiphertext,
|
||||
skipMultilineEncoding,
|
||||
secretKeyTag,
|
||||
secretKeyCiphertext,
|
||||
secretKeyIV
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
return { approval };
|
||||
}
|
||||
}
|
||||
const secret = await server.services.secret.createSecret({
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
path: req.body.secretPath,
|
||||
type: req.body.type,
|
||||
path: secretPath,
|
||||
type,
|
||||
environment: req.body.environment,
|
||||
secretName: req.params.secretName,
|
||||
projectId: req.body.workspaceId,
|
||||
secretKeyIV: req.body.secretKeyIV,
|
||||
secretKeyTag: req.body.secretKeyTag,
|
||||
secretKeyCiphertext: req.body.secretKeyCiphertext,
|
||||
secretValueIV: req.body.secretValueIV,
|
||||
secretValueTag: req.body.secretValueTag,
|
||||
secretValueCiphertext: req.body.secretValueCiphertext,
|
||||
secretCommentIV: req.body.secretCommentIV,
|
||||
secretCommentTag: req.body.secretCommentTag,
|
||||
secretCommentCiphertext: req.body.secretCommentCiphertext,
|
||||
skipMultilineEncoding: req.body.skipMultilineEncoding,
|
||||
metadata: req.body.metadata
|
||||
projectId,
|
||||
secretKeyIV,
|
||||
secretKeyTag,
|
||||
secretKeyCiphertext,
|
||||
secretValueIV,
|
||||
secretValueTag,
|
||||
secretValueCiphertext,
|
||||
secretCommentIV,
|
||||
secretCommentTag,
|
||||
secretCommentCiphertext,
|
||||
skipMultilineEncoding,
|
||||
metadata
|
||||
});
|
||||
|
||||
return { secret };
|
||||
@@ -165,35 +226,103 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||
metadata: z.record(z.string()).optional()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
secret: SecretsSchema.omit({ secretBlindIndex: true })
|
||||
})
|
||||
200: z.union([
|
||||
z.object({
|
||||
secret: SecretsSchema.omit({ secretBlindIndex: true })
|
||||
}),
|
||||
z
|
||||
.object({ approval: SecretApprovalRequestsSchema })
|
||||
.describe("When secret protection policy is enabled")
|
||||
])
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY]),
|
||||
handler: async (req) => {
|
||||
const {
|
||||
secretValueCiphertext,
|
||||
secretValueTag,
|
||||
secretValueIV,
|
||||
type,
|
||||
environment,
|
||||
secretPath,
|
||||
workspaceId: projectId,
|
||||
tags,
|
||||
secretCommentIV,
|
||||
secretCommentTag,
|
||||
secretCommentCiphertext,
|
||||
secretName: newSecretName,
|
||||
secretKeyIV,
|
||||
secretKeyTag,
|
||||
secretKeyCiphertext,
|
||||
skipMultilineEncoding,
|
||||
secretReminderRepeatDays,
|
||||
secretReminderNote,
|
||||
metadata
|
||||
} = req.body;
|
||||
|
||||
if (req.body.type !== SecretType.Personal && req.permission.type === ActorType.USER) {
|
||||
const policy = await server.services.secretApprovalPolicy.getSapOfFolder({
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
secretPath,
|
||||
environment,
|
||||
projectId
|
||||
});
|
||||
if (policy) {
|
||||
const approval =
|
||||
await server.services.secretApprovalRequest.generateSecretApprovalRequest({
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
secretPath,
|
||||
environment,
|
||||
projectId,
|
||||
policy,
|
||||
data: {
|
||||
[CommitType.Update]: [
|
||||
{
|
||||
secretName: req.params.secretName,
|
||||
newSecretName,
|
||||
secretValueCiphertext,
|
||||
secretValueIV,
|
||||
secretValueTag,
|
||||
secretCommentIV,
|
||||
secretCommentTag,
|
||||
secretCommentCiphertext,
|
||||
skipMultilineEncoding,
|
||||
secretKeyTag,
|
||||
secretKeyCiphertext,
|
||||
secretKeyIV
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
return { approval };
|
||||
}
|
||||
}
|
||||
|
||||
const secret = await server.services.secret.updateSecret({
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
path: req.body.secretPath,
|
||||
type: req.body.type,
|
||||
environment: req.body.environment,
|
||||
path: secretPath,
|
||||
type,
|
||||
environment,
|
||||
secretName: req.params.secretName,
|
||||
projectId: req.body.workspaceId,
|
||||
secretKeyIV: req.body.secretKeyIV,
|
||||
secretKeyTag: req.body.secretKeyTag,
|
||||
secretKeyCiphertext: req.body.secretKeyCiphertext,
|
||||
secretValueIV: req.body.secretValueIV,
|
||||
secretValueTag: req.body.secretValueTag,
|
||||
secretValueCiphertext: req.body.secretValueCiphertext,
|
||||
secretCommentIV: req.body.secretCommentIV,
|
||||
secretCommentTag: req.body.secretCommentTag,
|
||||
secretCommentCiphertext: req.body.secretCommentCiphertext,
|
||||
skipMultilineEncoding: req.body.skipMultilineEncoding,
|
||||
metadata: req.body.metadata,
|
||||
secretReminderRepeatDays: req.body.secretReminderRepeatDays,
|
||||
secretReminderNote: req.body.secretReminderNote,
|
||||
newSecretName: req.body.secretName
|
||||
projectId,
|
||||
secretKeyIV,
|
||||
secretKeyTag,
|
||||
secretKeyCiphertext,
|
||||
secretValueIV,
|
||||
tags,
|
||||
secretValueTag,
|
||||
secretValueCiphertext,
|
||||
secretCommentIV,
|
||||
secretCommentTag,
|
||||
secretCommentCiphertext,
|
||||
skipMultilineEncoding,
|
||||
metadata,
|
||||
secretReminderRepeatDays,
|
||||
secretReminderNote,
|
||||
newSecretName
|
||||
});
|
||||
|
||||
return { secret };
|
||||
@@ -215,22 +344,57 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||
environment: z.string().trim()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
secret: SecretsSchema.omit({ secretBlindIndex: true })
|
||||
})
|
||||
200: z.union([
|
||||
z.object({
|
||||
secret: SecretsSchema.omit({ secretBlindIndex: true })
|
||||
}),
|
||||
z
|
||||
.object({ approval: SecretApprovalRequestsSchema })
|
||||
.describe("When secret protection policy is enabled")
|
||||
])
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY]),
|
||||
handler: async (req) => {
|
||||
const { secretPath, type, workspaceId: projectId, secretId, environment } = req.body;
|
||||
if (req.body.type !== SecretType.Personal && req.permission.type === ActorType.USER) {
|
||||
const policy = await server.services.secretApprovalPolicy.getSapOfFolder({
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
secretPath,
|
||||
environment,
|
||||
projectId
|
||||
});
|
||||
if (policy) {
|
||||
const approval =
|
||||
await server.services.secretApprovalRequest.generateSecretApprovalRequest({
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
secretPath,
|
||||
environment,
|
||||
projectId,
|
||||
policy,
|
||||
data: {
|
||||
[CommitType.Delete]: [
|
||||
{
|
||||
secretName: req.params.secretName
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
return { approval };
|
||||
}
|
||||
}
|
||||
|
||||
const secret = await server.services.secret.deleteSecret({
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
path: req.body.secretPath,
|
||||
type: req.body.type,
|
||||
environment: req.body.environment,
|
||||
path: secretPath,
|
||||
type,
|
||||
environment,
|
||||
secretName: req.params.secretName,
|
||||
projectId: req.body.workspaceId,
|
||||
secretId: req.body.secretId
|
||||
projectId,
|
||||
secretId
|
||||
});
|
||||
|
||||
return { secret };
|
||||
@@ -265,20 +429,51 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||
.min(1)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
secrets: SecretsSchema.omit({ secretBlindIndex: true }).array()
|
||||
})
|
||||
200: z.union([
|
||||
z.object({
|
||||
secrets: SecretsSchema.omit({ secretBlindIndex: true }).array()
|
||||
}),
|
||||
z
|
||||
.object({ approval: SecretApprovalRequestsSchema })
|
||||
.describe("When secret protection policy is enabled")
|
||||
])
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY]),
|
||||
handler: async (req) => {
|
||||
const { environment, workspaceId: projectId, secretPath, secrets: inputSecrets } = req.body;
|
||||
if (req.permission.type === ActorType.USER) {
|
||||
const policy = await server.services.secretApprovalPolicy.getSapOfFolder({
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
secretPath,
|
||||
environment,
|
||||
projectId
|
||||
});
|
||||
if (policy) {
|
||||
const approval =
|
||||
await server.services.secretApprovalRequest.generateSecretApprovalRequest({
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
secretPath,
|
||||
environment,
|
||||
projectId,
|
||||
policy,
|
||||
data: {
|
||||
[CommitType.Create]: inputSecrets.filter(({ type }) => type === "shared")
|
||||
}
|
||||
});
|
||||
return { approval };
|
||||
}
|
||||
}
|
||||
|
||||
const secrets = await server.services.secret.createManySecret({
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
path: req.body.secretPath,
|
||||
environment: req.body.environment,
|
||||
projectId: req.body.workspaceId,
|
||||
secrets: req.body.secrets
|
||||
path: secretPath,
|
||||
environment,
|
||||
projectId,
|
||||
secrets: inputSecrets
|
||||
});
|
||||
|
||||
return { secrets };
|
||||
@@ -313,20 +508,50 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||
.min(1)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
secrets: SecretsSchema.omit({ secretBlindIndex: true }).array()
|
||||
})
|
||||
200: z.union([
|
||||
z.object({
|
||||
secrets: SecretsSchema.omit({ secretBlindIndex: true }).array()
|
||||
}),
|
||||
z
|
||||
.object({ approval: SecretApprovalRequestsSchema })
|
||||
.describe("When secret protection policy is enabled")
|
||||
])
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY]),
|
||||
handler: async (req) => {
|
||||
const { environment, workspaceId: projectId, secretPath, secrets: inputSecrets } = req.body;
|
||||
if (req.permission.type === ActorType.USER) {
|
||||
const policy = await server.services.secretApprovalPolicy.getSapOfFolder({
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
secretPath,
|
||||
environment,
|
||||
projectId
|
||||
});
|
||||
if (policy) {
|
||||
const approval =
|
||||
await server.services.secretApprovalRequest.generateSecretApprovalRequest({
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
secretPath,
|
||||
environment,
|
||||
projectId,
|
||||
policy,
|
||||
data: {
|
||||
[CommitType.Update]: inputSecrets.filter(({ type }) => type === "shared")
|
||||
}
|
||||
});
|
||||
return { approval };
|
||||
}
|
||||
}
|
||||
const secrets = await server.services.secret.updateManySecret({
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
path: req.body.secretPath,
|
||||
environment: req.body.environment,
|
||||
projectId: req.body.workspaceId,
|
||||
secrets: req.body.secrets
|
||||
path: secretPath,
|
||||
environment,
|
||||
projectId,
|
||||
secrets: inputSecrets
|
||||
});
|
||||
|
||||
return { secrets };
|
||||
@@ -350,20 +575,50 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||
.min(1)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
secrets: SecretsSchema.omit({ secretBlindIndex: true }).array()
|
||||
})
|
||||
200: z.union([
|
||||
z.object({
|
||||
secrets: SecretsSchema.omit({ secretBlindIndex: true }).array()
|
||||
}),
|
||||
z
|
||||
.object({ approval: SecretApprovalRequestsSchema })
|
||||
.describe("When secret protection policy is enabled")
|
||||
])
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY]),
|
||||
handler: async (req) => {
|
||||
const { environment, workspaceId: projectId, secretPath, secrets: inputSecrets } = req.body;
|
||||
if (req.permission.type === ActorType.USER) {
|
||||
const policy = await server.services.secretApprovalPolicy.getSapOfFolder({
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
secretPath,
|
||||
environment,
|
||||
projectId
|
||||
});
|
||||
if (policy) {
|
||||
const approval =
|
||||
await server.services.secretApprovalRequest.generateSecretApprovalRequest({
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
secretPath,
|
||||
environment,
|
||||
projectId,
|
||||
policy,
|
||||
data: {
|
||||
[CommitType.Delete]: inputSecrets.filter(({ type }) => type === "shared")
|
||||
}
|
||||
});
|
||||
return { approval };
|
||||
}
|
||||
}
|
||||
const secrets = await server.services.secret.deleteManySecret({
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
path: req.body.secretPath,
|
||||
environment: req.body.environment,
|
||||
projectId: req.body.workspaceId,
|
||||
secrets: req.body.secrets
|
||||
environment,
|
||||
projectId,
|
||||
secrets: inputSecrets
|
||||
});
|
||||
|
||||
return { secrets };
|
||||
|
||||
Reference in New Issue
Block a user