4
.vscode/extensions.json
vendored
4
.vscode/extensions.json
vendored
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"recommendations": [
|
||||
|
||||
"nrwl.angular-console",
|
||||
"esbenp.prettier-vscode"
|
||||
"esbenp.prettier-vscode",
|
||||
"dbaeumer.vscode-eslint"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import { Controller, Get, Req } from '@nestjs/common';
|
||||
import { Roles } from 'nest-keycloak-connect';
|
||||
|
||||
@Controller('shop')
|
||||
export class UserController {
|
||||
@Get('group')
|
||||
@Roles({ roles: ['shop'] })
|
||||
async getGroups(@Req() req) {
|
||||
return {
|
||||
user: req.user,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
export class HeaderConstants {
|
||||
static readonly AUTHORIZATION = 'authorization';
|
||||
static readonly TRACE_ID = 'x-traceid';
|
||||
|
||||
// Puoi aggiungere qui altri header futuri
|
||||
static readonly CORRELATION_ID = 'x-correlation-id';
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
// apps/gateway/src/common/interceptors/axios-forward.service.ts
|
||||
import { Injectable, OnModuleInit } from '@nestjs/common';
|
||||
import { HttpService } from '@nestjs/axios';
|
||||
import { traceStorage } from '../storage/trace-storage';
|
||||
import { HeaderConstants } from '../constants/headers.constants';
|
||||
|
||||
@Injectable()
|
||||
export class AxiosConfigService implements OnModuleInit {
|
||||
constructor(private readonly httpService: HttpService) {}
|
||||
|
||||
onModuleInit() {
|
||||
// Accediamo all'istanza Axios interna di NestJS
|
||||
const axios = this.httpService.axiosRef;
|
||||
|
||||
axios.interceptors.request.use((config) => {
|
||||
// Recuperiamo i dati dal magazzino ALS
|
||||
const store = traceStorage.getStore();
|
||||
|
||||
if (store) {
|
||||
if (store.authorization) {
|
||||
config.headers[HeaderConstants.AUTHORIZATION] = store.authorization;
|
||||
}
|
||||
if (store.traceId) {
|
||||
config.headers[HeaderConstants.TRACE_ID] = store.traceId;
|
||||
}
|
||||
if (store.correlationId) {
|
||||
config.headers[HeaderConstants.CORRELATION_ID] = store.correlationId;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`[Axios Outgoing] URL: ${config.url}`);
|
||||
console.log(`[Axios Outgoing] Headers:`, config.headers);
|
||||
|
||||
return config;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
// apps/gateway/src/common/middleware/trace.middleware.ts
|
||||
import { Injectable, NestMiddleware } from '@nestjs/common';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { traceStorage } from '../storage/trace-storage';
|
||||
import { HeaderConstants } from '../constants/headers.constants';
|
||||
|
||||
@Injectable()
|
||||
export class TraceMiddleware implements NestMiddleware {
|
||||
use(req: any, res: any, next: () => void) {
|
||||
|
||||
// 1. Gestione Authorization: se manca, sarà undefined
|
||||
const auth = req.headers[HeaderConstants.AUTHORIZATION];
|
||||
if (!auth) {
|
||||
console.warn(
|
||||
`[Gateway] Richiesta ricevuta senza header di autorizzazione su: ${req.url}`,
|
||||
);
|
||||
}
|
||||
|
||||
// 2. Gestione TraceID: se manca, ne generiamo uno nuovo (fondamentale per il debug!)
|
||||
const traceId = req.headers[HeaderConstants.TRACE_ID] || uuidv4();
|
||||
const correlationId =
|
||||
req.headers[HeaderConstants.CORRELATION_ID] || uuidv4();
|
||||
|
||||
// Opzionale: rimandiamo il traceId nella risposta per aiutare il frontend
|
||||
res.setHeader(HeaderConstants.TRACE_ID, traceId);
|
||||
|
||||
const store = {
|
||||
authorization: auth,
|
||||
traceId: traceId,
|
||||
correlationId: correlationId,
|
||||
};
|
||||
|
||||
// .run() fa sì che tutto ciò che accade dentro 'next()'
|
||||
// abbia accesso a questo 'store'
|
||||
traceStorage.run(store, () => {
|
||||
next();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
// apps/gateway/src/common/storage/trace-storage.ts
|
||||
import { AsyncLocalStorage } from 'async_hooks';
|
||||
|
||||
export interface TraceContext {
|
||||
authorization?: string;
|
||||
traceId?: string;
|
||||
correlationId?: string;
|
||||
}
|
||||
|
||||
// Creiamo un'istanza globale del magazzino
|
||||
export const traceStorage = new AsyncLocalStorage<TraceContext>();
|
||||
|
||||
39
nx.json
39
nx.json
@@ -2,15 +2,38 @@
|
||||
"$schema": "./node_modules/nx/schemas/nx-schema.json",
|
||||
"analytics": false,
|
||||
"namedInputs": {
|
||||
"default": [
|
||||
"{projectRoot}/**/*",
|
||||
"sharedGlobals"
|
||||
],
|
||||
"default": ["{projectRoot}/**/*", "sharedGlobals"],
|
||||
"production": [
|
||||
"default"
|
||||
"default",
|
||||
"!{projectRoot}/.eslintrc.json",
|
||||
"!{projectRoot}/eslint.config.mjs"
|
||||
],
|
||||
"sharedGlobals": [
|
||||
"{workspaceRoot}/.github/workflows/ci.yml"
|
||||
]
|
||||
"sharedGlobals": ["{workspaceRoot}/.github/workflows/ci.yml"]
|
||||
},
|
||||
"plugins": [
|
||||
{
|
||||
"plugin": "@nx/webpack/plugin",
|
||||
"options": {
|
||||
"buildTargetName": "build",
|
||||
"serveTargetName": "serve",
|
||||
"previewTargetName": "preview",
|
||||
"buildDepsTargetName": "build-deps",
|
||||
"watchDepsTargetName": "watch-deps",
|
||||
"serveStaticTargetName": "serve-static"
|
||||
}
|
||||
},
|
||||
{
|
||||
"plugin": "@nx/eslint/plugin",
|
||||
"options": {
|
||||
"targetName": "lint"
|
||||
}
|
||||
}
|
||||
],
|
||||
"targetDefaults": {
|
||||
"@nx/js:tsc": {
|
||||
"cache": true,
|
||||
"dependsOn": ["^build"],
|
||||
"inputs": ["production", "^production"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
41
package.json
41
package.json
@@ -4,10 +4,47 @@
|
||||
"license": "MIT",
|
||||
"scripts": {},
|
||||
"private": true,
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.8.0",
|
||||
"@nestjs/schematics": "^11.0.0",
|
||||
"@nestjs/testing": "^11.0.0",
|
||||
"@nx/eslint": "22.6.4",
|
||||
"@nx/eslint-plugin": "22.6.4",
|
||||
"@nx/js": "22.6.4",
|
||||
"@nx/nest": "^22.6.4",
|
||||
"@nx/node": "22.6.4",
|
||||
"@nx/web": "22.6.4",
|
||||
"@nx/webpack": "22.6.4",
|
||||
"@nx/workspace": "22.6.4",
|
||||
"nx": "22.6.4"
|
||||
"@swc-node/register": "~1.11.1",
|
||||
"@swc/core": "~1.15.5",
|
||||
"@swc/helpers": "~0.5.18",
|
||||
"@types/node": "20.19.9",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"eslint": "^9.8.0",
|
||||
"eslint-config-prettier": "^10.0.0",
|
||||
"jsonc-eslint-parser": "^2.1.0",
|
||||
"nx": "22.6.4",
|
||||
"prettier": "~3.6.2",
|
||||
"tslib": "^2.3.0",
|
||||
"typescript": "~5.9.2",
|
||||
"typescript-eslint": "^8.40.0",
|
||||
"webpack-cli": "^5.1.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@keycloak/keycloak-admin-client": "^26.5.7",
|
||||
"@nestjs/axios": "^4.0.1",
|
||||
"@nestjs/common": "^10.4.22",
|
||||
"@nestjs/config": "^4.0.3",
|
||||
"@nestjs/core": "^10.4.22",
|
||||
"@nestjs/platform-express": "^10.4.22",
|
||||
"axios": "^1.14.0",
|
||||
"jsonwebtoken": "^9.0.3",
|
||||
"jwks-rsa": "^4.0.1",
|
||||
"keycloak-connect": "^26.1.1",
|
||||
"nest-keycloak-connect": "^1.10.1",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"rxjs": "^7.8.0",
|
||||
"uuid": "^13.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
8596
pnpm-lock.yaml
generated
8596
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,14 @@
|
||||
#
|
||||
PORT=3002
|
||||
#
|
||||
KEYCLOAK_URL=http://localhost:8080
|
||||
# Base URL del server
|
||||
KEYCLOAK_BASE_URL=http://localhost:8080
|
||||
|
||||
# URL Auth derivata (concatenazione)
|
||||
KEYCLOAK_URL_AUTH=${KEYCLOAK_BASE_URL}/auth
|
||||
|
||||
# altre configurazioni
|
||||
KEYCLOAK_REALM=appweb
|
||||
KEYCLOAK_CLIENT_ID=gw-negozi
|
||||
KEYCLOAK_CLIENT_SECRET=Nf0WIneXN9bOleTpgrbj54ypI8AnIbd5
|
||||
KEYCLOAK_CLIENT_ID=user-service
|
||||
KEYCLOAK_CLIENT_SECRET=tCDsN56Y3Ii4h0SfqrT4o6SoV8fw2fe8
|
||||
#
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import KeycloakAdminClient from '@keycloak/keycloak-admin-client';
|
||||
import { Injectable, OnModuleInit, Logger } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { KeycloakConfigKeys } from '@appweb/shared';
|
||||
|
||||
@Injectable()
|
||||
export class KeycloakService implements OnModuleInit {
|
||||
private kcAdminClient: KeycloakAdminClient;
|
||||
private readonly logger = new Logger(KeycloakService.name);
|
||||
|
||||
constructor(private readonly configService: ConfigService) {}
|
||||
|
||||
async onModuleInit() {
|
||||
// Inizializzazione del client con i valori presi dal file .env
|
||||
this.kcAdminClient = new KeycloakAdminClient({
|
||||
baseUrl: this.configService.get<string>(KeycloakConfigKeys.BASE_URL),
|
||||
realmName: this.configService.get<string>(KeycloakConfigKeys.REALM),
|
||||
});
|
||||
|
||||
try {
|
||||
// Autenticazione tramite Service Account
|
||||
await this.kcAdminClient.auth({
|
||||
grantType: 'client_credentials',
|
||||
clientId: this.configService.get<string>(KeycloakConfigKeys.CLIENT_ID),
|
||||
clientSecret: this.configService.get<string>(KeycloakConfigKeys.CLIENT_SECRET),
|
||||
});
|
||||
this.logger.log(
|
||||
'Connessione a Keycloak Admin API stabilita con successo.',
|
||||
);
|
||||
} catch (error) {
|
||||
this.logger.error("Errore durante l'autenticazione su Keycloak:", error);
|
||||
}
|
||||
}
|
||||
|
||||
// Esempio: Ottieni tutti i gruppi
|
||||
async getGroups() {
|
||||
return await this.kcAdminClient.groups.find();
|
||||
}
|
||||
|
||||
// Esempio: Ottieni tutti gli utenti
|
||||
async getUsers() {
|
||||
return await this.kcAdminClient.users.find();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import { Controller, Get, Post, Body } from '@nestjs/common';
|
||||
import { AuthenticatedUser, Roles } from 'nest-keycloak-connect';
|
||||
|
||||
@Controller('user')
|
||||
export class UserController {
|
||||
@Post()
|
||||
@Roles({ roles: ['shop'] }) // Solo chi ha il ruolo admin può creare
|
||||
async createData(@Body() data: any, @AuthenticatedUser() user: any) {
|
||||
// 'user' contiene i dati del JWT (sub, preferred_username, email)
|
||||
const userId = user.sub;
|
||||
const userName = user.preferred_username;
|
||||
|
||||
console.log(`L'utente ${userName} sta creando un record`);
|
||||
|
||||
// Qui chiamerai il tuo service passando userId per il database
|
||||
return { message: 'Dati salvati', creatoDa: userId };
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user