Skip to content

Commit

Permalink
fix(schema): Add versions field to project secrets and variables resp…
Browse files Browse the repository at this point in the history
…onse (keyshade-xyz#590)

Co-authored-by: rajdip-b <[email protected]>
  • Loading branch information
muntaxir4 and rajdip-b committed Jan 1, 2025
1 parent 2ece96a commit 87fb1b3
Show file tree
Hide file tree
Showing 7 changed files with 307 additions and 169 deletions.
111 changes: 101 additions & 10 deletions apps/api/src/secret/secret.e2e.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -619,14 +619,28 @@ describe('Secret Controller Tests', () => {
expect(response.json().items.length).toBe(1)

const { secret, values } = response.json().items[0]
expect(secret.id).toBeDefined()
expect(secret.name).toBeDefined()
expect(secret.note).toBeDefined()
expect(secret.projectId).toBeDefined()
expect(secret).toStrictEqual({
id: secret1.id,
name: secret1.name,
slug: secret1.slug,
note: secret1.note,
projectId: project1.id,
lastUpdatedById: secret1.lastUpdatedById,
lastUpdatedBy: {
id: user1.id,
name: user1.name
},
createdAt: secret1.createdAt.toISOString(),
updatedAt: secret1.updatedAt.toISOString(),
rotateAt: secret1.rotateAt.toISOString()
})
expect(values.length).toBe(1)

const value = values[0]
expect(value.environment).toBeDefined()
expect(value.version).toBe(1)
expect(value.environment.id).toBe(environment1.id)
expect(value.environment.slug).toBe(environment1.slug)
expect(value.environment.name).toBe(environment1.name)
expect(value.value).not.toEqual('Secret 1 value')

//check metadata
Expand All @@ -645,6 +659,69 @@ describe('Secret Controller Tests', () => {
)
})

it('should be able to fetch only new versions of secrets', async () => {
// Update secret1
await secretService.updateSecret(user1, secret1.slug, {
entries: [
{
value: 'Secret new 1 value',
environmentSlug: environment1.slug
}
]
})

const response = await app.inject({
method: 'GET',
url: `/secret/${project1.slug}?page=0&limit=10`,
headers: {
'x-e2e-user-email': user1.email
}
})

expect(response.statusCode).toBe(200)
expect(response.json().items.length).toBe(1)

const { secret, values } = response.json().items[0]
expect(secret).toStrictEqual({
id: secret1.id,
name: secret1.name,
slug: secret1.slug,
note: secret1.note,
projectId: project1.id,
lastUpdatedById: secret1.lastUpdatedById,
lastUpdatedBy: {
id: user1.id,
name: user1.name
},
createdAt: secret1.createdAt.toISOString(),
updatedAt: expect.any(String),
rotateAt: secret1.rotateAt.toISOString()
})
expect(values.length).toBe(1)

const value = values[0]
expect(value.version).toBe(2)
expect(value.environment.id).toBe(environment1.id)
expect(value.environment.slug).toBe(environment1.slug)
expect(value.environment.name).toBe(environment1.name)
expect(value.value).not.toEqual('Secret 1 new value')

//check metadata
const metadata = response.json().metadata
expect(metadata.totalCount).toEqual(1)
expect(metadata.links.self).toEqual(
`/secret/${project1.slug}?decryptValue=false&page=0&limit=10&sort=name&order=asc&search=`
)
expect(metadata.links.first).toEqual(
`/secret/${project1.slug}?decryptValue=false&page=0&limit=10&sort=name&order=asc&search=`
)
expect(metadata.links.previous).toBeNull()
expect(metadata.links.next).toBeNull()
expect(metadata.links.last).toEqual(
`/secret/${project1.slug}?decryptValue=false&page=0&limit=10&sort=name&order=asc&search=`
)
})

it('should be able to fetch all secrets decrypted', async () => {
const response = await app.inject({
method: 'GET',
Expand All @@ -658,14 +735,28 @@ describe('Secret Controller Tests', () => {
expect(response.json().items.length).toBe(1)

const { secret, values } = response.json().items[0]
expect(secret.id).toBeDefined()
expect(secret.name).toBeDefined()
expect(secret.note).toBeDefined()
expect(secret.projectId).toBeDefined()
expect(secret).toStrictEqual({
id: secret1.id,
name: secret1.name,
slug: secret1.slug,
note: secret1.note,
projectId: project1.id,
lastUpdatedById: secret1.lastUpdatedById,
lastUpdatedBy: {
id: user1.id,
name: user1.name
},
createdAt: secret1.createdAt.toISOString(),
updatedAt: secret1.updatedAt.toISOString(),
rotateAt: secret1.rotateAt.toISOString()
})
expect(values.length).toBe(1)

const value = values[0]
expect(value.environment).toBeDefined()
expect(value.version).toBe(1)
expect(value.environment.id).toBe(environment1.id)
expect(value.environment.slug).toBe(environment1.slug)
expect(value.environment.name).toBe(environment1.name)
expect(value.value).toEqual('Secret 1 value')

//check metadata
Expand Down
135 changes: 58 additions & 77 deletions apps/api/src/secret/service/secret.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -669,6 +669,8 @@ export class SecretService {
},
versions: {
select: {
value: true,
version: true,
environment: {
select: {
name: true,
Expand All @@ -687,93 +689,72 @@ export class SecretService {
}
})

const secretsWithEnvironmentalValues = new Map<
Secret['id'],
{
secret: Secret
values: {
environment: {
name: Environment['name']
id: Environment['id']
}
value: SecretVersion['value']
version: SecretVersion['version']
}[]
}
>()

// Find all the environments for this project
const environments = await this.prisma.environment.findMany({
where: {
projectId
}
})
const environmentIds = new Map(
environments.map((env) => [env.id, env.name])
)
const secretsWithEnvironmentalValues = new Set<{
secret: Partial<Secret>
values: {
environment: {
name: Environment['name']
id: Environment['id']
slug: Environment['slug']
}
value: SecretVersion['value']
version: SecretVersion['version']
}[]
}>()

for (const secret of secrets) {
// Make a copy of the environment IDs
const envIds = new Map(environmentIds)
let iterations = envIds.size

// Find the latest version for each environment
while (iterations--) {
const latestVersion = await this.prisma.secretVersion.findFirst({
where: {
secretId: secret.id,
environmentId: {
in: Array.from(envIds.keys())
}
},
orderBy: {
version: 'desc'
},
include: {
environment: {
select: {
id: true,
slug: true
}
}
// Logic to update the map:
// 1. If the environment ID is not present in the key, insert the environment ID and the secret version
// 2. If the environment ID is already present, check if the existing secret version is lesser than the new secret version.
// If it is, update the secret version
const envIdToSecretVersionMap = new Map<
Environment['id'],
Partial<SecretVersion> & {
environment: {
id: Environment['id']
slug: Environment['slug']
name: Environment['name']
}
})
}
>()

if (!latestVersion) continue
for (const secretVersion of secret.versions) {
const environmentId = secretVersion.environment.id
const existingSecretVersion = envIdToSecretVersionMap.get(environmentId)

if (secretsWithEnvironmentalValues.has(secret.id)) {
secretsWithEnvironmentalValues.get(secret.id).values.push({
environment: {
id: latestVersion.environmentId,
name: envIds.get(latestVersion.environmentId)
},
value: decryptValue
? await decrypt(project.privateKey, latestVersion.value)
: latestVersion.value,
version: latestVersion.version
})
if (!existingSecretVersion) {
envIdToSecretVersionMap.set(environmentId, secretVersion)
} else {
secretsWithEnvironmentalValues.set(secret.id, {
secret,
values: [
{
environment: {
id: latestVersion.environmentId,
name: envIds.get(latestVersion.environmentId)
},
value: decryptValue
? await decrypt(project.privateKey, latestVersion.value)
: latestVersion.value,
version: latestVersion.version
}
]
})
if (existingSecretVersion.version < secretVersion.version) {
envIdToSecretVersionMap.set(environmentId, secretVersion)
}
}

envIds.delete(latestVersion.environmentId)
}

delete secret.versions

// Add the secret to the map
secretsWithEnvironmentalValues.add({
secret,
values: await Promise.all(
Array.from(envIdToSecretVersionMap.values()).map(
async (secretVersion) => ({
environment: {
id: secretVersion.environment.id,
name: secretVersion.environment.name,
slug: secretVersion.environment.slug
},
value: decryptValue
? await decrypt(project.privateKey, secretVersion.value)
: secretVersion.value,
version: secretVersion.version
})
)
)
})
}

// console.log(secretsWithEnvironmentalValues)
const items = Array.from(secretsWithEnvironmentalValues.values())

// Calculate pagination metadata
Expand Down
Loading

0 comments on commit 87fb1b3

Please sign in to comment.