-
-
Notifications
You must be signed in to change notification settings - Fork 18
secret storage #1447
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
secret storage #1447
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| import { BadRequestException, createParamDecorator, ExecutionContext } from '@nestjs/common'; | ||
| import { IRequestWithCognitoInfo } from '../authorization/index.js'; | ||
| import { Messages } from '../exceptions/text/messages.js'; | ||
|
|
||
| export const CompanyId = createParamDecorator(async (data: any, ctx: ExecutionContext): Promise<string> => { | ||
| const request: IRequestWithCognitoInfo = ctx.switchToHttp().getRequest(); | ||
| const userId = request.decoded?.sub; | ||
|
|
||
| if (!userId) { | ||
| throw new BadRequestException(Messages.USER_ID_MISSING); | ||
| } | ||
|
|
||
| // Company ID should be retrieved from the user entity via a repository lookup | ||
| // This is a simplified version - in practice, you'd inject a UserRepository | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. UserRepository can not be injected in the decorator |
||
| // For now, we'll assume the company ID is added to the request by middleware | ||
| const companyId = (request as any).companyId; | ||
|
|
||
| if (!companyId) { | ||
| throw new BadRequestException('Company ID not found for user'); | ||
| } | ||
|
|
||
| return companyId; | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| import { Column, CreateDateColumn, Entity, Index, JoinColumn, ManyToOne, PrimaryGeneratedColumn, Relation } from 'typeorm'; | ||
| import { UserEntity } from '../user/user.entity.js'; | ||
| import { UserSecretEntity } from '../user-secret/user-secret.entity.js'; | ||
|
|
||
| export enum SecretActionEnum { | ||
| CREATE = 'create', | ||
| VIEW = 'view', | ||
| COPY = 'copy', | ||
| UPDATE = 'update', | ||
| DELETE = 'delete', | ||
| } | ||
|
|
||
| @Entity('secret_access_logs') | ||
| export class SecretAccessLogEntity { | ||
| @PrimaryGeneratedColumn('uuid') | ||
| id: string; | ||
|
|
||
| @ManyToOne(() => UserSecretEntity, (secret) => secret.accessLogs, { onDelete: 'CASCADE' }) | ||
| @JoinColumn() | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's better to explicitly set the foreign key name and the separate field with this name to the entity |
||
| secret: Relation<UserSecretEntity>; | ||
|
|
||
| @Column() | ||
| @Index() | ||
| secretId: string; | ||
|
|
||
| @ManyToOne(() => UserEntity, { onDelete: 'CASCADE' }) | ||
| @JoinColumn() | ||
| user: Relation<UserEntity>; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's better to explicitly set the foreign key name and the separate field with this name to the entity |
||
|
|
||
| @Column() | ||
| @Index() | ||
| userId: string; | ||
|
|
||
| @Column({ | ||
| type: 'enum', | ||
| enum: SecretActionEnum, | ||
| }) | ||
| action: SecretActionEnum; | ||
|
|
||
| @CreateDateColumn() | ||
| @Index() | ||
| accessedAt: Date; | ||
|
|
||
| @Column({ type: 'varchar', length: 45, nullable: true }) | ||
| ipAddress: string; | ||
|
|
||
| @Column({ type: 'text', nullable: true }) | ||
| userAgent: string; | ||
|
|
||
| @Column({ default: true }) | ||
| success: boolean; | ||
|
|
||
| @Column({ type: 'text', nullable: true }) | ||
| errorMessage: string; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| import { ApiProperty } from '@nestjs/swagger'; | ||
| import { SecretActionEnum } from '../../../secret-access-log/secret-access-log.entity.js'; | ||
|
|
||
| export class AuditLogEntryDto { | ||
| @ApiProperty() | ||
| id: string; | ||
|
|
||
| @ApiProperty({ enum: SecretActionEnum }) | ||
| action: SecretActionEnum; | ||
|
|
||
| @ApiProperty() | ||
| user: { | ||
| id: string; | ||
| email: string; | ||
| }; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure that the user object will be correctly displayed in Swagger this way; it's better to create a separate object describing it with Swagger decorators |
||
|
|
||
| @ApiProperty() | ||
| accessedAt: Date; | ||
|
|
||
| @ApiProperty({ required: false }) | ||
| ipAddress?: string; | ||
|
|
||
| @ApiProperty({ required: false }) | ||
| userAgent?: string; | ||
|
|
||
| @ApiProperty() | ||
| success: boolean; | ||
|
|
||
| @ApiProperty({ required: false }) | ||
| errorMessage?: string; | ||
| } | ||
|
|
||
| export class AuditLogResponseDto { | ||
| @ApiProperty({ type: [AuditLogEntryDto] }) | ||
| data: AuditLogEntryDto[]; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ({ type: AuditLogEntryDto, isArray: true }) |
||
|
|
||
| @ApiProperty() | ||
| pagination: { | ||
| total: number; | ||
| page: number; | ||
| limit: number; | ||
| totalPages: number; | ||
| }; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A separate class with Swagger decorators is needed to describe the pagination object. It most likely already exists somewhere in the project. |
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| import { ApiProperty } from '@nestjs/swagger'; | ||
| import { Transform } from 'class-transformer'; | ||
| import { IsBoolean, IsISO8601, IsNotEmpty, IsOptional, IsString, Matches, MaxLength, MinLength, ValidateIf } from 'class-validator'; | ||
|
|
||
| export class CreateSecretDto { | ||
| @ApiProperty({ required: true, type: 'string' }) | ||
| @IsString() | ||
| @IsNotEmpty() | ||
| @MinLength(1) | ||
| @MaxLength(255) | ||
| @Matches(/^[a-zA-Z0-9_-]+$/, { | ||
| message: 'slug must contain only letters, numbers, hyphens, and underscores', | ||
| }) | ||
| @Transform(({ value }) => value.trim()) | ||
| slug: string; | ||
|
|
||
| @ApiProperty({ required: true, type: 'string' }) | ||
| @IsString() | ||
| @IsNotEmpty() | ||
| @MinLength(1) | ||
| @MaxLength(10000) | ||
| value: string; | ||
|
|
||
| @ApiProperty({ required: false, type: 'string' }) | ||
| @IsOptional() | ||
| @IsISO8601() | ||
| expiresAt?: string; | ||
|
|
||
| @ApiProperty({ required: false, type: 'boolean' }) | ||
| @IsBoolean() | ||
| @IsOptional() | ||
| masterEncryption?: boolean; | ||
|
|
||
| @ApiProperty({ required: false, type: 'string' }) | ||
| @IsString() | ||
| @IsOptional() | ||
| @MinLength(8) | ||
| @ValidateIf((o) => o.masterEncryption === true) | ||
| masterPassword?: string; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| import { ApiProperty } from '@nestjs/swagger'; | ||
|
|
||
| export class FoundSecretDto { | ||
| @ApiProperty() | ||
| id: string; | ||
|
|
||
| @ApiProperty() | ||
| slug: string; | ||
|
|
||
| @ApiProperty({ required: false }) | ||
| value?: string; | ||
|
|
||
| @ApiProperty() | ||
| companyId: string; | ||
|
|
||
| @ApiProperty() | ||
| createdAt: Date; | ||
|
|
||
| @ApiProperty() | ||
| updatedAt: Date; | ||
|
|
||
| @ApiProperty({ required: false }) | ||
| lastAccessedAt?: Date; | ||
|
|
||
| @ApiProperty({ required: false }) | ||
| expiresAt?: Date; | ||
|
|
||
| @ApiProperty() | ||
| masterEncryption: boolean; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| import { ApiProperty } from '@nestjs/swagger'; | ||
|
|
||
| export class SecretListItemDto { | ||
| @ApiProperty() | ||
| id: string; | ||
|
|
||
| @ApiProperty() | ||
| slug: string; | ||
|
|
||
| @ApiProperty() | ||
| companyId: string; | ||
|
|
||
| @ApiProperty() | ||
| createdAt: Date; | ||
|
|
||
| @ApiProperty() | ||
| updatedAt: Date; | ||
|
|
||
| @ApiProperty({ required: false }) | ||
| lastAccessedAt?: Date; | ||
|
|
||
| @ApiProperty({ required: false }) | ||
| expiresAt?: Date; | ||
|
|
||
| @ApiProperty() | ||
| masterEncryption: boolean; | ||
| } | ||
|
|
||
| export class SecretListResponseDto { | ||
| @ApiProperty({ type: [SecretListItemDto] }) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ({ type: SecretListItemDto, isArray: true }) |
||
| data: SecretListItemDto[]; | ||
|
|
||
| @ApiProperty() | ||
| pagination: { | ||
| total: number; | ||
| page: number; | ||
| limit: number; | ||
| totalPages: number; | ||
| }; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A separate class with Swagger decorators is needed to describe the pagination object. |
||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,16 @@ | ||||||||||||||||||||||||||||
| import { ApiProperty } from '@nestjs/swagger'; | ||||||||||||||||||||||||||||
| import { IsISO8601, IsNotEmpty, IsOptional, IsString, MaxLength, MinLength } from 'class-validator'; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| export class UpdateSecretDto { | ||||||||||||||||||||||||||||
| @ApiProperty({ required: true, type: 'string' }) | ||||||||||||||||||||||||||||
| @IsString() | ||||||||||||||||||||||||||||
| @IsNotEmpty() | ||||||||||||||||||||||||||||
| @MinLength(1) | ||||||||||||||||||||||||||||
| @MaxLength(10000) | ||||||||||||||||||||||||||||
| value: string; | ||||||||||||||||||||||||||||
|
Comment on lines
+5
to
+10
|
||||||||||||||||||||||||||||
| @ApiProperty({ required: true, type: 'string' }) | |
| @IsString() | |
| @IsNotEmpty() | |
| @MinLength(1) | |
| @MaxLength(10000) | |
| value: string; | |
| @ApiProperty({ required: false, type: 'string' }) | |
| @IsOptional() | |
| @IsString() | |
| @IsNotEmpty() | |
| @MinLength(1) | |
| @MaxLength(10000) | |
| value?: string; |
Uh oh!
There was an error while loading. Please reload this page.