Skip to content

Commit d9c6bd5

Browse files
committed
feat: add allowed networks configuration for agents and keyrings
- Introduced a new question for allowed networks in both AgentCreateQuestions and KeyringsCreateQuestions, allowing users to specify network filters. - Updated the AgentsController and KeyringsController to utilize the NetworkAllowedGuard for enhanced security based on allowed networks. - Modified the AgentsCommand to handle the new allowedNetworks property, ensuring proper integration during agent creation. - Enhanced the Keyrings module to include the NetworkAllowedGuard, improving overall security for keyring operations.
1 parent 13e26db commit d9c6bd5

5 files changed

Lines changed: 88 additions & 4 deletions

File tree

apps/api/src/_common/functions/resolve-client-ip.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import type { Request } from 'express';
44
* Lit une en-tête HTTP (string ou première entrée d'un tableau).
55
*/
66
function headerString(req: Request, name: string): string | undefined {
7-
const v = req.headers[name.toLowerCase()];
7+
const headers = req?.headers;
8+
if (!headers) return undefined;
9+
const v = headers[name.toLowerCase()];
810
if (typeof v === 'string') {
911
return v;
1012
}

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

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,19 @@ export class AgentCreateQuestions {
5959
parsePassword(val: string) {
6060
return val
6161
}
62+
63+
@Question({
64+
message: 'Réseaux autorisés (CSV, vide = aucun filtrage) ?',
65+
name: 'allowedNetworks',
66+
})
67+
parseAllowedNetworks(val: string) {
68+
const raw = `${val || ''}`.trim()
69+
if (!raw) return []
70+
return raw
71+
.split(',')
72+
.map((item) => `${item || ''}`.trim())
73+
.filter((item) => item.length > 0)
74+
}
6275
}
6376

6477
/**
@@ -106,8 +119,15 @@ export class AgentsCreateCommand extends CommandRunner {
106119
async run(_inputs: string[], _options: any): Promise<void> {
107120
this.logger.log('Starting agent creation process...')
108121
// Pose les questions définies dans AgentCreateQuestions pour obtenir les données de l'agent
109-
const agent = await this.inquirer.ask<AgentsCreateDto>('agent-create-questions', undefined)
122+
const agent = await this.inquirer.ask<AgentsCreateDto & { allowedNetworks?: string[] }>('agent-create-questions', undefined)
110123
try {
124+
if (Array.isArray((agent as any).allowedNetworks) && (agent as any).allowedNetworks.length > 0) {
125+
;(agent as any).security = {
126+
...(agent as any).security,
127+
allowedNetworks: (agent as any).allowedNetworks,
128+
}
129+
}
130+
delete (agent as any).allowedNetworks
111131
// Crée l'agent avec les données collectées
112132
await this.agentsService.create(agent)
113133
console.log('Agent created successfully')

apps/api/src/core/auth/_strategies/jwt.strategy.ts

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,36 @@ import { AgentType } from '~/_common/types/agent.type';
88
import { JwtPayload } from 'jsonwebtoken';
99
import { Keyrings } from '~/core/keyrings/_schemas/keyrings.schema';
1010
import { Agents } from '~/core/agents/_schemas/agents.schema';
11+
import { resolveClientIp } from '~/_common/functions/resolve-client-ip';
12+
import ipRangeCheck from 'ip-range-check';
13+
14+
function isClientIpAllowed(allowedNetworks?: string[] | null, clientIp?: string | null): boolean {
15+
if (!Array.isArray(allowedNetworks) || allowedNetworks.length === 0) return true;
16+
if (!clientIp) return false;
17+
18+
const normalizedRules = allowedNetworks.map((item) => `${item || ''}`.trim()).filter((item) => item.length > 0);
19+
if (normalizedRules.length === 0) return true;
20+
21+
try {
22+
return ipRangeCheck(clientIp, normalizedRules);
23+
} catch {
24+
return false;
25+
}
26+
}
27+
28+
function resolveAllowedNetworksFromVerifiedIdentity(verified: unknown): string[] | null {
29+
const v = verified as any;
30+
const direct = Array.isArray(v?.allowedNetworks) ? (v.allowedNetworks as string[]) : null;
31+
if (direct && direct.length > 0) return direct;
32+
33+
const fromSecurity = Array.isArray(v?.security?.allowedNetworks) ? (v.security.allowedNetworks as string[]) : null;
34+
if (fromSecurity && fromSecurity.length > 0) return fromSecurity;
35+
36+
const fromIdentitySecurity = Array.isArray(v?.identity?.security?.allowedNetworks) ? (v.identity.security.allowedNetworks as string[]) : null;
37+
if (fromIdentitySecurity && fromIdentitySecurity.length > 0) return fromIdentitySecurity;
38+
39+
return null;
40+
}
1141

1242
@Injectable()
1343
export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
@@ -27,7 +57,7 @@ export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
2757

2858
// noinspection JSUnusedGlobalSymbols
2959
public async validate(
30-
_: Request,
60+
req: Request,
3161
payload: JwtPayload & { identity: AgentType; mfaVerified?: boolean; mfaVerifiedAt?: number | null },
3262
done: VerifiedCallback,
3363
): Promise<void> {
@@ -37,6 +67,12 @@ export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
3767

3868
if (!user) return done(new ForbiddenException(), false);
3969

70+
const clientIp = resolveClientIp(req);
71+
const allowedNetworks = resolveAllowedNetworksFromVerifiedIdentity(user);
72+
if (!isClientIpAllowed(allowedNetworks, clientIp)) {
73+
return done(new ForbiddenException('Network not allowed'), false);
74+
}
75+
4076
const roles = [...(Array.isArray(payload.identity?.roles) ? payload.identity.roles : [])];
4177
if (!roles.includes('admin') && payload.identity?._id === '000000000000000000000000') {
4278
roles.push('admin');

apps/api/src/core/keyrings/keyrings.command.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,20 @@ export class KeyringsCreateQuestions {
3030

3131
return roles;
3232
}
33+
34+
@Question({
35+
message: 'Réseaux autorisés (CSV, vide = 0.0.0.0/0) ?',
36+
name: 'allowedNetworks',
37+
})
38+
parseAllowedNetworks(val: string) {
39+
const raw = `${val || ''}`.trim()
40+
if (!raw) return ['0.0.0.0/0']
41+
const values = raw
42+
.split(',')
43+
.map((item) => `${item || ''}`.trim())
44+
.filter((item) => item.length > 0)
45+
return values.length > 0 ? values : ['0.0.0.0/0']
46+
}
3347
}
3448

3549
@SubCommand({ name: 'create' })

apps/web/src/plugins/http-mfa.client.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,21 @@ export default defineNuxtPlugin((nuxtApp) => {
5151
prompt: {
5252
model: '',
5353
type: 'text',
54+
autocomplete: 'one-time-code',
55+
autocorrect: 'off',
56+
autocapitalize: 'off',
57+
spellcheck: false,
58+
inputmode: 'numeric',
59+
pattern: '[0-9]{6}',
60+
maxlength: 6,
61+
minlength: 6,
62+
required: true,
63+
placeholder: '123456',
64+
label: 'Code TOTP',
65+
outlined: true,
5466
isValid: (val: string) => /^\d{6}$/.test(String(val || '').trim()),
5567
},
56-
cancel: true,
68+
cancel: false,
5769
persistent: true,
5870
color: 'warning',
5971
ok: { label: 'Valider', color: 'warning' },

0 commit comments

Comments
 (0)