Skip to content

Commit

Permalink
feat(cli): Added CLI
Browse files Browse the repository at this point in the history
  • Loading branch information
rajdip-b committed Jun 26, 2024
1 parent 3c042cf commit 9ce25f3
Show file tree
Hide file tree
Showing 30 changed files with 1,370 additions and 138 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,6 @@ Thumbs.db
.env.sentry-build-plugin

# turbo
.turbo
.turbo

keyshade.json
2 changes: 0 additions & 2 deletions apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,11 @@
"@nestjs/websockets": "^10.3.7",
"@socket.io/redis-adapter": "^8.3.0",
"@supabase/supabase-js": "^2.39.6",
"chalk": "^4.1.2",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",
"cookie-parser": "^1.4.6",
"eccrypto": "^1.1.6",
"minio": "^8.0.0",
"moment": "^2.30.1",
"nodemailer": "^6.9.9",
"passport-github2": "^0.1.12",
"passport-gitlab2": "^5.0.0",
Expand Down
14 changes: 14 additions & 0 deletions apps/api/src/api-key/controller/api-key.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,18 @@ export class ApiKeyController {
search
)
}

@Get('/access/live-updates')
@RequiredApiKeyAuthorities(
Authority.READ_SECRET,
Authority.READ_VARIABLE,
Authority.READ_ENVIRONMENT,
Authority.READ_PROJECT,
Authority.READ_WORKSPACE
)
async canAccessLiveUpdates() {
return {
canAccessLiveUpdates: true
}
}
}
23 changes: 18 additions & 5 deletions apps/api/src/api-key/service/api-key.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,18 +43,31 @@ export class ApiKeyService {
}

async updateApiKey(user: User, apiKeyId: string, dto: UpdateApiKey) {
const apiKey = await this.prisma.apiKey.findUnique({
where: {
id: apiKeyId,
userId: user.id
}
})

if (!apiKey) {
throw new NotFoundException(`API key with id ${apiKeyId} not found`)
}

const existingAuthorities = new Set(apiKey.authorities)
dto.authorities &&
dto.authorities.forEach((auth) => existingAuthorities.add(auth))

const updatedApiKey = await this.prisma.apiKey.update({
where: {
id: apiKeyId,
userId: user.id
},
data: {
name: dto.name,
authorities: dto.authorities
? {
set: dto.authorities
}
: undefined,
authorities: {
set: Array.from(existingAuthorities)
},
expiresAt: dto.expiresAfter
? addHoursToDate(dto.expiresAfter)
: undefined
Expand Down
4 changes: 2 additions & 2 deletions apps/api/src/auth/guard/auth/auth.guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export class AuthGuard implements CanActivate {
if (authType === 'API_KEY') {
const apiKeyValue = this.extractApiKeyFromHeader(request)
if (!apiKeyValue) {
throw new ForbiddenException()
throw new ForbiddenException('No API key provided')
}

const apiKey = await this.prisma.apiKey.findUnique({
Expand All @@ -88,7 +88,7 @@ export class AuthGuard implements CanActivate {
})

if (!apiKey) {
throw new ForbiddenException()
throw new ForbiddenException('Invalid API key')
}

user = apiKey.user
Expand Down
14 changes: 14 additions & 0 deletions apps/api/src/secret/controller/secret.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,4 +87,18 @@ export class SecretController {
search
)
}

@Get('/all/:projectId/:environmentId')
@RequiredApiKeyAuthorities(Authority.READ_SECRET)
async getAllSecretsOfEnvironment(
@CurrentUser() user: User,
@Param('projectId') projectId: string,
@Param('environmentId') environmentId: string
) {
return await this.secretService.getAllSecretsOfProjectAndEnvironment(
user,
projectId,
environmentId
)
}
}
86 changes: 78 additions & 8 deletions apps/api/src/secret/service/secret.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ import { RedisClientType } from 'redis'
import { REDIS_CLIENT } from '../../provider/redis.provider'
import { CHANGE_NOTIFIER_RSC } from '../../socket/change-notifier.socket'
import { AuthorityCheckerService } from '../../common/authority-checker.service'
import {
ChangeNotification,
ChangeNotificationEvent
} from 'src/socket/socket.types'

@Injectable()
export class SecretService {
Expand Down Expand Up @@ -213,6 +217,9 @@ export class SecretService {
select: {
environmentId: true,
value: true
},
orderBy: {
version: 'desc'
}
}
}
Expand All @@ -238,7 +245,6 @@ export class SecretService {
take: 1
})

// Create the new version
// Create the new version
op.push(
this.prisma.secretVersion.create({
Expand Down Expand Up @@ -268,8 +274,8 @@ export class SecretService {
environmentId: entry.environmentId,
name: updatedSecret.name,
value: entry.value,
isSecret: true
})
isPlaintext: true
} as ChangeNotificationEvent)
)
} catch (error) {
this.logger.error(`Error publishing secret update to Redis: ${error}`)
Expand Down Expand Up @@ -314,6 +320,8 @@ export class SecretService {
prisma: this.prisma
})

const project = secret.project

// Filter the secret versions by the environment
secret.versions = secret.versions.filter(
(version) => version.environmentId === environmentId
Expand Down Expand Up @@ -351,9 +359,11 @@ export class SecretService {
JSON.stringify({
environmentId,
name: secret.name,
value: secret.versions[rollbackVersion - 1].value,
isSecret: true
})
value: project.storePrivateKey
? await decrypt(project.privateKey, secret.versions[0].value)
: secret.versions[0].value,
isPlaintext: !project.storePrivateKey
} as ChangeNotificationEvent)
)
} catch (error) {
this.logger.error(`Error publishing secret update to Redis: ${error}`)
Expand Down Expand Up @@ -416,6 +426,63 @@ export class SecretService {
this.logger.log(`User ${user.id} deleted secret ${secret.id}`)
}

async getAllSecretsOfProjectAndEnvironment(
user: User,
projectId: Project['id'],
environmentId: Environment['id']
) {
// Fetch the project
const project =
await this.authorityCheckerService.checkAuthorityOverProject({
userId: user.id,
entity: { id: projectId },
authority: Authority.READ_SECRET,
prisma: this.prisma
})

const secrets = await this.prisma.secret.findMany({
where: {
projectId,
versions: {
some: {
environmentId
}
}
},
include: {
lastUpdatedBy: {
select: {
id: true,
name: true
}
},
versions: {
where: {
environmentId
},
orderBy: {
version: 'desc'
},
take: 1
}
}
})

const response: ChangeNotification[] = []

for (const secret of secrets) {
response.push({
name: secret.name,
value: project.storePrivateKey
? await decrypt(project.privateKey, secret.versions[0].value)
: secret.versions[0].value,
isPlaintext: !project.storePrivateKey
})
}

return response
}

async getAllSecretsOfProject(
user: User,
projectId: Project['id'],
Expand Down Expand Up @@ -470,6 +537,7 @@ export class SecretService {
id: Environment['id']
}
value: SecretVersion['value']
version: SecretVersion['version']
}[]
}
>()
Expand Down Expand Up @@ -513,7 +581,8 @@ export class SecretService {
},
value: decryptValue
? await decrypt(project.privateKey, latestVersion.value)
: latestVersion.value
: latestVersion.value,
version: latestVersion.version
})
} else {
secretsWithEnvironmentalValues.set(secret.id, {
Expand All @@ -526,7 +595,8 @@ export class SecretService {
},
value: decryptValue
? await decrypt(project.privateKey, latestVersion.value)
: latestVersion.value
: latestVersion.value,
version: latestVersion.version
}
]
})
Expand Down
14 changes: 11 additions & 3 deletions apps/api/src/socket/change-notifier.socket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,10 +141,18 @@ export default class ChangeNotifier
// Add the client to the environment
await this.addClientToEnvironment(client, environment.id)

const clientRegisteredResponse = {
workspaceId: workspace.id,
projectId: project.id,
environmentId: environment.id
}

// Send ACK to client
client.send({
status: 200
})
client.emit('client-registered', clientRegisteredResponse)

this.logger.log(
`Client registered: ${client.id} for configuration: ${clientRegisteredResponse}`
)
}

private async addClientToEnvironment(client: Socket, environmentId: string) {
Expand Down
8 changes: 7 additions & 1 deletion apps/api/src/socket/socket.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,16 @@ export interface ChangeNotifierRegistration {
environmentName: string
}

export interface ClientRegisteredResponse {
workspaceId: string
projectId: string
environmentId: string
}

export interface ChangeNotification {
name: string
value: string
isSecret: boolean
isPlaintext: boolean
}

export interface ChangeNotificationEvent extends ChangeNotification {
Expand Down
17 changes: 16 additions & 1 deletion apps/api/src/variable/controller/variable.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { RequiredApiKeyAuthorities } from '../../decorators/required-api-key-aut
import { Authority, User } from '@prisma/client'
import { CurrentUser } from '../../decorators/user.decorator'
import { CreateVariable } from '../dto/create.variable/create.variable'
import { UpdateVariable } from '../dto/update.variable/update.variable'

@Controller('variable')
export class VariableController {
Expand All @@ -33,7 +34,7 @@ export class VariableController {
async updateVariable(
@CurrentUser() user: User,
@Param('variableId') variableId: string,
@Body() dto: CreateVariable
@Body() dto: UpdateVariable
) {
return await this.variableService.updateVariable(user, variableId, dto)
}
Expand Down Expand Up @@ -84,4 +85,18 @@ export class VariableController {
search
)
}

@Get('/all/:projectId/:environmentId')
@RequiredApiKeyAuthorities(Authority.READ_VARIABLE)
async getAllSecretsOfEnvironment(
@CurrentUser() user: User,
@Param('projectId') projectId: string,
@Param('environmentId') environmentId: string
) {
return await this.variableService.getAllVariablesOfProjectAndEnvironment(
user,
projectId,
environmentId
)
}
}
Loading

0 comments on commit 9ce25f3

Please sign in to comment.