Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,7 @@ RUN cd shared-code && ../node_modules/.bin/tsc
RUN cd backend && yarn run nest build
COPY --from=front_builder /app/frontend/dist/dissendium-v0 /var/www/html
COPY frontend/nginx/default.conf /etc/nginx/sites-enabled/default

RUN chown -R appuser:appuser /app
RUN mkdir -p /app/backend/node_modules/.cache && chown -R appuser:appuser /app/backend/node_modules/.cache
RUN chown -R appuser:appuser /var/lib/nginx
RUN chown -R appuser:appuser /var/log/nginx
RUN chown -R appuser:appuser /run
Expand Down
1,809 changes: 1,809 additions & 0 deletions SECRET_STORAGE_SPECIFICATION.md

Large diffs are not rendered by default.

33 changes: 0 additions & 33 deletions backend/Dockerfile

This file was deleted.

2 changes: 2 additions & 0 deletions backend/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import { GetHelloUseCase } from './use-cases-app/get-hello.use.case.js';
import { ThrottlerModule, ThrottlerGuard } from '@nestjs/throttler';
import { SharedJobsModule } from './entities/shared-jobs/shared-jobs.module.js';
import { TableCategoriesModule } from './entities/table-categories/table-categories.module.js';
import { UserSecretModule } from './entities/user-secret/user-secret.module.js';

@Module({
imports: [
Expand Down Expand Up @@ -81,6 +82,7 @@ import { TableCategoriesModule } from './entities/table-categories/table-categor
LoggingModule,
SharedJobsModule,
TableCategoriesModule,
UserSecretModule,
],
controllers: [AppController],
providers: [
Expand Down
23 changes: 23 additions & 0 deletions backend/src/decorators/company-id.decorator.ts
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
Copy link
Collaborator

Choose a reason for hiding this comment

The 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;
});
55 changes: 55 additions & 0 deletions backend/src/entities/secret-access-log/secret-access-log.entity.ts
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()
Copy link
Collaborator

Choose a reason for hiding this comment

The 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>;
Copy link
Collaborator

Choose a reason for hiding this comment

The 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;
}
44 changes: 44 additions & 0 deletions backend/src/entities/user-secret/application/dto/audit-log.dto.ts
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;
};
Copy link
Collaborator

Choose a reason for hiding this comment

The 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[];
Copy link
Collaborator

Choose a reason for hiding this comment

The 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;
};
Copy link
Collaborator

Choose a reason for hiding this comment

The 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] })
Copy link
Collaborator

Choose a reason for hiding this comment

The 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;
};
Copy link
Collaborator

Choose a reason for hiding this comment

The 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
Copy link

Copilot AI Nov 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The value field is marked as required: true in the @ApiProperty decorator, but for an update operation, the value should be optional to allow partial updates (e.g., only updating expiresAt). The validators @IsNotEmpty(), @MinLength(1) will still enforce that if value is provided, it must be valid. Consider changing to @ApiProperty({ required: false, type: 'string' }) and adding @IsOptional() as the first validator.

Suggested change
@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;

Copilot uses AI. Check for mistakes.

@ApiProperty({ required: false, type: 'string' })
@IsOptional()
@IsISO8601()
expiresAt?: string;
}
Loading
Loading