From d5141e5b6ae36399984778ce6cb042115ffa6804 Mon Sep 17 00:00:00 2001 From: marcos-silva-rodrigues Date: Mon, 26 May 2025 13:37:25 -0300 Subject: [PATCH] REFACTOR: rename openid to identity provider --- docsfera.json | 229 ++++++++---------- package-lock.json | 24 +- package.json | 2 +- src/app.module.ts | 4 +- src/main.ts | 1 - src/modules/auth/auth.controller.ts | 3 +- .../dto/identity-provider.dto.ts | 38 +++ .../identity-provider.controller.ts | 89 +++++++ .../identity-provider.module.ts} | 10 +- .../identity-provider.service.ts | 85 +++++++ src/modules/openid/dto/openid.ts | 16 -- src/modules/openid/openid.controller.ts | 73 ------ src/modules/openid/openid.service.ts | 79 ------ 13 files changed, 340 insertions(+), 313 deletions(-) create mode 100644 src/modules/identity-provider/dto/identity-provider.dto.ts create mode 100644 src/modules/identity-provider/identity-provider.controller.ts rename src/modules/{openid/openid.module.ts => identity-provider/identity-provider.module.ts} (51%) create mode 100644 src/modules/identity-provider/identity-provider.service.ts delete mode 100644 src/modules/openid/dto/openid.ts delete mode 100644 src/modules/openid/openid.controller.ts delete mode 100644 src/modules/openid/openid.service.ts diff --git a/docsfera.json b/docsfera.json index 2392ee9..d2c0e5f 100644 --- a/docsfera.json +++ b/docsfera.json @@ -101,131 +101,6 @@ ] } }, - "/auth/idp": { - "post": { - "operationId": "AuthController_addIdentityProvider", - "parameters": [ - { - "name": "dadosfera-lang", - "in": "header", - "required": false, - "schema": { - "enum": [ - "pt-br", - "en-us" - ], - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/AuthRefreshAccessTokenRes" - } - } - } - } - }, - "tags": [ - "Auth" - ], - "security": [ - { - "access-token": [] - } - ] - } - }, - "/auth/idp/{name}": { - "get": { - "operationId": "AuthController_loginIdp", - "parameters": [ - { - "name": "dadosfera-lang", - "in": "header", - "required": false, - "schema": { - "enum": [ - "pt-br", - "en-us" - ], - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/AuthRefreshAccessTokenRes" - } - } - } - } - }, - "tags": [ - "Auth" - ], - "security": [ - { - "access-token": [] - } - ] - } - }, - "/auth/idp/{name}/callback": { - "get": { - "operationId": "AuthController_callbackIdp", - "parameters": [ - { - "name": "dadosfera-lang", - "in": "header", - "required": false, - "schema": { - "enum": [ - "pt-br", - "en-us" - ], - "type": "string" - } - }, - { - "name": "name", - "required": true, - "in": "path", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/AuthRefreshAccessTokenRes" - } - } - } - } - }, - "tags": [ - "Auth" - ], - "security": [ - { - "access-token": [] - } - ] - } - }, "/auth/sso/snowflake": { "post": { "operationId": "AuthController_snowflakeSignIn", @@ -6004,6 +5879,50 @@ ] } }, + "/identity-providers": { + "post": { + "operationId": "IdentityProviderController_addIdentityProvider", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateIdentityProvider" + } + } + } + }, + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IdentityProviderResponse" + } + } + } + } + } + }, + "get": { + "operationId": "IdentityProviderController_getProviders", + "parameters": [], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IdentityProviderListResponse" + } + } + } + } + } + } + }, "/network-policy": { "get": { "operationId": "NetworkPolicyController_getNetworks", @@ -8960,6 +8879,66 @@ "theme" ] }, + "CreateIdentityProvider": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "clientId": { + "type": "string" + }, + "clientSecret": { + "type": "string" + }, + "issueUrl": { + "type": "string" + } + }, + "required": [ + "name", + "clientId", + "clientSecret", + "issueUrl" + ] + }, + "IdentityProviderResponse": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "clientId": { + "type": "string" + }, + "issueUrl": { + "type": "string" + } + }, + "required": [ + "id", + "name", + "clientId", + "issueUrl" + ] + }, + "IdentityProviderListResponse": { + "type": "object", + "properties": { + "providers": { + "type": "array", + "items": { + "$ref": "#/components/schemas/IdentityProviderResponse" + } + } + }, + "required": [ + "providers" + ] + }, "NetworkPoliciesDTO": { "type": "object", "properties": { diff --git a/package-lock.json b/package-lock.json index 832a278..dd37be4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "@aws-sdk/client-secrets-manager": "^3.414.0", "@dadosfera/dadosfera-logs": "^1.0.0-beta.4", "@dadosfera/protospack": "2.5.3", - "@dadosfera/protospack-v2": "3.37.0-beta.23", + "@dadosfera/protospack-v2": "file:../protospack-v2/dadosfera-protospack-v2-0.0.0.tgz", "@grpc/grpc-js": "^1.9.3", "@grpc/proto-loader": "^0.7.9", "@nestjs/cli": "^9.5.0", @@ -33,6 +33,8 @@ "csv": "^6.3.11", "dotenv": "^14.3.2", "elastic-apm-node": "^3.50.0", + "express-session": "^1.18.1", + "handlebars": "^4.7.8", "helmet": "^5.1.1", "jsonwebtoken": "^9.0.2", "jwk-to-pem": "^2.0.5", @@ -1401,9 +1403,9 @@ } }, "node_modules/@dadosfera/protospack-v2": { - "version": "3.37.0-beta.10", - "resolved": "https://dadosfera-611330257153.d.codeartifact.us-east-1.amazonaws.com/npm/dadosfera-npm/@dadosfera/protospack-v2/-/protospack-v2-3.37.0-beta.10.tgz", - "integrity": "sha512-nKSu9VpOPFSTeX0yN4aEY0HjvGxgbLPYXl217NHgLm+a/+i01USDaxjwIOqP5hhDvhnMNaiCq5awiQTYbhF/7A==", + "version": "0.0.0", + "resolved": "file:../protospack-v2/dadosfera-protospack-v2-0.0.0.tgz", + "integrity": "sha512-k5eDEeKuWeWaSrfymtiWtHP/u6bnZoldz9eDcFE86O54To6m8gbNM8ON4GhqOGLhbpWo0DZ6CoyPNCGhTt3Zqg==", "license": "ISC", "dependencies": { "@grpc/grpc-js": "^1.9.3", @@ -3724,11 +3726,15 @@ "@types/send": "*" } }, - "node_modules/@types/gensync": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@types/gensync/-/gensync-1.0.4.tgz", - "integrity": "sha512-C3YYeRQWp2fmq9OryX+FoDy8nXS6scQ7dPptD8LnFDAUNcKWJjXQKDNJD3HVm+kOUsXhTOkpi69vI4EuAr95bA==", - "dev": true + "node_modules/@types/express-session": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/@types/express-session/-/express-session-1.18.1.tgz", + "integrity": "sha512-S6TkD/lljxDlQ2u/4A70luD8/ZxZcrU5pQwI1rVXCiaVIywoFgbA+PIUNDjPhQpPdK0dGleLtYc/y7XWBfclBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express": "*" + } }, "node_modules/@types/graceful-fs": { "version": "4.1.9", diff --git a/package.json b/package.json index a3798f0..c9451fd 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "@aws-sdk/client-secrets-manager": "^3.414.0", "@dadosfera/dadosfera-logs": "^1.0.0-beta.4", "@dadosfera/protospack": "2.5.3", - "@dadosfera/protospack-v2": "3.37.0-beta.23", + "@dadosfera/protospack-v2": "file:../protospack-v2/dadosfera-protospack-v2-0.0.0.tgz", "@grpc/grpc-js": "^1.9.3", "@grpc/proto-loader": "^0.7.9", "@nestjs/cli": "^9.5.0", diff --git a/src/app.module.ts b/src/app.module.ts index 00ff830..1cb7032 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -28,7 +28,7 @@ import { MixpanelModule } from './modules/mixpanel/mixpanel.module'; import { CustomersModule } from './modules/customers/customers.module'; import { OpenDataModule } from './modules/open-data/open-data.module'; import { ThemeModule } from './modules/theme/theme.module'; -import { OpenidModule } from './modules/openid/openid.module'; +import { IdentityProviderModule } from './modules/identity-provider/identity-provider.module'; import { NetworkPolicyModule } from './modules/network-policy/network-policy.module'; @Module({ @@ -65,7 +65,7 @@ import { NetworkPolicyModule } from './modules/network-policy/network-policy.mod ThemeModule, //Always leave HealthModule last, so it is on the bottom of swagger HealthModule, - OpenidModule, + IdentityProviderModule, NetworkPolicyModule ], }) diff --git a/src/main.ts b/src/main.ts index 1f18744..eff6350 100644 --- a/src/main.ts +++ b/src/main.ts @@ -30,7 +30,6 @@ async function bootstrap() { secret: process.env.SESSION_SECRET, resave: false, saveUninitialized: false, - })); configureSwagger(app); await app.listen(3333); diff --git a/src/modules/auth/auth.controller.ts b/src/modules/auth/auth.controller.ts index c6dd753..af3f053 100644 --- a/src/modules/auth/auth.controller.ts +++ b/src/modules/auth/auth.controller.ts @@ -26,8 +26,7 @@ import { AuthConfirmResetPasswordRequest, AuthEnableTotpMfaRequest, AuthDisableTotpMfaRequest, - AuthVerifyTotpMfaRequest, - IdProvider, + AuthVerifyTotpMfaRequest } from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/messages'; import { PERMISSIONS_GROUPS } from 'src/authentication/permissions.enum'; diff --git a/src/modules/identity-provider/dto/identity-provider.dto.ts b/src/modules/identity-provider/dto/identity-provider.dto.ts new file mode 100644 index 0000000..d64cfc9 --- /dev/null +++ b/src/modules/identity-provider/dto/identity-provider.dto.ts @@ -0,0 +1,38 @@ +import { ApiProperty } from "@nestjs/swagger"; + +export class CreateIdentityProvider { + + @ApiProperty() + name: string; + + @ApiProperty() + clientId: string; + + @ApiProperty() + clientSecret: string; + + @ApiProperty() + issueUrl: string; +} + + +export class IdentityProviderResponse { + + @ApiProperty() + id: string; + + @ApiProperty() + name: string; + + @ApiProperty() + clientId: string; + + @ApiProperty() + issueUrl: string; +} + +export class IdentityProviderListResponse { + + @ApiProperty() + providers: IdentityProviderResponse[] +} \ No newline at end of file diff --git a/src/modules/identity-provider/identity-provider.controller.ts b/src/modules/identity-provider/identity-provider.controller.ts new file mode 100644 index 0000000..274ee9f --- /dev/null +++ b/src/modules/identity-provider/identity-provider.controller.ts @@ -0,0 +1,89 @@ +import DadosferaLogger from '@dadosfera/dadosfera-logs'; +import { + Body, + Controller, + Get, + HttpCode, + HttpStatus, + Inject, + Param, + Post, + Redirect, + Req, +} from '@nestjs/common'; +import { IdentityProviderService } from './identity-provider.service'; +import { RequestUser, User } from 'src/decorators/user.decorator'; +import { Language } from 'src/decorators/language.decorator'; +import { LanguageEnum } from 'src/utils/languages.enum'; +import { PackTheMetadata } from 'src/utils/ PackTheMetadata'; +import { ApiOkResponse } from '@nestjs/swagger'; +import { CreateIdentityProvider, IdentityProviderListResponse, IdentityProviderResponse } from './dto/identity-provider.dto'; +import { Request } from 'express'; + +@Controller('identity-providers') +export class IdentityProviderController { + logger: DadosferaLogger; + + constructor( + @Inject(DadosferaLogger) + dadosferaLogger: DadosferaLogger, + private identityProviderService: IdentityProviderService, + ) { + this.logger = dadosferaLogger.logger; + } + + @Post() + @HttpCode(HttpStatus.OK) + @ApiOkResponse({ type: IdentityProviderResponse }) + async addIdentityProvider( + @User() user: RequestUser, + @Body() body: CreateIdentityProvider, + @Language() language: LanguageEnum, + ) { + this.logger.info('/openid - addIdentityProvider'); + + const metadata = PackTheMetadata({ + customer_id: user.customer_id, + language, + }); + + return await this.identityProviderService.create(body, metadata); + } + + @Get() + @HttpCode(HttpStatus.OK) + @ApiOkResponse({ type: IdentityProviderListResponse }) + async getProviders( + @User() user: RequestUser, + @Language() language: LanguageEnum, + ) { + this.logger.info('GET identity-providers'); + const metadata = PackTheMetadata({ + ...user, + language + }) + + return await this.identityProviderService.getList(metadata) + } + + // @Get(':name') + // @HttpCode(HttpStatus.OK) + // @Redirect() + // async loginIdp(@Param('name') name: string , @Req() req: Request) { + // this.logger.info('/openid - :name'); + + // const redirectUrl = await this.openidService.loginIdp(name, req); + // return { + + // url: redirectUrl, + // }; + // } + + // @Get('/:name/callback') + // @HttpCode(HttpStatus.OK) + // async callbackIdp(@Param('name') name: string, @Req() req: Request) { + // this.logger.info('/openid - /:name/callback'); + + // return await this.openidService.authorizeIdp(name, req); + // } +} diff --git a/src/modules/openid/openid.module.ts b/src/modules/identity-provider/identity-provider.module.ts similarity index 51% rename from src/modules/openid/openid.module.ts rename to src/modules/identity-provider/identity-provider.module.ts index ce242d9..7449ea4 100644 --- a/src/modules/openid/openid.module.ts +++ b/src/modules/identity-provider/identity-provider.module.ts @@ -1,6 +1,6 @@ import { Module } from '@nestjs/common'; -import { OpenidController } from './openid.controller'; -import { OpenidService } from './openid.service'; +import { IdentityProviderController } from './identity-provider.controller'; +import { IdentityProviderService } from './identity-provider.service'; import { ClientsModule } from '@nestjs/microservices'; import { DucClient } from '../duc/client.config'; import DadosferaLogger from '@dadosfera/dadosfera-logs'; @@ -9,7 +9,7 @@ const client = new DucClient(); @Module({ imports: [ClientsModule.register([client.providerOptions])], - controllers: [OpenidController], - providers: [OpenidService, DadosferaLogger] + controllers: [IdentityProviderController], + providers: [IdentityProviderService, DadosferaLogger] }) -export class OpenidModule {} +export class IdentityProviderModule {} diff --git a/src/modules/identity-provider/identity-provider.service.ts b/src/modules/identity-provider/identity-provider.service.ts new file mode 100644 index 0000000..a29e198 --- /dev/null +++ b/src/modules/identity-provider/identity-provider.service.ts @@ -0,0 +1,85 @@ +import { Inject, Injectable, OnModuleInit } from '@nestjs/common'; +import { DucClient } from '../duc/client.config'; +import { ClientGrpc } from '@nestjs/microservices'; +import { ProtoServices } from '@dadosfera/protospack-v2/dist/lib/Duc'; +import { IdentityProviderProtoService } from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/write-service'; +import { lastValueFrom } from 'rxjs'; +import { IdentityProviderRequest } from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/messages'; +import { Metadata } from '@grpc/grpc-js'; +import { Issuer, generators } from 'openid-client'; +import { Request } from 'express'; + +@Injectable() +export class IdentityProviderService implements OnModuleInit { + private identityProviderService: IdentityProviderProtoService; + constructor( + @Inject(DucClient.name) private readonly grpcClient: ClientGrpc, + ) {} + + onModuleInit() { + this.identityProviderService = + this.grpcClient.getService( + ProtoServices.IdentityProviderProtoService, + ); + } + + async create(body: IdentityProviderRequest, metadata: Metadata) { + return await lastValueFrom( + this.identityProviderService.Create(body, metadata), + ); + } + + async getList(metadata: Metadata) { + return await lastValueFrom( + this.identityProviderService.GetList({}, metadata), + ); + } + + // async loginIdp(name: string, req: Request) { + // const client = await this.buildOpenIdClient(name); + + // const code_verifier: string = generators.codeVerifier(); + // const code_challenge: string = generators.codeChallenge(code_verifier); + + // req.session.code_verifier = code_verifier; + + // return client.authorizationUrl({ + // scope: 'openid email', + // response_type: 'code', + // code_challenge, + // code_challenge_method: 'S256', + // }); + // } + + // async authorizeIdp(name: string, req: Request) { + // const client = await this.buildOpenIdClient(name); + // const params = client.callbackParams(req); + // const code_verifier = req.session.code_verifier; + // const tokenSet = await client.callback( + // `http://localhost:3333/openid/${name}/callback`, + // params, + // { code_verifier }, + // ); + + // delete req.session.code_verifier; + + // console.log('received and validated tokens %j', tokenSet); + // console.log('validated ID Token claims %j', tokenSet.claims()); + + // return tokenSet.claims(); + // } + + // private async buildOpenIdClient(name: string) { + // const idp = await lastValueFrom( + // this.identityProviderService.FindIdentityProvider({ name }), + // ); + + // const issuer = await Issuer.discover(idp.issueUrl); + // return new issuer.Client({ + // client_id: idp.clientId, + // client_secret: idp.clientSecret, + // redirect_uris: [`http://localhost:3333/openid/${idp.name}/callback`], + // response_types: ['code'], + // }); + // } +} diff --git a/src/modules/openid/dto/openid.ts b/src/modules/openid/dto/openid.ts deleted file mode 100644 index c5cc838..0000000 --- a/src/modules/openid/dto/openid.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { ApiProperty } from "@nestjs/swagger"; - -export class CreatedIdentityProvider { - - @ApiProperty() - name: string; - - @ApiProperty() - clientId: string; - - @ApiProperty() - clientSecret: string; - - @ApiProperty() - issueUrl: string; -} \ No newline at end of file diff --git a/src/modules/openid/openid.controller.ts b/src/modules/openid/openid.controller.ts deleted file mode 100644 index 028834e..0000000 --- a/src/modules/openid/openid.controller.ts +++ /dev/null @@ -1,73 +0,0 @@ -import DadosferaLogger from '@dadosfera/dadosfera-logs'; -import { - Body, - Controller, - Get, - HttpCode, - HttpStatus, - Inject, - Param, - Post, - Redirect, - Req, -} from '@nestjs/common'; -import { OpenidService } from './openid.service'; -import { RequestUser, User } from 'src/decorators/user.decorator'; -import { Language } from 'src/decorators/language.decorator'; -import { LanguageEnum } from 'src/utils/languages.enum'; -import { PackTheMetadata } from 'src/utils/ PackTheMetadata'; -import { ApiOkResponse } from '@nestjs/swagger'; -import { CreatedIdentityProvider } from './dto/openid'; -import { Request } from 'express'; - -@Controller('openid') -export class OpenidController { - logger: DadosferaLogger; - - constructor( - @Inject(DadosferaLogger) - dadosferaLogger: DadosferaLogger, - private openidService: OpenidService, - ) { - this.logger = dadosferaLogger.logger; - } - - @Post() - @HttpCode(HttpStatus.OK) - @ApiOkResponse({ type: CreatedIdentityProvider }) - async addIdentityProvider( - @User() user: RequestUser, - @Body() body: CreatedIdentityProvider, - @Language() language: LanguageEnum, - ) { - this.logger.info('/openid - addIdentityProvider'); - - const metadata = PackTheMetadata({ - customer_id: user.customer_id, - language, - }); - - return await this.openidService.createIdentityProvider(body, metadata); - } - - @Get(':name') - @HttpCode(HttpStatus.OK) - @Redirect() - async loginIdp(@Param('name') name: string , @Req() req: Request) { - this.logger.info('/openid - :name'); - - const redirectUrl = await this.openidService.loginIdp(name, req); - return { - - url: redirectUrl, - }; - } - - @Get('/:name/callback') - @HttpCode(HttpStatus.OK) - async callbackIdp(@Param('name') name: string, @Req() req: Request) { - this.logger.info('/openid - /:name/callback'); - - return await this.openidService.authorizeIdp(name, req); - } -} diff --git a/src/modules/openid/openid.service.ts b/src/modules/openid/openid.service.ts deleted file mode 100644 index 10879aa..0000000 --- a/src/modules/openid/openid.service.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { Inject, Injectable, OnModuleInit } from '@nestjs/common'; -import { DucClient } from '../duc/client.config'; -import { ClientGrpc } from '@nestjs/microservices'; -import { ProtoServices } from '@dadosfera/protospack-v2/dist/lib/Duc'; -import { IdentityProviderProtoService } from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/write-service'; -import { lastValueFrom } from 'rxjs'; -import { IdProvider } from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/messages'; -import { Metadata } from '@grpc/grpc-js'; -import { Issuer, generators } from 'openid-client'; -import { Request } from 'express'; - -@Injectable() -export class OpenidService implements OnModuleInit { - private identityProviderService: IdentityProviderProtoService; - constructor( - @Inject(DucClient.name) private readonly grpcClient: ClientGrpc, - ) {} - - onModuleInit() { - this.identityProviderService = - this.grpcClient.getService( - ProtoServices.IdentityProviderProtoService, - ); - } - - async createIdentityProvider(body: IdProvider, metadata: Metadata) { - return await lastValueFrom( - this.identityProviderService.CreateIdentityProvider(body, metadata), - ); - } - - async loginIdp(name: string, req: Request) { - const client = await this.buildOpenIdClient(name); - - const code_verifier: string = generators.codeVerifier(); - const code_challenge: string = generators.codeChallenge(code_verifier); - - req.session.code_verifier = code_verifier; - - return client.authorizationUrl({ - scope: 'openid email', - response_type: 'code', - code_challenge, - code_challenge_method: 'S256', - }); - } - - async authorizeIdp(name: string, req: Request) { - const client = await this.buildOpenIdClient(name); - const params = client.callbackParams(req); - const code_verifier = req.session.code_verifier; - const tokenSet = await client.callback( - `http://localhost:3333/openid/${name}/callback`, - params, - { code_verifier }, - ); - - delete req.session.code_verifier; - - console.log('received and validated tokens %j', tokenSet); - console.log('validated ID Token claims %j', tokenSet.claims()); - - return tokenSet.claims(); - } - - private async buildOpenIdClient(name: string) { - const idp = await lastValueFrom( - this.identityProviderService.FindIdentityProvider({ name }), - ); - - const issuer = await Issuer.discover(idp.issueUrl); - return new issuer.Client({ - client_id: idp.clientId, - client_secret: idp.clientSecret, - redirect_uris: [`http://localhost:3333/openid/${idp.name}/callback`], - response_types: ['code'], - }); - } -}