Skip to content

Commit 2312fe0

Browse files
committed
feat: implement agent payload sanitization and security updates
- Added a method to sanitize agent payloads, removing sensitive information such as passwords and old passwords from the response. - Updated the SecurityPartDTO to make the oldPasswords field optional, enhancing flexibility in data handling. - Modified the security schema to include a setter for allowedNetworks, ensuring proper formatting and filtering of network entries. - Enhanced the Vue components to handle the new security structure, ensuring that sensitive data is not exposed in the UI.
1 parent 1e4791a commit 2312fe0

5 files changed

Lines changed: 51 additions & 8 deletions

File tree

apps/api/src/core/agents/_dto/parts/security.part.dto.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,7 @@ export class SecurityPartDTO {
4141
* @readonly En pratique, ne devrait pas être modifiée manuellement
4242
* @optional
4343
*/
44-
@IsArray()
45-
@IsString({ each: true })
44+
@IsOptional()
4645
@ApiProperty({ type: [String] })
4746
public oldPasswords?: string[]
4847

apps/api/src/core/agents/_schemas/_parts/security.part.schema.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,11 @@ export class SecurityPart extends Document {
8888
*/
8989
@Prop({
9090
type: [String],
91+
set: (value: string[] | string | null | undefined): string[] | undefined => {
92+
if (value === null || value === undefined) return undefined
93+
const values = Array.isArray(value) ? value : [value]
94+
return values.map((item) => `${item || ''}`.trim()).filter((item) => item.length > 0)
95+
},
9196
})
9297
public allowedNetworks?: string[]
9398

apps/api/src/core/agents/agents.controller.ts

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,28 @@ import { UseRoles } from '~/_common/decorators/use-roles.decorator'
3434
@ApiTags('core/agents')
3535
@Controller('agents')
3636
export class AgentsController extends AbstractController {
37+
protected sanitizeAgentPayload<T = any>(payload: T): T {
38+
if (!payload || typeof payload !== 'object') return payload
39+
40+
if (Array.isArray(payload)) {
41+
return payload.map((item) => this.sanitizeAgentPayload(item)) as T
42+
}
43+
44+
const source = typeof (payload as any).toObject === 'function' ? (payload as any).toObject() : payload
45+
const cloned: Record<string, any> = { ...(source as Record<string, any>) }
46+
47+
delete cloned.password
48+
if (cloned.security && typeof cloned.security === 'object') {
49+
cloned.security = { ...cloned.security }
50+
delete cloned.security.oldPasswords
51+
}
52+
if (cloned.value && typeof cloned.value === 'object') {
53+
cloned.value = this.sanitizeAgentPayload(cloned.value)
54+
}
55+
56+
return cloned as T
57+
}
58+
3759
/**
3860
* Projection des champs retournés pour les opérations de recherche
3961
*
@@ -91,7 +113,7 @@ export class AgentsController extends AbstractController {
91113
const data = await this._service.create(body)
92114
return res.status(HttpStatus.CREATED).json({
93115
statusCode: HttpStatus.CREATED,
94-
data,
116+
data: this.sanitizeAgentPayload(data),
95117
})
96118
}
97119

@@ -173,11 +195,11 @@ export class AgentsController extends AbstractController {
173195
): Promise<Response> {
174196
const data = await this._service.findById(_id, {
175197
password: 0,
176-
security: 0,
198+
'security.oldPasswords': 0,
177199
})
178200
return res.status(HttpStatus.OK).json({
179201
statusCode: HttpStatus.OK,
180-
data,
202+
data: this.sanitizeAgentPayload(data),
181203
})
182204
}
183205

@@ -211,7 +233,7 @@ export class AgentsController extends AbstractController {
211233
const data = await this._service.update(_id, body)
212234
return res.status(HttpStatus.OK).json({
213235
statusCode: HttpStatus.OK,
214-
data,
236+
data: this.sanitizeAgentPayload(data),
215237
})
216238
}
217239

@@ -241,7 +263,7 @@ export class AgentsController extends AbstractController {
241263
const data = await this._service.delete(_id)
242264
return res.status(HttpStatus.OK).json({
243265
statusCode: HttpStatus.OK,
244-
data,
266+
data: this.sanitizeAgentPayload(data),
245267
})
246268
}
247269
}

apps/web/src/pages/settings/agents/[_id].vue

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,11 @@ export default defineNuxtComponent({
5656
transform: (result: unknown) => {
5757
const res = result as Response | undefined
5858
if (!res || res.data == null) throw new Error('Invalid API response')
59-
return res.data as Agent
59+
const agent = res.data as Agent & { security?: { allowedNetworks?: string[] }; allowedNetworks?: string[] }
60+
return {
61+
...agent,
62+
allowedNetworks: Array.isArray(agent?.security?.allowedNetworks) ? [...agent.security.allowedNetworks] : [],
63+
} as Agent
6064
},
6165
})
6266
if (error.value) {

apps/web/src/pages/settings/agents/[_id]/index.vue

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,19 @@ export default defineNuxtComponent({
8787
const sanitizedAgent: Agent & Record<string, unknown> = { ...this.data.agent }
8888
delete sanitizedAgent.metadata
8989
90+
const allowedNetworks = Array.isArray(sanitizedAgent.allowedNetworks)
91+
? sanitizedAgent.allowedNetworks
92+
: undefined
93+
const security = typeof sanitizedAgent.security === 'object' && sanitizedAgent.security !== null ? { ...(sanitizedAgent.security as Record<string, unknown>) } : {}
94+
delete security.oldPasswords
95+
96+
if (allowedNetworks) {
97+
security.allowedNetworks = allowedNetworks
98+
}
99+
100+
sanitizedAgent.security = security
101+
delete sanitizedAgent.allowedNetworks
102+
90103
try {
91104
await this.$http[method](path, {
92105
body: sanitizedAgent,

0 commit comments

Comments
 (0)