From ea9c0502b714461534676c2f93dbc1bd4e87b426 Mon Sep 17 00:00:00 2001 From: marcos-silva-rodrigues Date: Mon, 24 Mar 2025 07:58:26 -0300 Subject: [PATCH 01/21] UPDATE: new route to add identity provider --- docsfera.json | 68 +++++++++++++++++++++++++++++ package-lock.json | 8 ++-- package.json | 2 +- src/modules/auth/auth.controller.ts | 20 +++++++++ src/modules/auth/auth.service.ts | 16 ++++++- src/modules/auth/dtos/openid.ts | 12 +++++ 6 files changed, 120 insertions(+), 6 deletions(-) create mode 100644 src/modules/auth/dtos/openid.ts diff --git a/docsfera.json b/docsfera.json index 212ad4e..dbd3636 100644 --- a/docsfera.json +++ b/docsfera.json @@ -93,6 +93,55 @@ ] } }, + "/auth/idp": { + "post": { + "operationId": "AuthController_addIdentityProvider", + "parameters": [ + { + "name": "dadosfera-lang", + "in": "header", + "required": false, + "schema": { + "enum": [ + "pt-br", + "en-us" + ], + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateIdentityProvider" + } + } + } + }, + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthRefreshAccessTokenRes" + } + } + } + } + }, + "tags": [ + "Auth" + ], + "security": [ + { + "access-token": [] + } + ] + } + }, "/auth/sso/snowflake": { "post": { "operationId": "AuthController_snowflakeSignIn", @@ -5840,6 +5889,25 @@ "accessToken" ] }, + "CreateIdentityProvider": { + "type": "object", + "properties": { + "clientId": { + "type": "string" + }, + "clientSecret": { + "type": "string" + }, + "issueUrl": { + "type": "string" + } + }, + "required": [ + "clientId", + "clientSecret", + "issueUrl" + ] + }, "CreateConnectionDto": { "type": "object", "properties": { diff --git a/package-lock.json b/package-lock.json index 31af08d..9983a8b 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.10 ", + "@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", @@ -1298,9 +1298,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-bV/C8RR3Qska7qrNjDxPIjEsjWd5ZFjTVJg/C3zTOk4HeYvPbR+ln3elRRla4CkZzPz4T4VzD+j4284+sq5x8g==", "license": "ISC", "dependencies": { "@grpc/grpc-js": "^1.9.3", diff --git a/package.json b/package.json index 5a9463a..c824a10 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.10 ", + "@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/modules/auth/auth.controller.ts b/src/modules/auth/auth.controller.ts index 1097d51..ed9d1fc 100644 --- a/src/modules/auth/auth.controller.ts +++ b/src/modules/auth/auth.controller.ts @@ -52,6 +52,7 @@ import jwt from 'jsonwebtoken'; import { LanguageEnum } from 'src/utils/languages.enum'; import { Language } from 'src/decorators/language.decorator'; import { ApiInternalOnlyEndpoint } from 'src/decorators/swagger.decorator'; +import { CreateIdentityProvider } from './dtos/openid'; @ApiTags('Auth') @ApiHeaders([{ name: 'dadosfera-lang', enum: LanguageEnum, required: false }]) @@ -109,6 +110,25 @@ export class AuthController { return this.authClient.refreshAccessToken({ refreshToken }, metadata); } + @Post('/idp') + @HttpCode(HttpStatus.OK) + @ApiOkResponse({ type: AuthRefreshAccessTokenRes }) + @RequireAllPermissions(PERMISSIONS_GROUPS.SNOWFLAKE.permissions.OPEN) + async addIdentityProvider( + @User() user: RequestUser, + @Body() body: CreateIdentityProvider, + @Language() language: LanguageEnum, + ) { + this.logger.info('/auth - RefreshAccessToken'); + + const metadata = PackTheMetadata({ + customer_id: user.customer_id, + language, + }); + + return this.authClient.createIdentityProvider(body, metadata); + } + @ApiInternalOnlyEndpoint() @Post('/sso/snowflake') @RequireAllPermissions(PERMISSIONS_GROUPS.SNOWFLAKE.permissions.OPEN) diff --git a/src/modules/auth/auth.service.ts b/src/modules/auth/auth.service.ts index 8822086..098f369 100644 --- a/src/modules/auth/auth.service.ts +++ b/src/modules/auth/auth.service.ts @@ -4,7 +4,7 @@ import { DadosferaLogger } from '@dadosfera/dadosfera-logs'; import { lastValueFrom } from 'rxjs'; import { ProtoServices } from '@dadosfera/protospack-v2/dist/lib/Duc'; -import { AuthProtoService as AuthServiceInterface } from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/write-service'; +import { AuthProtoService as AuthServiceInterface, IdentityProviderProtoService } from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/write-service'; import { AuthSnowflakeSignInRequest, AuthSignInRequest, @@ -17,15 +17,19 @@ import { AuthResetPasswordRequest, AuthVerifyResetPasswordCodeRequest, AuthConfirmResetPasswordRequest, + CreateIdentityProviderRequest, } from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/messages'; import { DucClient } from '../duc/client.config'; import { Metadata } from '@grpc/grpc-js'; +import { CreateIdentityProvider } from './dtos/openid'; @Injectable() export class AuthClientService implements OnModuleInit { logger: DadosferaLogger; + private authService: AuthServiceInterface; + private identityProviderService: IdentityProviderProtoService; constructor( @Inject(DadosferaLogger) dadosferaLogger: DadosferaLogger, @@ -38,6 +42,10 @@ export class AuthClientService implements OnModuleInit { this.authService = this.grpcClient.getService( ProtoServices.AuthProtoService, ); + + this.identityProviderService = this.grpcClient.getService( + ProtoServices.IdentityProviderProtoService, + ); } async getPublicKeys() { @@ -168,4 +176,10 @@ export class AuthClientService implements OnModuleInit { this.authService.AuthOauthSignIn({ token, username, refreshToken: '' }), ); } + + createIdentityProvider(body: CreateIdentityProvider, metadata: Metadata) { + return lastValueFrom( + this.identityProviderService.CreateIdentityProvider(body, metadata), + ); + } } diff --git a/src/modules/auth/dtos/openid.ts b/src/modules/auth/dtos/openid.ts new file mode 100644 index 0000000..67a48d8 --- /dev/null +++ b/src/modules/auth/dtos/openid.ts @@ -0,0 +1,12 @@ +import { ApiProperty } from "@nestjs/swagger"; + +export class CreateIdentityProvider { + @ApiProperty() + clientId: string; + + @ApiProperty() + clientSecret: string; + + @ApiProperty() + issueUrl: string; +} \ No newline at end of file From f1345472b5d8d9a6753ac9ff88e000968e10e7a0 Mon Sep 17 00:00:00 2001 From: marcos-silva-rodrigues Date: Tue, 25 Mar 2025 09:18:27 -0300 Subject: [PATCH 02/21] UPDATE: add routes to login with idp --- docsfera.json | 107 ++++++++++++++----- package-lock.json | 156 +++++++++++++++++++++++++++- package.json | 3 + src/@types/express-session.d.ts | 8 ++ src/main.ts | 7 ++ src/modules/auth/auth.controller.ts | 40 ++++++- src/modules/auth/auth.service.ts | 84 ++++++++++++++- src/modules/auth/dtos/openid.ts | 6 +- 8 files changed, 377 insertions(+), 34 deletions(-) create mode 100644 src/@types/express-session.d.ts diff --git a/docsfera.json b/docsfera.json index dbd3636..6c26d80 100644 --- a/docsfera.json +++ b/docsfera.json @@ -110,16 +110,92 @@ } } ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CreateIdentityProvider" + "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": "", @@ -5889,25 +5965,6 @@ "accessToken" ] }, - "CreateIdentityProvider": { - "type": "object", - "properties": { - "clientId": { - "type": "string" - }, - "clientSecret": { - "type": "string" - }, - "issueUrl": { - "type": "string" - } - }, - "required": [ - "clientId", - "clientSecret", - "issueUrl" - ] - }, "CreateConnectionDto": { "type": "object", "properties": { diff --git a/package-lock.json b/package-lock.json index 9983a8b..2e46038 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,11 +32,13 @@ "cron-parser": "^4.9.0", "dotenv": "^14.3.2", "elastic-apm-node": "^3.50.0", + "express-session": "^1.18.1", "helmet": "^5.1.1", "jsonwebtoken": "^9.0.2", "jwk-to-pem": "^2.0.5", "mixpanel": "^0.17.0", "ms": "^3.0.0-canary.1", + "openid-client": "^5.7.1", "passport": "^0.6.0", "passport-facebook": "^3.0.0", "passport-forcedotcom": "^0.2.0", @@ -50,6 +52,7 @@ }, "devDependencies": { "@types/express": "^4.17.17", + "@types/express-session": "^1.18.1", "@types/jest": "27.0.2", "@types/jsonwebtoken": "^8.5.9", "@types/jwk-to-pem": "^2.0.1", @@ -1300,7 +1303,7 @@ "node_modules/@dadosfera/protospack-v2": { "version": "0.0.0", "resolved": "file:../protospack-v2/dadosfera-protospack-v2-0.0.0.tgz", - "integrity": "sha512-bV/C8RR3Qska7qrNjDxPIjEsjWd5ZFjTVJg/C3zTOk4HeYvPbR+ln3elRRla4CkZzPz4T4VzD+j4284+sq5x8g==", + "integrity": "sha512-Ttj4IjYPgm8c29IW7RYOF76jzCsofcKSSeo6eUrElzSYCkEJAW3k4QDlTSKmCNrv6J6SZdMckPQotGaMN2oFDA==", "license": "ISC", "dependencies": { "@grpc/grpc-js": "^1.9.3", @@ -3434,6 +3437,16 @@ "@types/send": "*" } }, + "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/gensync": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@types/gensync/-/gensync-1.0.4.tgz", @@ -6157,6 +6170,75 @@ "url": "https://opencollective.com/express" } }, + "node_modules/express-session": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.1.tgz", + "integrity": "sha512-a5mtTqEaZvBCL9A9aqkrtfz+3SMDhOVUnjafjo+s7A9Txkq+SVX2DLvSp1Zrv4uCXa3lMSK3viWnh9Gg07PBUA==", + "license": "MIT", + "dependencies": { + "cookie": "0.7.2", + "cookie-signature": "1.0.7", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-headers": "~1.0.2", + "parseurl": "~1.3.3", + "safe-buffer": "5.2.1", + "uid-safe": "~2.1.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/express-session/node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express-session/node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", + "license": "MIT" + }, + "node_modules/express-session/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express-session/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/express-session/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/express/node_modules/body-parser": { "version": "1.20.3", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", @@ -8016,6 +8098,15 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/jose": { + "version": "4.15.9", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz", + "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -8874,6 +8965,15 @@ "resolved": "https://registry.npmjs.org/object-filter-sequence/-/object-filter-sequence-1.0.0.tgz", "integrity": "sha512-CsubGNxhIEChNY4cXYuA6KXafztzHqzLLZ/y3Kasf3A+sa3lL9thq3z+7o0pZqzEinjXT6lXDPAfVWI59dUyzQ==" }, + "node_modules/object-hash": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", + "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/object-identity-map": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/object-identity-map/-/object-identity-map-1.0.2.tgz", @@ -8914,6 +9014,15 @@ "node": ">= 0.4" } }, + "node_modules/oidc-token-hash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.1.0.tgz", + "integrity": "sha512-y0W+X7Ppo7oZX6eovsRkuzcSM40Bicg2JEJkDJ4irIt1wsYAP5MLSNv+QAogO8xivMffw/9OvV3um1pxXgt1uA==", + "license": "MIT", + "engines": { + "node": "^10.13.0 || >=12.0.0" + } + }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -8925,6 +9034,15 @@ "node": ">= 0.8" } }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -8955,6 +9073,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/openid-client": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-5.7.1.tgz", + "integrity": "sha512-jDBPgSVfTnkIh71Hg9pRvtJc6wTwqjRkN88+gCFtYWrlP4Yx2Dsrow8uPi3qLr/aeymPF3o2+dS+wOpglK04ew==", + "license": "MIT", + "dependencies": { + "jose": "^4.15.9", + "lru-cache": "^6.0.0", + "object-hash": "^2.2.0", + "oidc-token-hash": "^5.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/optional-js": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/optional-js/-/optional-js-2.3.0.tgz", @@ -9662,6 +9795,15 @@ "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==" }, + "node_modules/random-bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -11083,6 +11225,18 @@ "node": ">=8" } }, + "node_modules/uid-safe": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", + "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", + "license": "MIT", + "dependencies": { + "random-bytes": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/uid2": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/uid2/-/uid2-0.0.4.tgz", diff --git a/package.json b/package.json index c824a10..d61a0da 100644 --- a/package.json +++ b/package.json @@ -50,11 +50,13 @@ "cron-parser": "^4.9.0", "dotenv": "^14.3.2", "elastic-apm-node": "^3.50.0", + "express-session": "^1.18.1", "helmet": "^5.1.1", "jsonwebtoken": "^9.0.2", "jwk-to-pem": "^2.0.5", "mixpanel": "^0.17.0", "ms": "^3.0.0-canary.1", + "openid-client": "^5.7.1", "passport": "^0.6.0", "passport-facebook": "^3.0.0", "passport-forcedotcom": "^0.2.0", @@ -71,6 +73,7 @@ }, "devDependencies": { "@types/express": "^4.17.17", + "@types/express-session": "^1.18.1", "@types/jest": "27.0.2", "@types/jsonwebtoken": "^8.5.9", "@types/jwk-to-pem": "^2.0.1", diff --git a/src/@types/express-session.d.ts b/src/@types/express-session.d.ts new file mode 100644 index 0000000..6531c85 --- /dev/null +++ b/src/@types/express-session.d.ts @@ -0,0 +1,8 @@ +import 'express-session'; + +declare module 'express-session' { + interface SessionData { + state: string | undefined; + code_verifier: string | undefined; + } +} \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index 46f1701..1f18744 100644 --- a/src/main.ts +++ b/src/main.ts @@ -8,6 +8,7 @@ import { AppModule } from './app.module'; import { writeFileSync } from 'fs'; import { execSync } from 'child_process'; import { INestApplication } from '@nestjs/common'; +import session from 'express-session'; async function bootstrap() { DadosferaLogger.setupLogger({ serviceName: 'maestro', @@ -25,6 +26,12 @@ async function bootstrap() { }, }); app.use(helmet()); + app.use(session({ + secret: process.env.SESSION_SECRET, + resave: false, + saveUninitialized: false, + + })); configureSwagger(app); await app.listen(3333); if (process.env.KILL_AFTER_START) await app.close(); diff --git a/src/modules/auth/auth.controller.ts b/src/modules/auth/auth.controller.ts index ed9d1fc..33c243b 100644 --- a/src/modules/auth/auth.controller.ts +++ b/src/modules/auth/auth.controller.ts @@ -27,6 +27,7 @@ import { AuthEnableTotpMfaRequest, AuthDisableTotpMfaRequest, AuthVerifyTotpMfaRequest, + IdProvider, } from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/messages'; import { PERMISSIONS_GROUPS } from 'src/authentication/permissions.enum'; @@ -52,7 +53,7 @@ import jwt from 'jsonwebtoken'; import { LanguageEnum } from 'src/utils/languages.enum'; import { Language } from 'src/decorators/language.decorator'; import { ApiInternalOnlyEndpoint } from 'src/decorators/swagger.decorator'; -import { CreateIdentityProvider } from './dtos/openid'; +import { CreatedIdentityProvider, CreateIdentityProvider } from './dtos/openid'; @ApiTags('Auth') @ApiHeaders([{ name: 'dadosfera-lang', enum: LanguageEnum, required: false }]) @@ -112,11 +113,10 @@ export class AuthController { @Post('/idp') @HttpCode(HttpStatus.OK) - @ApiOkResponse({ type: AuthRefreshAccessTokenRes }) - @RequireAllPermissions(PERMISSIONS_GROUPS.SNOWFLAKE.permissions.OPEN) + @ApiOkResponse({ type: CreatedIdentityProvider }) async addIdentityProvider( @User() user: RequestUser, - @Body() body: CreateIdentityProvider, + @Body() body: IdProvider, @Language() language: LanguageEnum, ) { this.logger.info('/auth - RefreshAccessToken'); @@ -129,6 +129,38 @@ export class AuthController { return this.authClient.createIdentityProvider(body, metadata); } + @Get('/idp/:name') + @HttpCode(HttpStatus.OK) + @Redirect() + async loginIdp( + @Body() body: { name: string }, + @Req() req: Request, + ) { + this.logger.info('/auth - RefreshAccessToken'); + + const redirectUrl = this.authClient.loginIdp(body.name, req); + return { + url: redirectUrl, + }; + } + + @Get('/idp/:name/callback') + @HttpCode(HttpStatus.OK) + @ApiOkResponse({ type: AuthRefreshAccessTokenRes }) + @Redirect() + @RequireAllPermissions(PERMISSIONS_GROUPS.SNOWFLAKE.permissions.OPEN) + async callbackIdp( + @Param('name') name: string, + @Req() req: Request, + ) { + this.logger.info('/auth - RefreshAccessToken'); + + const redirectUrl = this.authClient.authorizeIdp(name, req); + return { + url: redirectUrl, + }; + } + @ApiInternalOnlyEndpoint() @Post('/sso/snowflake') @RequireAllPermissions(PERMISSIONS_GROUPS.SNOWFLAKE.permissions.OPEN) diff --git a/src/modules/auth/auth.service.ts b/src/modules/auth/auth.service.ts index 098f369..1f15be6 100644 --- a/src/modules/auth/auth.service.ts +++ b/src/modules/auth/auth.service.ts @@ -17,14 +17,17 @@ import { AuthResetPasswordRequest, AuthVerifyResetPasswordCodeRequest, AuthConfirmResetPasswordRequest, - CreateIdentityProviderRequest, + IdProvider, } from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/messages'; import { DucClient } from '../duc/client.config'; import { Metadata } from '@grpc/grpc-js'; +import * as client from 'openid-client' +import { Request } from 'express'; -import { CreateIdentityProvider } from './dtos/openid'; @Injectable() export class AuthClientService implements OnModuleInit { + + logger: DadosferaLogger; @@ -177,9 +180,84 @@ export class AuthClientService implements OnModuleInit { ); } - createIdentityProvider(body: CreateIdentityProvider, metadata: Metadata) { + createIdentityProvider(body: IdProvider, metadata: Metadata) { return lastValueFrom( this.identityProviderService.CreateIdentityProvider(body, metadata), ); } + + + async loginIdp(name: string, req: Request) { + const idp = await lastValueFrom( + this.identityProviderService.FindIdentityProvider({ name }), + ); + + const config: client.Configuration = await client.discovery( + new URL(idp.issueUrl), + idp.clientId, + idp.clientSecret, + ) + + const redirect_uri = `http://localhost:3000/auth/idp/${name}/callback`; + const scope = 'openid email'; + + const code_verifier: string = client.randomPKCECodeVerifier() + const code_challenge: string = + await client.calculatePKCECodeChallenge(code_verifier) + let state: string; + const parameters: Record = { + redirect_uri, + scope, + code_challenge, + code_challenge_method: 'S256', + } + req.session.code_verifier = code_verifier; + + if (!config.serverMetadata().supportsPKCE()) { + /** + * We cannot be sure the server supports PKCE so we're going to use state too. + * Use of PKCE is backwards compatible even if the AS doesn't support it which + * is why we're using it regardless. Like PKCE, random state must be generated + * for every redirect to the authorization_endpoint. + */ + state = client.randomState() + parameters.state = state + req.session.state = state; + } + + const authUrl = client.buildAuthorizationUrl(config, parameters); + return authUrl; + } + + async authorizeIdp(name: string, req: Request) { + const code_verifier_session = req.session.code_verifier; + const state_session = req.session.state; + const url = new URL("http://localhost:3000" + req.url); + + const idp = await lastValueFrom( + this.identityProviderService.FindIdentityProvider({ name }), + ); + + const config: client.Configuration = await client.discovery( + new URL(idp.issueUrl), + idp.clientId, + idp.clientSecret, + ) + + const tokens: client.TokenEndpointResponse = await client.authorizationCodeGrant( + config, + url, + { + pkceCodeVerifier: code_verifier_session, + expectedState: state_session, + }, + ) + + delete req.session.code_verifier; + delete req.session.state; + + console.log('Token Endpoint Response', tokens) + + return tokens; + } } diff --git a/src/modules/auth/dtos/openid.ts b/src/modules/auth/dtos/openid.ts index 67a48d8..c5cc838 100644 --- a/src/modules/auth/dtos/openid.ts +++ b/src/modules/auth/dtos/openid.ts @@ -1,6 +1,10 @@ import { ApiProperty } from "@nestjs/swagger"; -export class CreateIdentityProvider { +export class CreatedIdentityProvider { + + @ApiProperty() + name: string; + @ApiProperty() clientId: string; From 4c94a51a9d3fbd8ad99e03753946add8d4e8c1ae Mon Sep 17 00:00:00 2001 From: marcos-silva-rodrigues Date: Thu, 3 Apr 2025 07:46:16 -0300 Subject: [PATCH 03/21] REFACTOR: openid module --- src/app.module.ts | 4 +- src/modules/auth/auth.controller.ts | 51 ------------ src/modules/auth/auth.service.ts | 83 +------------------ .../{auth/dtos => openid/dto}/openid.ts | 0 src/modules/openid/openid.controller.ts | 73 ++++++++++++++++ src/modules/openid/openid.module.ts | 15 ++++ src/modules/openid/openid.service.ts | 79 ++++++++++++++++++ 7 files changed, 171 insertions(+), 134 deletions(-) rename src/modules/{auth/dtos => openid/dto}/openid.ts (100%) create mode 100644 src/modules/openid/openid.controller.ts create mode 100644 src/modules/openid/openid.module.ts create mode 100644 src/modules/openid/openid.service.ts diff --git a/src/app.module.ts b/src/app.module.ts index 3e54fde..4229217 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -28,6 +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'; @Module({ providers: [ @@ -62,7 +63,8 @@ import { ThemeModule } from './modules/theme/theme.module'; OpenDataModule, ThemeModule, //Always leave HealthModule last, so it is on the bottom of swagger - HealthModule + HealthModule, + OpenidModule ], }) export class AppModule {} diff --git a/src/modules/auth/auth.controller.ts b/src/modules/auth/auth.controller.ts index 33c243b..c810d2b 100644 --- a/src/modules/auth/auth.controller.ts +++ b/src/modules/auth/auth.controller.ts @@ -53,7 +53,6 @@ import jwt from 'jsonwebtoken'; import { LanguageEnum } from 'src/utils/languages.enum'; import { Language } from 'src/decorators/language.decorator'; import { ApiInternalOnlyEndpoint } from 'src/decorators/swagger.decorator'; -import { CreatedIdentityProvider, CreateIdentityProvider } from './dtos/openid'; @ApiTags('Auth') @ApiHeaders([{ name: 'dadosfera-lang', enum: LanguageEnum, required: false }]) @@ -111,56 +110,6 @@ export class AuthController { return this.authClient.refreshAccessToken({ refreshToken }, metadata); } - @Post('/idp') - @HttpCode(HttpStatus.OK) - @ApiOkResponse({ type: CreatedIdentityProvider }) - async addIdentityProvider( - @User() user: RequestUser, - @Body() body: IdProvider, - @Language() language: LanguageEnum, - ) { - this.logger.info('/auth - RefreshAccessToken'); - - const metadata = PackTheMetadata({ - customer_id: user.customer_id, - language, - }); - - return this.authClient.createIdentityProvider(body, metadata); - } - - @Get('/idp/:name') - @HttpCode(HttpStatus.OK) - @Redirect() - async loginIdp( - @Body() body: { name: string }, - @Req() req: Request, - ) { - this.logger.info('/auth - RefreshAccessToken'); - - const redirectUrl = this.authClient.loginIdp(body.name, req); - return { - url: redirectUrl, - }; - } - - @Get('/idp/:name/callback') - @HttpCode(HttpStatus.OK) - @ApiOkResponse({ type: AuthRefreshAccessTokenRes }) - @Redirect() - @RequireAllPermissions(PERMISSIONS_GROUPS.SNOWFLAKE.permissions.OPEN) - async callbackIdp( - @Param('name') name: string, - @Req() req: Request, - ) { - this.logger.info('/auth - RefreshAccessToken'); - - const redirectUrl = this.authClient.authorizeIdp(name, req); - return { - url: redirectUrl, - }; - } - @ApiInternalOnlyEndpoint() @Post('/sso/snowflake') @RequireAllPermissions(PERMISSIONS_GROUPS.SNOWFLAKE.permissions.OPEN) diff --git a/src/modules/auth/auth.service.ts b/src/modules/auth/auth.service.ts index 1f15be6..f02404f 100644 --- a/src/modules/auth/auth.service.ts +++ b/src/modules/auth/auth.service.ts @@ -17,12 +17,10 @@ import { AuthResetPasswordRequest, AuthVerifyResetPasswordCodeRequest, AuthConfirmResetPasswordRequest, - IdProvider, } from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/messages'; import { DucClient } from '../duc/client.config'; import { Metadata } from '@grpc/grpc-js'; -import * as client from 'openid-client' -import { Request } from 'express'; + @Injectable() export class AuthClientService implements OnModuleInit { @@ -180,84 +178,5 @@ export class AuthClientService implements OnModuleInit { ); } - createIdentityProvider(body: IdProvider, metadata: Metadata) { - return lastValueFrom( - this.identityProviderService.CreateIdentityProvider(body, metadata), - ); - } - - async loginIdp(name: string, req: Request) { - const idp = await lastValueFrom( - this.identityProviderService.FindIdentityProvider({ name }), - ); - - const config: client.Configuration = await client.discovery( - new URL(idp.issueUrl), - idp.clientId, - idp.clientSecret, - ) - - const redirect_uri = `http://localhost:3000/auth/idp/${name}/callback`; - const scope = 'openid email'; - - const code_verifier: string = client.randomPKCECodeVerifier() - const code_challenge: string = - await client.calculatePKCECodeChallenge(code_verifier) - let state: string; - const parameters: Record = { - redirect_uri, - scope, - code_challenge, - code_challenge_method: 'S256', - } - req.session.code_verifier = code_verifier; - - if (!config.serverMetadata().supportsPKCE()) { - /** - * We cannot be sure the server supports PKCE so we're going to use state too. - * Use of PKCE is backwards compatible even if the AS doesn't support it which - * is why we're using it regardless. Like PKCE, random state must be generated - * for every redirect to the authorization_endpoint. - */ - state = client.randomState() - parameters.state = state - req.session.state = state; - } - - const authUrl = client.buildAuthorizationUrl(config, parameters); - return authUrl; - } - - async authorizeIdp(name: string, req: Request) { - const code_verifier_session = req.session.code_verifier; - const state_session = req.session.state; - const url = new URL("http://localhost:3000" + req.url); - - const idp = await lastValueFrom( - this.identityProviderService.FindIdentityProvider({ name }), - ); - - const config: client.Configuration = await client.discovery( - new URL(idp.issueUrl), - idp.clientId, - idp.clientSecret, - ) - - const tokens: client.TokenEndpointResponse = await client.authorizationCodeGrant( - config, - url, - { - pkceCodeVerifier: code_verifier_session, - expectedState: state_session, - }, - ) - - delete req.session.code_verifier; - delete req.session.state; - - console.log('Token Endpoint Response', tokens) - - return tokens; - } } diff --git a/src/modules/auth/dtos/openid.ts b/src/modules/openid/dto/openid.ts similarity index 100% rename from src/modules/auth/dtos/openid.ts rename to src/modules/openid/dto/openid.ts diff --git a/src/modules/openid/openid.controller.ts b/src/modules/openid/openid.controller.ts new file mode 100644 index 0000000..028834e --- /dev/null +++ b/src/modules/openid/openid.controller.ts @@ -0,0 +1,73 @@ +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.module.ts b/src/modules/openid/openid.module.ts new file mode 100644 index 0000000..ce242d9 --- /dev/null +++ b/src/modules/openid/openid.module.ts @@ -0,0 +1,15 @@ +import { Module } from '@nestjs/common'; +import { OpenidController } from './openid.controller'; +import { OpenidService } from './openid.service'; +import { ClientsModule } from '@nestjs/microservices'; +import { DucClient } from '../duc/client.config'; +import DadosferaLogger from '@dadosfera/dadosfera-logs'; + +const client = new DucClient(); + +@Module({ + imports: [ClientsModule.register([client.providerOptions])], + controllers: [OpenidController], + providers: [OpenidService, DadosferaLogger] +}) +export class OpenidModule {} diff --git a/src/modules/openid/openid.service.ts b/src/modules/openid/openid.service.ts new file mode 100644 index 0000000..10879aa --- /dev/null +++ b/src/modules/openid/openid.service.ts @@ -0,0 +1,79 @@ +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'], + }); + } +} From d5141e5b6ae36399984778ce6cb042115ffa6804 Mon Sep 17 00:00:00 2001 From: marcos-silva-rodrigues Date: Mon, 26 May 2025 13:37:25 -0300 Subject: [PATCH 04/21] 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'], - }); - } -} From 75e8b3bf4b028eb818052bf132d3924f35c977cd Mon Sep 17 00:00:00 2001 From: marcos-silva-rodrigues Date: Tue, 17 Jun 2025 14:58:35 -0300 Subject: [PATCH 05/21] FIX: correct dto name --- docsfera.json | 4 ++-- package-lock.json | 2 +- src/modules/identity-provider/dto/identity-provider.dto.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docsfera.json b/docsfera.json index d2c0e5f..cb5b5df 100644 --- a/docsfera.json +++ b/docsfera.json @@ -8891,7 +8891,7 @@ "clientSecret": { "type": "string" }, - "issueUrl": { + "issuerUrl": { "type": "string" } }, @@ -8899,7 +8899,7 @@ "name", "clientId", "clientSecret", - "issueUrl" + "issuerUrl" ] }, "IdentityProviderResponse": { diff --git a/package-lock.json b/package-lock.json index dd37be4..76217f4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1405,7 +1405,7 @@ "node_modules/@dadosfera/protospack-v2": { "version": "0.0.0", "resolved": "file:../protospack-v2/dadosfera-protospack-v2-0.0.0.tgz", - "integrity": "sha512-k5eDEeKuWeWaSrfymtiWtHP/u6bnZoldz9eDcFE86O54To6m8gbNM8ON4GhqOGLhbpWo0DZ6CoyPNCGhTt3Zqg==", + "integrity": "sha512-b8cBIXQAgEtrqLltJ5n4CycTH9Pm43VmQvoGL7PwyvErysXviWpwhtz5Qp8ELpfu5+rquQ2vRhqSaDmCFlGzew==", "license": "ISC", "dependencies": { "@grpc/grpc-js": "^1.9.3", diff --git a/src/modules/identity-provider/dto/identity-provider.dto.ts b/src/modules/identity-provider/dto/identity-provider.dto.ts index d64cfc9..803b6cd 100644 --- a/src/modules/identity-provider/dto/identity-provider.dto.ts +++ b/src/modules/identity-provider/dto/identity-provider.dto.ts @@ -12,7 +12,7 @@ export class CreateIdentityProvider { clientSecret: string; @ApiProperty() - issueUrl: string; + issuerUrl: string; } From a5c8de496d7a0dbdd21e3d3deb63fcccdbeaabaa Mon Sep 17 00:00:00 2001 From: marcos-silva-rodrigues Date: Tue, 24 Jun 2025 16:20:03 -0300 Subject: [PATCH 06/21] FEAT: map permission to identity provider --- docsfera.json | 58 ++++++++++++++++++- package-lock.json | 2 +- package.json | 2 +- .../dto/identity-provider.dto.ts | 9 +++ .../identity-provider.controller.ts | 38 ++++++------ .../identity-provider.service.ts | 48 ++++++++------- 6 files changed, 110 insertions(+), 47 deletions(-) diff --git a/docsfera.json b/docsfera.json index cb5b5df..7e631de 100644 --- a/docsfera.json +++ b/docsfera.json @@ -5923,6 +5923,46 @@ } } }, + "/identity-providers/{id}": { + "get": { + "operationId": "IdentityProviderController_loginIdp", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "" + } + } + } + }, + "/identity-providers/{name}/callback": { + "get": { + "operationId": "IdentityProviderController_callbackIdp", + "parameters": [ + { + "name": "name", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "" + } + } + } + }, "/network-policy": { "get": { "operationId": "NetworkPolicyController_getNetworks", @@ -8893,13 +8933,20 @@ }, "issuerUrl": { "type": "string" + }, + "permissions": { + "type": "array", + "items": { + "type": "number" + } } }, "required": [ "name", "clientId", "clientSecret", - "issuerUrl" + "issuerUrl", + "permissions" ] }, "IdentityProviderResponse": { @@ -8916,13 +8963,20 @@ }, "issueUrl": { "type": "string" + }, + "permissions": { + "type": "array", + "items": { + "type": "string" + } } }, "required": [ "id", "name", "clientId", - "issueUrl" + "issueUrl", + "permissions" ] }, "IdentityProviderListResponse": { diff --git a/package-lock.json b/package-lock.json index 76217f4..3634cd5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1405,7 +1405,7 @@ "node_modules/@dadosfera/protospack-v2": { "version": "0.0.0", "resolved": "file:../protospack-v2/dadosfera-protospack-v2-0.0.0.tgz", - "integrity": "sha512-b8cBIXQAgEtrqLltJ5n4CycTH9Pm43VmQvoGL7PwyvErysXviWpwhtz5Qp8ELpfu5+rquQ2vRhqSaDmCFlGzew==", + "integrity": "sha512-gT6JX/mh57cc/f9ey3ZPZ03fYKCqTj6CewazOnkf1juSAON65IR/9pD8lx/KMMcc3mYUT3Z3yjVAmXjvN09jPw==", "license": "ISC", "dependencies": { "@grpc/grpc-js": "^1.9.3", diff --git a/package.json b/package.json index c9451fd..d4710bc 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": "file:../protospack-v2/dadosfera-protospack-v2-0.0.0.tgz", + "@dadosfera/protospack-v2": "3.38.0-beta.4", "@grpc/grpc-js": "^1.9.3", "@grpc/proto-loader": "^0.7.9", "@nestjs/cli": "^9.5.0", diff --git a/src/modules/identity-provider/dto/identity-provider.dto.ts b/src/modules/identity-provider/dto/identity-provider.dto.ts index 803b6cd..8eff00c 100644 --- a/src/modules/identity-provider/dto/identity-provider.dto.ts +++ b/src/modules/identity-provider/dto/identity-provider.dto.ts @@ -13,6 +13,9 @@ export class CreateIdentityProvider { @ApiProperty() issuerUrl: string; + + @ApiProperty() + permissions: number[]; } @@ -29,6 +32,12 @@ export class IdentityProviderResponse { @ApiProperty() issueUrl: string; + + @ApiProperty() + permissions: { + id: number; + name: string; + }[]; } export class IdentityProviderListResponse { diff --git a/src/modules/identity-provider/identity-provider.controller.ts b/src/modules/identity-provider/identity-provider.controller.ts index 274ee9f..320d503 100644 --- a/src/modules/identity-provider/identity-provider.controller.ts +++ b/src/modules/identity-provider/identity-provider.controller.ts @@ -43,7 +43,7 @@ export class IdentityProviderController { this.logger.info('/openid - addIdentityProvider'); const metadata = PackTheMetadata({ - customer_id: user.customer_id, + ...user, language, }); @@ -66,24 +66,26 @@ export class IdentityProviderController { 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'); + @Get(':id') + @HttpCode(HttpStatus.OK) + @Redirect() + async loginIdp(@Param('id') id: string) { + this.logger.info('/openid - :name'); - // const redirectUrl = await this.openidService.loginIdp(name, req); - // return { - - // url: redirectUrl, - // }; - // } + const redirectUrl = await this.identityProviderService.loginIdentityProvider(id); + return { + url: redirectUrl, + }; + } - // @Get('/:name/callback') - // @HttpCode(HttpStatus.OK) - // async callbackIdp(@Param('name') name: string, @Req() req: Request) { - // this.logger.info('/openid - /:name/callback'); + @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); - // } + return { + name, + params: req.params + }; + } } diff --git a/src/modules/identity-provider/identity-provider.service.ts b/src/modules/identity-provider/identity-provider.service.ts index a29e198..6dc90d0 100644 --- a/src/modules/identity-provider/identity-provider.service.ts +++ b/src/modules/identity-provider/identity-provider.service.ts @@ -35,21 +35,19 @@ export class IdentityProviderService implements OnModuleInit { ); } - // async loginIdp(name: string, req: Request) { - // const client = await this.buildOpenIdClient(name); + async loginIdentityProvider(id: string) { + const client = await this.buildOpenIdClient(id); - // const code_verifier: string = generators.codeVerifier(); - // const code_challenge: string = generators.codeChallenge(code_verifier); + 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', - // }); - // } + 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); @@ -69,17 +67,17 @@ export class IdentityProviderService implements OnModuleInit { // return tokenSet.claims(); // } - // private async buildOpenIdClient(name: string) { - // const idp = await lastValueFrom( - // this.identityProviderService.FindIdentityProvider({ name }), - // ); + private async buildOpenIdClient(id: string) { + const idp = await lastValueFrom( + this.identityProviderService.FindIdentityProvider({ id }), + ); - // 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'], - // }); - // } + const issuer = await Issuer.discover(idp.issuerUrl); + return new issuer.Client({ + client_id: idp.clientId, + client_secret: idp.clientSecret, + redirect_uris: [`http://localhost:3333/auth/idp/${idp.name}/callback`], + response_types: ['code'], + }); + } } From 57e6e49c7c68d1c6e2e5d4d691cea445e39aa39a Mon Sep 17 00:00:00 2001 From: marcos-silva-rodrigues Date: Wed, 25 Jun 2025 10:28:26 -0300 Subject: [PATCH 07/21] FEAT: init url callback --- docsfera.json | 35 +++++++++++++++++++ src/modules/auth/auth.controller.ts | 18 ++++++++++ .../identity-provider.service.ts | 31 ++++++++-------- src/utils/errorCodes.ts | 4 +++ 4 files changed, 72 insertions(+), 16 deletions(-) diff --git a/docsfera.json b/docsfera.json index 7e631de..f34893a 100644 --- a/docsfera.json +++ b/docsfera.json @@ -675,6 +675,41 @@ ] } }, + "/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": "" + } + }, + "tags": [ + "Auth" + ] + } + }, "/connections": { "post": { "operationId": "ConnectionController_createConnection", diff --git a/src/modules/auth/auth.controller.ts b/src/modules/auth/auth.controller.ts index af3f053..5fab2cd 100644 --- a/src/modules/auth/auth.controller.ts +++ b/src/modules/auth/auth.controller.ts @@ -420,4 +420,22 @@ export class AuthController { return this.authClient.resetUsers(body.users, metadata); } + + @Get('/idp/:name/callback') + @HttpCode(HttpStatus.OK) + async callbackIdp(@Param('name') idpName: string, @Req() req: Request) { + this.logger.info('/auth/idp/:name/callback'); + + const { params } = req; + + if (!params || !params.code) { + this.logger.error('No code received from IDP'); + throw new Error(ErrorCodes.IDENTITY_PROVIDER.INVALID_RESPONSE); + } + + return { + ok: true + } + + } } diff --git a/src/modules/identity-provider/identity-provider.service.ts b/src/modules/identity-provider/identity-provider.service.ts index 6dc90d0..f34d0c9 100644 --- a/src/modules/identity-provider/identity-provider.service.ts +++ b/src/modules/identity-provider/identity-provider.service.ts @@ -36,17 +36,29 @@ export class IdentityProviderService implements OnModuleInit { } async loginIdentityProvider(id: string) { - const client = await this.buildOpenIdClient(id); + const idp = await lastValueFrom( + this.identityProviderService.FindIdentityProvider({ id }), + ); + + const issuer = await Issuer.discover(idp.issuerUrl); + const client = new issuer.Client({ + client_id: idp.clientId, + client_secret: idp.clientSecret, + redirect_uris: [`http://localhost:3333/auth/idp/${idp.name}/callback`], + response_types: ['code'], + }); const code_verifier: string = generators.codeVerifier(); const code_challenge: string = generators.codeChallenge(code_verifier); - return client.authorizationUrl({ - scope: 'openid email', + const url = client.authorizationUrl({ + scope: 'openid email profile', response_type: 'code', code_challenge, code_challenge_method: 'S256', }); + const idpUrl = url + '&identity_provider=' + idp.name + return idpUrl; } // async authorizeIdp(name: string, req: Request) { @@ -67,17 +79,4 @@ export class IdentityProviderService implements OnModuleInit { // return tokenSet.claims(); // } - private async buildOpenIdClient(id: string) { - const idp = await lastValueFrom( - this.identityProviderService.FindIdentityProvider({ id }), - ); - - const issuer = await Issuer.discover(idp.issuerUrl); - return new issuer.Client({ - client_id: idp.clientId, - client_secret: idp.clientSecret, - redirect_uris: [`http://localhost:3333/auth/idp/${idp.name}/callback`], - response_types: ['code'], - }); - } } diff --git a/src/utils/errorCodes.ts b/src/utils/errorCodes.ts index 1ba83e6..6d08b17 100644 --- a/src/utils/errorCodes.ts +++ b/src/utils/errorCodes.ts @@ -75,6 +75,9 @@ const CATALOG = { PREVIEW_TOO_BIG: 'CATALOG.PREVIEW_TOO_BIG', METADATA_TOO_BIG: 'CATALOG.METADATA_TOO_BIG', }; +const IDENTITY_PROVIDER = { + INVALID_RESPONSE: 'IDENTITY_PROVIDER.INVALID_RESPONSE', +} const ErrorCodes = { UNKNOWN: 'UNKNOWN', RATE_LIMIT: 'RATE_LIMIT', @@ -91,6 +94,7 @@ const ErrorCodes = { TRANSFORMATION, CONNECTION, CATALOG, + IDENTITY_PROVIDER }; export default ErrorCodes; From f726a9883fc47e37c00e50d6d8cf619fcb94fa5f Mon Sep 17 00:00:00 2001 From: marcos-silva-rodrigues Date: Fri, 27 Jun 2025 13:21:59 -0300 Subject: [PATCH 08/21] FEAT: create user from identity provider --- docsfera.json | 85 ++---- package-lock.json | 247 +++++++++++++++++- package.json | 6 +- src/modules/auth/auth.controller.ts | 17 -- .../identity-provider/dto/sso-signin.dto.ts | 9 + .../identity-provider.controller.ts | 36 ++- .../identity-provider.module.ts | 5 +- .../identity-provider.service.ts | 96 +++++-- src/services/cache.service.ts | 23 ++ src/services/service.module.ts | 22 ++ 10 files changed, 436 insertions(+), 110 deletions(-) create mode 100644 src/modules/identity-provider/dto/sso-signin.dto.ts create mode 100644 src/services/cache.service.ts create mode 100644 src/services/service.module.ts diff --git a/docsfera.json b/docsfera.json index f34893a..59a458b 100644 --- a/docsfera.json +++ b/docsfera.json @@ -675,41 +675,6 @@ ] } }, - "/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": "" - } - }, - "tags": [ - "Auth" - ] - } - }, "/connections": { "post": { "operationId": "ConnectionController_createConnection", @@ -5939,7 +5904,12 @@ } } } - } + }, + "security": [ + { + "access-token": [] + } + ] }, "get": { "operationId": "IdentityProviderController_getProviders", @@ -5955,6 +5925,29 @@ } } } + }, + "security": [ + { + "access-token": [] + } + ] + } + }, + "/identity-providers/callback": { + "get": { + "operationId": "IdentityProviderController_callbackIdp", + "parameters": [], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } } } }, @@ -5978,26 +5971,6 @@ } } }, - "/identity-providers/{name}/callback": { - "get": { - "operationId": "IdentityProviderController_callbackIdp", - "parameters": [ - { - "name": "name", - "required": true, - "in": "path", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "" - } - } - } - }, "/network-policy": { "get": { "operationId": "NetworkPolicyController_getNetworks", diff --git a/package-lock.json b/package-lock.json index 3634cd5..63acbdc 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": "file:../protospack-v2/dadosfera-protospack-v2-0.0.0.tgz", + "@dadosfera/protospack-v2": "3.38.0-beta.6", "@grpc/grpc-js": "^1.9.3", "@grpc/proto-loader": "^0.7.9", "@nestjs/cli": "^9.5.0", @@ -27,6 +27,8 @@ "@nestjs/swagger": "^6.3.0", "@nestjs/testing": "^9.4.3", "axios": "^0.27.2", + "cache-manager": "^5.1.4", + "cache-manager-ioredis-yet": "^1.1.0", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "cron-parser": "^4.9.0", @@ -48,12 +50,14 @@ "passport-hubspot-oauth2": "^1.0.3", "passport-mailchimp": "^1.1.0", "puppeteer": "^24.7.2", + "redis": "^4.5.1", "reflect-metadata": "^0.1.13", "rimraf": "^3.0.2", "rxjs": "^7.5.5", "swagger-ui-express": "^4.6.3" }, "devDependencies": { + "@types/cache-manager": "^4.0.6", "@types/express": "^4.17.17", "@types/express-session": "^1.18.1", "@types/jest": "27.0.2", @@ -1403,9 +1407,9 @@ } }, "node_modules/@dadosfera/protospack-v2": { - "version": "0.0.0", - "resolved": "file:../protospack-v2/dadosfera-protospack-v2-0.0.0.tgz", - "integrity": "sha512-gT6JX/mh57cc/f9ey3ZPZ03fYKCqTj6CewazOnkf1juSAON65IR/9pD8lx/KMMcc3mYUT3Z3yjVAmXjvN09jPw==", + "version": "3.38.0-beta.6", + "resolved": "https://dadosfera-611330257153.d.codeartifact.us-east-1.amazonaws.com/npm/dadosfera-npm/@dadosfera/protospack-v2/-/protospack-v2-3.38.0-beta.6.tgz", + "integrity": "sha512-nj60p/CCG8tTJ4xJCCsWJpoiiz6YqjFmKRhHlwWaMT2RXdOfhMwz1adNhbjaJAIlhXQ0rjb0wyZHzt5wAgtJvA==", "license": "ISC", "dependencies": { "@grpc/grpc-js": "^1.9.3", @@ -1603,6 +1607,12 @@ "dev": true, "license": "BSD-3-Clause" }, + "node_modules/@ioredis/commands": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz", + "integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==", + "license": "MIT" + }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -2972,6 +2982,65 @@ "node": ">=18" } }, + "node_modules/@redis/bloom": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.2.0.tgz", + "integrity": "sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==", + "license": "MIT", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/client": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.6.1.tgz", + "integrity": "sha512-/KCsg3xSlR+nCK8/8ZYSknYxvXHwubJrU82F3Lm1Fp6789VQ0/3RJKfsmRXjqfaTA++23CvC3hqmqe/2GEt6Kw==", + "license": "MIT", + "dependencies": { + "cluster-key-slot": "1.1.2", + "generic-pool": "3.9.0", + "yallist": "4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@redis/graph": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.1.1.tgz", + "integrity": "sha512-FEMTcTHZozZciLRl6GiiIB4zGm5z5F3F6a6FZCyrfxdKOhFlGkiAqlexWMBzCi4DcRoyiOsuLfW+cjlGWyExOw==", + "license": "MIT", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/json": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.7.tgz", + "integrity": "sha512-6UyXfjVaTBTJtKNG4/9Z8PSpKE6XgSyEb8iwaqDcy+uKrd/DGYHTWkUdnQDyzm727V7p21WUMhsqz5oy65kPcQ==", + "license": "MIT", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/search": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@redis/search/-/search-1.2.0.tgz", + "integrity": "sha512-tYoDBbtqOVigEDMAcTGsRlMycIIjwMCgD8eR2t0NANeQmgK/lvxNAvYyb6bZDD4frHRhIHkJu2TBRvB0ERkOmw==", + "license": "MIT", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/time-series": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.1.0.tgz", + "integrity": "sha512-c1Q99M5ljsIuc4YdaCwfUEXsofakb9c8+Zse2qxTadu8TalLXuAESzLvFAvNVbkmSlvlzIQOLpBCmWI9wTOt+g==", + "license": "MIT", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, "node_modules/@sinonjs/commons": { "version": "1.8.6", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz", @@ -3657,6 +3726,13 @@ "@types/node": "*" } }, + "node_modules/@types/cache-manager": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@types/cache-manager/-/cache-manager-4.0.6.tgz", + "integrity": "sha512-8qL93MF05/xrzFm/LSPtzNEOE1eQF3VwGHAcQEylgp5hDSTe41jtFwbSYAPfyYcVa28y1vYSjIt0c1fLLUiC/Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/connect": { "version": "3.4.38", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", @@ -5250,6 +5326,41 @@ "node": ">= 0.8" } }, + "node_modules/cache-manager": { + "version": "5.7.6", + "resolved": "https://registry.npmjs.org/cache-manager/-/cache-manager-5.7.6.tgz", + "integrity": "sha512-wBxnBHjDxF1RXpHCBD6HGvKER003Ts7IIm0CHpggliHzN1RZditb7rXoduE1rplc2DEFYKxhLKgFuchXMJje9w==", + "license": "MIT", + "dependencies": { + "eventemitter3": "^5.0.1", + "lodash.clonedeep": "^4.5.0", + "lru-cache": "^10.2.2", + "promise-coalesce": "^1.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/cache-manager-ioredis-yet": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cache-manager-ioredis-yet/-/cache-manager-ioredis-yet-1.2.2.tgz", + "integrity": "sha512-o03N/tQxfFONZ1XLGgIxOFHuQQpjpRdnSAL1THG1YWZIVp1JMUfjU3ElSAjFN1LjbJXa55IpC8waG+VEoLUCUw==", + "deprecated": "With cache-manager v6 we now are using Keyv", + "license": "MIT", + "dependencies": { + "cache-manager": "^5.2.3", + "ioredis": "^5.3.2" + }, + "engines": { + "node": ">= 16.17.0" + } + }, + "node_modules/cache-manager/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, "node_modules/call-bind": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", @@ -5527,6 +5638,15 @@ "node": ">=0.8" } }, + "node_modules/cluster-key-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", + "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -6040,6 +6160,15 @@ "node": ">=0.4.0" } }, + "node_modules/denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.10" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -6815,6 +6944,12 @@ "node": ">= 0.6" } }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "license": "MIT" + }, "node_modules/events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", @@ -7552,6 +7687,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/generic-pool": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.9.0.tgz", + "integrity": "sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -8127,6 +8271,30 @@ "node": ">= 0.10" } }, + "node_modules/ioredis": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.6.1.tgz", + "integrity": "sha512-UxC0Yv1Y4WRJiGQxQkP0hfdL0/5/6YvdfOOClRgJ0qppSarkhneSa6UvkMkms0AkdGimSH3Ikqm+6mkMmX7vGA==", + "license": "MIT", + "dependencies": { + "@ioredis/commands": "^1.1.1", + "cluster-key-slot": "^1.1.0", + "debug": "^4.3.4", + "denque": "^2.1.0", + "lodash.defaults": "^4.2.0", + "lodash.isarguments": "^3.1.0", + "redis-errors": "^1.2.0", + "redis-parser": "^3.0.0", + "standard-as-callback": "^2.1.0" + }, + "engines": { + "node": ">=12.22.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ioredis" + } + }, "node_modules/ip-address": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", @@ -9457,12 +9625,30 @@ "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", "license": "MIT" }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==", + "license": "MIT" + }, + "node_modules/lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", + "license": "MIT" + }, "node_modules/lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", "license": "MIT" }, + "node_modules/lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==", + "license": "MIT" + }, "node_modules/lodash.isboolean": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", @@ -10948,6 +11134,15 @@ "node": ">=0.4.0" } }, + "node_modules/promise-coalesce": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/promise-coalesce/-/promise-coalesce-1.1.2.tgz", + "integrity": "sha512-zLaJ9b8hnC564fnJH6NFSOGZYYdzrAJn2JUUIwzoQb32fG2QAakpDNM+CZo1km6keXkRXRM+hml1BFAPVnPkxg==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=16" + } + }, "node_modules/prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", @@ -11336,6 +11531,44 @@ "node": ">= 0.10" } }, + "node_modules/redis": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/redis/-/redis-4.7.1.tgz", + "integrity": "sha512-S1bJDnqLftzHXHP8JsT5II/CtHWQrASX5K96REjWjlmWKrviSOLWmM7QnRLstAWsu1VBBV1ffV6DzCvxNP0UJQ==", + "license": "MIT", + "workspaces": [ + "./packages/*" + ], + "dependencies": { + "@redis/bloom": "1.2.0", + "@redis/client": "1.6.1", + "@redis/graph": "1.1.1", + "@redis/json": "1.0.7", + "@redis/search": "1.2.0", + "@redis/time-series": "1.1.0" + } + }, + "node_modules/redis-errors": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", + "integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/redis-parser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz", + "integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==", + "license": "MIT", + "dependencies": { + "redis-errors": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/reflect-metadata": { "version": "0.1.14", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.14.tgz", @@ -12023,6 +12256,12 @@ "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==", "license": "MIT" }, + "node_modules/standard-as-callback": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz", + "integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==", + "license": "MIT" + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", diff --git a/package.json b/package.json index d4710bc..56f91f5 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.38.0-beta.4", + "@dadosfera/protospack-v2": "3.38.0-beta.6", "@grpc/grpc-js": "^1.9.3", "@grpc/proto-loader": "^0.7.9", "@nestjs/cli": "^9.5.0", @@ -45,6 +45,8 @@ "@nestjs/swagger": "^6.3.0", "@nestjs/testing": "^9.4.3", "axios": "^0.27.2", + "cache-manager": "^5.1.4", + "cache-manager-ioredis-yet": "^1.1.0", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "cron-parser": "^4.9.0", @@ -66,6 +68,7 @@ "passport-hubspot-oauth2": "^1.0.3", "passport-mailchimp": "^1.1.0", "puppeteer": "^24.7.2", + "redis": "^4.5.1", "reflect-metadata": "^0.1.13", "rimraf": "^3.0.2", "rxjs": "^7.5.5", @@ -75,6 +78,7 @@ "multer": "1.4.5-lts.1" }, "devDependencies": { + "@types/cache-manager": "^4.0.6", "@types/express": "^4.17.17", "@types/express-session": "^1.18.1", "@types/jest": "27.0.2", diff --git a/src/modules/auth/auth.controller.ts b/src/modules/auth/auth.controller.ts index 5fab2cd..8d8352e 100644 --- a/src/modules/auth/auth.controller.ts +++ b/src/modules/auth/auth.controller.ts @@ -421,21 +421,4 @@ export class AuthController { return this.authClient.resetUsers(body.users, metadata); } - @Get('/idp/:name/callback') - @HttpCode(HttpStatus.OK) - async callbackIdp(@Param('name') idpName: string, @Req() req: Request) { - this.logger.info('/auth/idp/:name/callback'); - - const { params } = req; - - if (!params || !params.code) { - this.logger.error('No code received from IDP'); - throw new Error(ErrorCodes.IDENTITY_PROVIDER.INVALID_RESPONSE); - } - - return { - ok: true - } - - } } diff --git a/src/modules/identity-provider/dto/sso-signin.dto.ts b/src/modules/identity-provider/dto/sso-signin.dto.ts new file mode 100644 index 0000000..d339a69 --- /dev/null +++ b/src/modules/identity-provider/dto/sso-signin.dto.ts @@ -0,0 +1,9 @@ +export class SsoSignInDto { + readonly nonce: string; + readonly codeVerifier: string; + readonly state: string; + readonly id: string; + readonly clientId: string; + readonly clientSecret: string; + readonly issuerUrl: string; +} \ 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 index 320d503..f92f377 100644 --- a/src/modules/identity-provider/identity-provider.controller.ts +++ b/src/modules/identity-provider/identity-provider.controller.ts @@ -19,6 +19,8 @@ import { PackTheMetadata } from 'src/utils/ PackTheMetadata'; import { ApiOkResponse } from '@nestjs/swagger'; import { CreateIdentityProvider, IdentityProviderListResponse, IdentityProviderResponse } from './dto/identity-provider.dto'; import { Request } from 'express'; +import ErrorCodes from 'src/utils/errorCodes'; +import { Authenticated } from 'src/decorators/authentication.decorator'; @Controller('identity-providers') export class IdentityProviderController { @@ -35,6 +37,7 @@ export class IdentityProviderController { @Post() @HttpCode(HttpStatus.OK) @ApiOkResponse({ type: IdentityProviderResponse }) + @Authenticated() async addIdentityProvider( @User() user: RequestUser, @Body() body: CreateIdentityProvider, @@ -53,6 +56,7 @@ export class IdentityProviderController { @Get() @HttpCode(HttpStatus.OK) @ApiOkResponse({ type: IdentityProviderListResponse }) + @Authenticated() async getProviders( @User() user: RequestUser, @Language() language: LanguageEnum, @@ -63,7 +67,27 @@ export class IdentityProviderController { language }) - return await this.identityProviderService.getList(metadata) + const result = await this.identityProviderService.getList(metadata) + return result + } + + @Get('/callback') + @HttpCode(HttpStatus.OK) + async callbackIdp(@Req() req: Request) { + this.logger.info('/callback'); + const { code, state } = req.query; + + if (!code) { + this.logger.error('No code received from IDP'); + throw new Error(ErrorCodes.IDENTITY_PROVIDER.INVALID_RESPONSE); + } + + if (!state) { + this.logger.error('No state received from IDP'); + throw new Error(ErrorCodes.IDENTITY_PROVIDER.INVALID_RESPONSE); + } + + return await this.identityProviderService.getTokenByIdp(req); } @Get(':id') @@ -78,14 +102,4 @@ export class IdentityProviderController { }; } - @Get('/:name/callback') - @HttpCode(HttpStatus.OK) - async callbackIdp(@Param('name') name: string, @Req() req: Request) { - this.logger.info('/openid - /:name/callback'); - - return { - name, - params: req.params - }; - } } diff --git a/src/modules/identity-provider/identity-provider.module.ts b/src/modules/identity-provider/identity-provider.module.ts index 7449ea4..97d6299 100644 --- a/src/modules/identity-provider/identity-provider.module.ts +++ b/src/modules/identity-provider/identity-provider.module.ts @@ -4,12 +4,13 @@ import { IdentityProviderService } from './identity-provider.service'; import { ClientsModule } from '@nestjs/microservices'; import { DucClient } from '../duc/client.config'; import DadosferaLogger from '@dadosfera/dadosfera-logs'; +import { ServicesModule } from 'src/services/service.module'; const client = new DucClient(); @Module({ - imports: [ClientsModule.register([client.providerOptions])], + imports: [ClientsModule.register([client.providerOptions]), ServicesModule], controllers: [IdentityProviderController], - providers: [IdentityProviderService, DadosferaLogger] + providers: [IdentityProviderService, DadosferaLogger], }) export class IdentityProviderModule {} diff --git a/src/modules/identity-provider/identity-provider.service.ts b/src/modules/identity-provider/identity-provider.service.ts index f34d0c9..f1dcf27 100644 --- a/src/modules/identity-provider/identity-provider.service.ts +++ b/src/modules/identity-provider/identity-provider.service.ts @@ -1,4 +1,4 @@ -import { Inject, Injectable, OnModuleInit } from '@nestjs/common'; +import { BadRequestException, 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'; @@ -7,6 +7,8 @@ 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 { SsoSignInDto } from './dto/sso-signin.dto'; +import { CacheService } from 'src/services/cache.service'; import { Request } from 'express'; @Injectable() @@ -14,6 +16,7 @@ export class IdentityProviderService implements OnModuleInit { private identityProviderService: IdentityProviderProtoService; constructor( @Inject(DucClient.name) private readonly grpcClient: ClientGrpc, + private readonly cacheService: CacheService ) {} onModuleInit() { @@ -37,46 +40,101 @@ export class IdentityProviderService implements OnModuleInit { async loginIdentityProvider(id: string) { const idp = await lastValueFrom( - this.identityProviderService.FindIdentityProvider({ id }), + this.identityProviderService.FindIdentityProvider({ id}), ); - const issuer = await Issuer.discover(idp.issuerUrl); + const redirectUri = this.getCallbackUrl(); + const cognitoIssuerURL = this.getCognitoIssuerUrl(); + + const issuer = await Issuer.discover(cognitoIssuerURL); const client = new issuer.Client({ client_id: idp.clientId, client_secret: idp.clientSecret, - redirect_uris: [`http://localhost:3333/auth/idp/${idp.name}/callback`], + redirect_uris: [redirectUri], response_types: ['code'], }); const code_verifier: string = generators.codeVerifier(); const code_challenge: string = generators.codeChallenge(code_verifier); + const state = generators.state(); + const nonce = generators.nonce(); + + // Using state because it is returned in the callback + // and we can use it to retrieve the code_verifier and nonce + await this.cacheService.set(state, { + codeVerifier: code_verifier, + nonce, + id: idp.id, + state, + clientId: idp.clientId, + clientSecret: idp.clientSecret, + issuerUrl: cognitoIssuerURL, + }); + const url = client.authorizationUrl({ - scope: 'openid email profile', + scope: 'openid email', response_type: 'code', code_challenge, code_challenge_method: 'S256', + state, + nonce }); const idpUrl = url + '&identity_provider=' + idp.name return idpUrl; } - // 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 }, - // ); + private getCallbackUrl() { + const AUTH_CALLBACK = process.env.AUTH_CALLBACK; + return AUTH_CALLBACK; + } - // delete req.session.code_verifier; + private getCognitoIssuerUrl() { + const COGNITO_ISSUER_URL = process.env.COGNITO_ISSUER_URL; + return COGNITO_ISSUER_URL; + } - // console.log('received and validated tokens %j', tokenSet); - // console.log('validated ID Token claims %j', tokenSet.claims()); + async getTokenByIdp(req: Request) { + const ssoSign = await this.cacheService.get(req.query.state as string); - // return tokenSet.claims(); - // } + if (!ssoSign) { + throw new BadRequestException('SSO sign-in is expired or not found'); + } + + const callbackUrl = this.getCallbackUrl(); + const issuer = await Issuer.discover(ssoSign.issuerUrl); + const client = new issuer.Client({ + client_id: ssoSign.clientId, + client_secret: ssoSign.clientSecret, + redirect_uris: [callbackUrl], + response_types: ['code'], + }); + + if (ssoSign === null) { + throw new BadRequestException('SSO sign-in is expired or not found'); + } + + const params = client.callbackParams(req) + const tokenSet = await client.callback( + callbackUrl, + params, + { + nonce: ssoSign.nonce, + code_verifier: ssoSign.codeVerifier, + state: ssoSign.state, + } + ); + + await this.cacheService.delete(ssoSign.state); + + return await lastValueFrom( + this.identityProviderService.SignInUser({ + accessToken: tokenSet.access_token, + idToken: tokenSet.id_token, + refreshToken: tokenSet.refresh_token, + id: ssoSign.id + }), + ); + } } diff --git a/src/services/cache.service.ts b/src/services/cache.service.ts new file mode 100644 index 0000000..25de25b --- /dev/null +++ b/src/services/cache.service.ts @@ -0,0 +1,23 @@ +import { CACHE_MANAGER, Inject, Injectable } from '@nestjs/common'; +import { Cache } from 'cache-manager'; + +@Injectable() +export class CacheService { + + constructor( + @Inject(CACHE_MANAGER) + private readonly cacheManager: Cache, + ) {} + + async get(key: string): Promise { + return await this.cacheManager.get(key); + } + + async set(key: string, value: T): Promise { + await this.cacheManager.set(key, value); + } + + async delete(key: string) { + await this.cacheManager.del(key); + } +} diff --git a/src/services/service.module.ts b/src/services/service.module.ts new file mode 100644 index 0000000..d129eaf --- /dev/null +++ b/src/services/service.module.ts @@ -0,0 +1,22 @@ +import { CacheModule, Module } from '@nestjs/common'; +import { CacheService } from './cache.service'; +import { redisStore } from 'cache-manager-ioredis-yet'; + +@Module({ + imports: [ + CacheModule.registerAsync({ + useFactory: async () => ({ + store: await redisStore({ + ttl: 1000 * 60, //1 minute + host: process.env.REDIS_HOST, + port: process.env.REDIS_PORT && Number(process.env.REDIS_PORT), + db: process.env.REDIS_DATABASE && Number(process.env.REDIS_DATABASE), + keyPrefix: 'maestro:sso', + }), + }), + }), + ], + providers: [CacheService], + exports: [CacheService], +}) +export class ServicesModule {} From d3040cd5a85fd0a2b907c4f5a259b22596aa2f7b Mon Sep 17 00:00:00 2001 From: marcos-silva-rodrigues Date: Thu, 3 Jul 2025 10:59:43 -0300 Subject: [PATCH 09/21] FEAT: update and delete identity provider --- docsfera.json | 131 +++++++++++++++--- package-lock.json | 8 +- package.json | 2 +- .../identity-provider.controller.ts | 73 ++++++++-- .../identity-provider.service.ts | 16 +++ 5 files changed, 194 insertions(+), 36 deletions(-) diff --git a/docsfera.json b/docsfera.json index 59a458b..b5a2c99 100644 --- a/docsfera.json +++ b/docsfera.json @@ -5906,6 +5906,12 @@ } }, "security": [ + { + "access-token": [] + }, + { + "access-token": [] + }, { "access-token": [] } @@ -5927,12 +5933,117 @@ } }, "security": [ + { + "access-token": [] + }, + { + "access-token": [] + }, { "access-token": [] } ] } }, + "/identity-providers/{id}": { + "delete": { + "operationId": "IdentityProviderController_deleteIdentityProvider", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "security": [ + { + "access-token": [] + }, + { + "access-token": [] + } + ] + }, + "put": { + "operationId": "IdentityProviderController_updateIdentityProviders", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateIdentityProvider" + } + } + } + }, + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "security": [ + { + "access-token": [] + }, + { + "access-token": [] + }, + { + "access-token": [] + } + ] + }, + "get": { + "operationId": "IdentityProviderController_loginIdp", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "" + } + } + } + }, "/identity-providers/callback": { "get": { "operationId": "IdentityProviderController_callbackIdp", @@ -5951,26 +6062,6 @@ } } }, - "/identity-providers/{id}": { - "get": { - "operationId": "IdentityProviderController_loginIdp", - "parameters": [ - { - "name": "id", - "required": true, - "in": "path", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "" - } - } - } - }, "/network-policy": { "get": { "operationId": "NetworkPolicyController_getNetworks", diff --git a/package-lock.json b/package-lock.json index 63acbdc..e6aa1e6 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.38.0-beta.6", + "@dadosfera/protospack-v2": "3.38.0-beta.7", "@grpc/grpc-js": "^1.9.3", "@grpc/proto-loader": "^0.7.9", "@nestjs/cli": "^9.5.0", @@ -1407,9 +1407,9 @@ } }, "node_modules/@dadosfera/protospack-v2": { - "version": "3.38.0-beta.6", - "resolved": "https://dadosfera-611330257153.d.codeartifact.us-east-1.amazonaws.com/npm/dadosfera-npm/@dadosfera/protospack-v2/-/protospack-v2-3.38.0-beta.6.tgz", - "integrity": "sha512-nj60p/CCG8tTJ4xJCCsWJpoiiz6YqjFmKRhHlwWaMT2RXdOfhMwz1adNhbjaJAIlhXQ0rjb0wyZHzt5wAgtJvA==", + "version": "3.38.0-beta.7", + "resolved": "https://dadosfera-611330257153.d.codeartifact.us-east-1.amazonaws.com/npm/dadosfera-npm/@dadosfera/protospack-v2/-/protospack-v2-3.38.0-beta.7.tgz", + "integrity": "sha512-Lp+hW/F/F9gMtrwBJCpFS8ekVfpEP4av8u8OQKSnTz8Cxc7MXC7OxD1BdKi0m05GuVpWEa5aCe+ZLWq0mhhjDw==", "license": "ISC", "dependencies": { "@grpc/grpc-js": "^1.9.3", diff --git a/package.json b/package.json index 56f91f5..e7aae5b 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.38.0-beta.6", + "@dadosfera/protospack-v2": "3.38.0-beta.7", "@grpc/grpc-js": "^1.9.3", "@grpc/proto-loader": "^0.7.9", "@nestjs/cli": "^9.5.0", diff --git a/src/modules/identity-provider/identity-provider.controller.ts b/src/modules/identity-provider/identity-provider.controller.ts index f92f377..8fe2a6a 100644 --- a/src/modules/identity-provider/identity-provider.controller.ts +++ b/src/modules/identity-provider/identity-provider.controller.ts @@ -2,12 +2,15 @@ import DadosferaLogger from '@dadosfera/dadosfera-logs'; import { Body, Controller, + Delete, Get, HttpCode, HttpStatus, Inject, Param, + Patch, Post, + Put, Redirect, Req, } from '@nestjs/common'; @@ -17,10 +20,15 @@ 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 { + CreateIdentityProvider, + IdentityProviderListResponse, + IdentityProviderResponse, +} from './dto/identity-provider.dto'; import { Request } from 'express'; import ErrorCodes from 'src/utils/errorCodes'; -import { Authenticated } from 'src/decorators/authentication.decorator'; +import { Authenticated, RequireModule, RequireSomePermission } from 'src/decorators/authentication.decorator'; +import { PERMISSIONS_GROUPS } from 'src/authentication/permissions.enum'; @Controller('identity-providers') export class IdentityProviderController { @@ -38,12 +46,14 @@ export class IdentityProviderController { @HttpCode(HttpStatus.OK) @ApiOkResponse({ type: IdentityProviderResponse }) @Authenticated() + @RequireModule('sso') + @RequireSomePermission(PERMISSIONS_GROUPS.USERS.permissions.ADMIN) async addIdentityProvider( @User() user: RequestUser, @Body() body: CreateIdentityProvider, @Language() language: LanguageEnum, ) { - this.logger.info('/openid - addIdentityProvider'); + this.logger.info('POST /identity-providers'); const metadata = PackTheMetadata({ ...user, @@ -57,6 +67,8 @@ export class IdentityProviderController { @HttpCode(HttpStatus.OK) @ApiOkResponse({ type: IdentityProviderListResponse }) @Authenticated() + @RequireModule('sso') + @RequireSomePermission(PERMISSIONS_GROUPS.USERS.permissions.ADMIN) async getProviders( @User() user: RequestUser, @Language() language: LanguageEnum, @@ -64,17 +76,56 @@ export class IdentityProviderController { this.logger.info('GET identity-providers'); const metadata = PackTheMetadata({ ...user, - language - }) + language, + }); - const result = await this.identityProviderService.getList(metadata) - return result + const result = await this.identityProviderService.getList(metadata); + return result; + } + + @Delete(':id') + @HttpCode(HttpStatus.NO_CONTENT) + @RequireModule('sso') + @RequireSomePermission(PERMISSIONS_GROUPS.USERS.permissions.ADMIN) + async deleteIdentityProvider( + @Param('id') id: string, + @User() user: RequestUser, + ) { + this.logger.info('DELETE /identity-providers'); + + const metadata = PackTheMetadata({ + ...user, + }); + + return await this.identityProviderService.deleteIdentityProvider( + id, + metadata, + ); + } + + @Put(':id') + @HttpCode(HttpStatus.OK) + @Authenticated() + @RequireModule('sso') + @RequireSomePermission(PERMISSIONS_GROUPS.USERS.permissions.ADMIN) + async updateIdentityProviders( + @Param('id') id: string, + @Body() body: CreateIdentityProvider, + @User() user: RequestUser, + ) { + this.logger.info('PUT /identity-providers'); + + const metadata = PackTheMetadata({ + ...user, + }); + + return await this.identityProviderService.updateIdentityProviders(id, body, metadata); } @Get('/callback') @HttpCode(HttpStatus.OK) async callbackIdp(@Req() req: Request) { - this.logger.info('/callback'); + this.logger.info('GET /identity-providers/callback'); const { code, state } = req.query; if (!code) { @@ -94,12 +145,12 @@ export class IdentityProviderController { @HttpCode(HttpStatus.OK) @Redirect() async loginIdp(@Param('id') id: string) { - this.logger.info('/openid - :name'); + this.logger.info('GET /identity-providers/:id'); - const redirectUrl = await this.identityProviderService.loginIdentityProvider(id); + const redirectUrl = + await this.identityProviderService.loginIdentityProvider(id); return { url: redirectUrl, }; } - } diff --git a/src/modules/identity-provider/identity-provider.service.ts b/src/modules/identity-provider/identity-provider.service.ts index f1dcf27..ca964ae 100644 --- a/src/modules/identity-provider/identity-provider.service.ts +++ b/src/modules/identity-provider/identity-provider.service.ts @@ -10,6 +10,7 @@ import { Issuer, generators } from 'openid-client'; import { SsoSignInDto } from './dto/sso-signin.dto'; import { CacheService } from 'src/services/cache.service'; import { Request } from 'express'; +import { CreateIdentityProvider } from './dto/identity-provider.dto'; @Injectable() export class IdentityProviderService implements OnModuleInit { @@ -137,4 +138,19 @@ export class IdentityProviderService implements OnModuleInit { ); } + async deleteIdentityProvider(id: string, metadata: Metadata) { + return await lastValueFrom( + this.identityProviderService.DeleteIdentityProvider({ id }, metadata), + ); + } + + async updateIdentityProviders(id: string, body: CreateIdentityProvider, metadata: Metadata) { + return await lastValueFrom( + this.identityProviderService.UpdateIdentityProvider({ + id, + ...body, + }, metadata), + ); + } + } From cbe5556ac9c06ef190b185333029e91c298b40e8 Mon Sep 17 00:00:00 2001 From: marcos-silva-rodrigues Date: Mon, 14 Jul 2025 10:51:36 -0300 Subject: [PATCH 10/21] FIX: auth callback --- docsfera.json | 2 +- .../identity-provider/identity-provider.controller.ts | 11 +++++++---- .../identity-provider/identity-provider.service.ts | 6 +++--- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/docsfera.json b/docsfera.json index b5a2c99..2f09773 100644 --- a/docsfera.json +++ b/docsfera.json @@ -6045,7 +6045,7 @@ } }, "/identity-providers/callback": { - "get": { + "post": { "operationId": "IdentityProviderController_callbackIdp", "parameters": [], "responses": { diff --git a/src/modules/identity-provider/identity-provider.controller.ts b/src/modules/identity-provider/identity-provider.controller.ts index 8fe2a6a..cc8bc9b 100644 --- a/src/modules/identity-provider/identity-provider.controller.ts +++ b/src/modules/identity-provider/identity-provider.controller.ts @@ -122,11 +122,14 @@ export class IdentityProviderController { return await this.identityProviderService.updateIdentityProviders(id, body, metadata); } - @Get('/callback') + @Post('/callback') @HttpCode(HttpStatus.OK) - async callbackIdp(@Req() req: Request) { + async callbackIdp(@Req() req: Request, @Body() body: { + state: string, + code: string + }) { this.logger.info('GET /identity-providers/callback'); - const { code, state } = req.query; + const { code, state } = body; if (!code) { this.logger.error('No code received from IDP'); @@ -138,7 +141,7 @@ export class IdentityProviderController { throw new Error(ErrorCodes.IDENTITY_PROVIDER.INVALID_RESPONSE); } - return await this.identityProviderService.getTokenByIdp(req); + return await this.identityProviderService.getTokenByIdp(code, state); } @Get(':id') diff --git a/src/modules/identity-provider/identity-provider.service.ts b/src/modules/identity-provider/identity-provider.service.ts index ca964ae..28eb5fa 100644 --- a/src/modules/identity-provider/identity-provider.service.ts +++ b/src/modules/identity-provider/identity-provider.service.ts @@ -95,8 +95,8 @@ export class IdentityProviderService implements OnModuleInit { return COGNITO_ISSUER_URL; } - async getTokenByIdp(req: Request) { - const ssoSign = await this.cacheService.get(req.query.state as string); + async getTokenByIdp(code: string, state: string) { + const ssoSign = await this.cacheService.get(state); if (!ssoSign) { throw new BadRequestException('SSO sign-in is expired or not found'); @@ -115,7 +115,7 @@ export class IdentityProviderService implements OnModuleInit { throw new BadRequestException('SSO sign-in is expired or not found'); } - const params = client.callbackParams(req) + const params = client.callbackParams(`${callbackUrl}?code=${code}&state=${state}`); const tokenSet = await client.callback( callbackUrl, params, From 8cb7144ff47944bca8bc4ce79f1b933e2ddf016a Mon Sep 17 00:00:00 2001 From: marcos-silva-rodrigues Date: Mon, 14 Jul 2025 15:14:25 -0300 Subject: [PATCH 11/21] FIX: send accessToken in refresh token endpoint --- src/modules/auth/auth.controller.ts | 4 ++-- src/modules/auth/auth.service.ts | 4 ++-- src/modules/auth/dtos/login.ts | 2 ++ 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/modules/auth/auth.controller.ts b/src/modules/auth/auth.controller.ts index 8d8352e..649adf5 100644 --- a/src/modules/auth/auth.controller.ts +++ b/src/modules/auth/auth.controller.ts @@ -104,7 +104,7 @@ export class AuthController { @Headers('origin') origin: string, ) { this.logger.info('/auth - RefreshAccessToken'); - const { refreshToken, customerName: customer_name } = body; + const { refreshToken, customerName: customer_name, accessToken } = body; const frontHost = origin.replace(/^https?:\/\//, ''); const metadata = PackTheMetadata({ language, @@ -112,7 +112,7 @@ export class AuthController { custom_host: frontHost, }); - return this.authClient.refreshAccessToken({ refreshToken }, metadata); + return this.authClient.refreshAccessToken({ refreshToken, accessToken }, metadata); } @ApiInternalOnlyEndpoint() diff --git a/src/modules/auth/auth.service.ts b/src/modules/auth/auth.service.ts index e8ff70f..38e2a95 100644 --- a/src/modules/auth/auth.service.ts +++ b/src/modules/auth/auth.service.ts @@ -74,13 +74,13 @@ export class AuthClientService implements OnModuleInit { } async refreshAccessToken( - { refreshToken }: AuthRefreshAccessTokenRequest, + { refreshToken, accessToken }: AuthRefreshAccessTokenRequest, metadata: Metadata, ) { this.logger.info('RefreshAccessToken'); return lastValueFrom( - this.authService.AuthRefreshAccessToken({ refreshToken }, metadata), + this.authService.AuthRefreshAccessToken({ refreshToken, accessToken }, metadata), ); } diff --git a/src/modules/auth/dtos/login.ts b/src/modules/auth/dtos/login.ts index 0cd90ae..34807e3 100644 --- a/src/modules/auth/dtos/login.ts +++ b/src/modules/auth/dtos/login.ts @@ -122,6 +122,8 @@ export class AuthRefreshAccessTokenReq { @ApiProperty() refreshToken: string; @ApiProperty() + accessToken: string; + @ApiProperty() customerName: string; } export class AuthRefreshAccessTokenRes { From c9bfe68ea5676b83beeeb0e93f19a1381aec23c9 Mon Sep 17 00:00:00 2001 From: marcos-silva-rodrigues Date: Mon, 14 Jul 2025 15:16:19 -0300 Subject: [PATCH 12/21] FEAT: list identity providers link for sso --- docsfera.json | 22 +++++++++++++++++++ package-lock.json | 8 +++---- package.json | 2 +- .../identity-provider.controller.ts | 16 ++++++++++++++ .../identity-provider.service.ts | 6 +++++ src/utils/errorCodes.ts | 1 + 6 files changed, 50 insertions(+), 5 deletions(-) diff --git a/docsfera.json b/docsfera.json index 2f09773..0bff3cc 100644 --- a/docsfera.json +++ b/docsfera.json @@ -6062,6 +6062,24 @@ } } }, + "/identity-providers/links": { + "get": { + "operationId": "IdentityProviderController_providerLinks", + "parameters": [], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + } + }, "/network-policy": { "get": { "operationId": "NetworkPolicyController_getNetworks", @@ -6370,12 +6388,16 @@ "refreshToken": { "type": "string" }, + "accessToken": { + "type": "string" + }, "customerName": { "type": "string" } }, "required": [ "refreshToken", + "accessToken", "customerName" ] }, diff --git a/package-lock.json b/package-lock.json index e6aa1e6..5067f64 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.38.0-beta.7", + "@dadosfera/protospack-v2": "3.38.0-beta.8", "@grpc/grpc-js": "^1.9.3", "@grpc/proto-loader": "^0.7.9", "@nestjs/cli": "^9.5.0", @@ -1407,9 +1407,9 @@ } }, "node_modules/@dadosfera/protospack-v2": { - "version": "3.38.0-beta.7", - "resolved": "https://dadosfera-611330257153.d.codeartifact.us-east-1.amazonaws.com/npm/dadosfera-npm/@dadosfera/protospack-v2/-/protospack-v2-3.38.0-beta.7.tgz", - "integrity": "sha512-Lp+hW/F/F9gMtrwBJCpFS8ekVfpEP4av8u8OQKSnTz8Cxc7MXC7OxD1BdKi0m05GuVpWEa5aCe+ZLWq0mhhjDw==", + "version": "3.38.0-beta.8", + "resolved": "https://dadosfera-611330257153.d.codeartifact.us-east-1.amazonaws.com/npm/dadosfera-npm/@dadosfera/protospack-v2/-/protospack-v2-3.38.0-beta.8.tgz", + "integrity": "sha512-8hEJnfFtKKLn7DqvkOi27GpXZkin6PodTvFbQJIplzcsxuurWNmtvWDmNRavm/SYJcotv4kTJkxnCPOKJqYYpA==", "license": "ISC", "dependencies": { "@grpc/grpc-js": "^1.9.3", diff --git a/package.json b/package.json index e7aae5b..e142271 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.38.0-beta.7", + "@dadosfera/protospack-v2": "3.38.0-beta.8", "@grpc/grpc-js": "^1.9.3", "@grpc/proto-loader": "^0.7.9", "@nestjs/cli": "^9.5.0", diff --git a/src/modules/identity-provider/identity-provider.controller.ts b/src/modules/identity-provider/identity-provider.controller.ts index cc8bc9b..75807d8 100644 --- a/src/modules/identity-provider/identity-provider.controller.ts +++ b/src/modules/identity-provider/identity-provider.controller.ts @@ -144,6 +144,21 @@ export class IdentityProviderController { return await this.identityProviderService.getTokenByIdp(code, state); } + @Get('/links') + @HttpCode(HttpStatus.OK) + async providerLinks(@Req() req: Request) { + this.logger.info('GET /identity-providers/links'); + + const frontDomain = req.headers['origin'] as string; + + if (!frontDomain) { + throw new Error(ErrorCodes.IDENTITY_PROVIDER.INVALID_HEADER); + } + + const result = await this.identityProviderService.identityProvidersLinksPerDomain(frontDomain); + return result; + } + @Get(':id') @HttpCode(HttpStatus.OK) @Redirect() @@ -156,4 +171,5 @@ export class IdentityProviderController { url: redirectUrl, }; } + } diff --git a/src/modules/identity-provider/identity-provider.service.ts b/src/modules/identity-provider/identity-provider.service.ts index 28eb5fa..22cf673 100644 --- a/src/modules/identity-provider/identity-provider.service.ts +++ b/src/modules/identity-provider/identity-provider.service.ts @@ -153,4 +153,10 @@ export class IdentityProviderService implements OnModuleInit { ); } + async identityProvidersLinksPerDomain(frontDomain: string) { + return await lastValueFrom( + this.identityProviderService.GetProviderLinksFromDomain({ frontDomain }), + ); + } + } diff --git a/src/utils/errorCodes.ts b/src/utils/errorCodes.ts index 6d08b17..a5856bd 100644 --- a/src/utils/errorCodes.ts +++ b/src/utils/errorCodes.ts @@ -77,6 +77,7 @@ const CATALOG = { }; const IDENTITY_PROVIDER = { INVALID_RESPONSE: 'IDENTITY_PROVIDER.INVALID_RESPONSE', + INVALID_HEADER: 'IDENTITY_PROVIDER.INVALID_HEADER', } const ErrorCodes = { UNKNOWN: 'UNKNOWN', From 42a39ee891e2e07bbed31c5063c4b205f54122e2 Mon Sep 17 00:00:00 2001 From: marcos-silva-rodrigues Date: Tue, 15 Jul 2025 16:21:44 -0300 Subject: [PATCH 13/21] FEAT: send user id to refresh token endpoint --- docsfera.json | 8 +-- package-lock.json | 8 +-- package.json | 2 +- src/modules/auth/auth.controller.ts | 5 +- src/modules/auth/auth.service.ts | 4 +- src/modules/auth/dtos/login.ts | 4 +- .../identity-provider.service.ts | 71 +++++++++++-------- 7 files changed, 54 insertions(+), 48 deletions(-) diff --git a/docsfera.json b/docsfera.json index 0bff3cc..a3c2b47 100644 --- a/docsfera.json +++ b/docsfera.json @@ -6388,17 +6388,13 @@ "refreshToken": { "type": "string" }, - "accessToken": { - "type": "string" - }, - "customerName": { + "userId": { "type": "string" } }, "required": [ "refreshToken", - "accessToken", - "customerName" + "userId" ] }, "AuthRefreshAccessTokenRes": { diff --git a/package-lock.json b/package-lock.json index 5067f64..bfc70d3 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.38.0-beta.8", + "@dadosfera/protospack-v2": "3.38.0-beta.9", "@grpc/grpc-js": "^1.9.3", "@grpc/proto-loader": "^0.7.9", "@nestjs/cli": "^9.5.0", @@ -1407,9 +1407,9 @@ } }, "node_modules/@dadosfera/protospack-v2": { - "version": "3.38.0-beta.8", - "resolved": "https://dadosfera-611330257153.d.codeartifact.us-east-1.amazonaws.com/npm/dadosfera-npm/@dadosfera/protospack-v2/-/protospack-v2-3.38.0-beta.8.tgz", - "integrity": "sha512-8hEJnfFtKKLn7DqvkOi27GpXZkin6PodTvFbQJIplzcsxuurWNmtvWDmNRavm/SYJcotv4kTJkxnCPOKJqYYpA==", + "version": "3.38.0-beta.9", + "resolved": "https://dadosfera-611330257153.d.codeartifact.us-east-1.amazonaws.com/npm/dadosfera-npm/@dadosfera/protospack-v2/-/protospack-v2-3.38.0-beta.9.tgz", + "integrity": "sha512-9rCSI3g/VVysJUoZ7SdaVe+/UOZUC4qwlnKntf7YxVhb5yVGS3D9U/TjLvjI8zVe5gQ3f711GrhXUgrTGYU/2g==", "license": "ISC", "dependencies": { "@grpc/grpc-js": "^1.9.3", diff --git a/package.json b/package.json index e142271..9b77603 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.38.0-beta.8", + "@dadosfera/protospack-v2": "3.38.0-beta.9", "@grpc/grpc-js": "^1.9.3", "@grpc/proto-loader": "^0.7.9", "@nestjs/cli": "^9.5.0", diff --git a/src/modules/auth/auth.controller.ts b/src/modules/auth/auth.controller.ts index 649adf5..4a46d47 100644 --- a/src/modules/auth/auth.controller.ts +++ b/src/modules/auth/auth.controller.ts @@ -104,15 +104,14 @@ export class AuthController { @Headers('origin') origin: string, ) { this.logger.info('/auth - RefreshAccessToken'); - const { refreshToken, customerName: customer_name, accessToken } = body; + const { refreshToken, userId } = body; const frontHost = origin.replace(/^https?:\/\//, ''); const metadata = PackTheMetadata({ language, - customer_name, custom_host: frontHost, }); - return this.authClient.refreshAccessToken({ refreshToken, accessToken }, metadata); + return this.authClient.refreshAccessToken({ refreshToken, userId }, metadata); } @ApiInternalOnlyEndpoint() diff --git a/src/modules/auth/auth.service.ts b/src/modules/auth/auth.service.ts index 38e2a95..8b1b4f2 100644 --- a/src/modules/auth/auth.service.ts +++ b/src/modules/auth/auth.service.ts @@ -74,13 +74,13 @@ export class AuthClientService implements OnModuleInit { } async refreshAccessToken( - { refreshToken, accessToken }: AuthRefreshAccessTokenRequest, + { refreshToken, userId }: AuthRefreshAccessTokenRequest, metadata: Metadata, ) { this.logger.info('RefreshAccessToken'); return lastValueFrom( - this.authService.AuthRefreshAccessToken({ refreshToken, accessToken }, metadata), + this.authService.AuthRefreshAccessToken({ refreshToken, userId }, metadata), ); } diff --git a/src/modules/auth/dtos/login.ts b/src/modules/auth/dtos/login.ts index 34807e3..dc57d54 100644 --- a/src/modules/auth/dtos/login.ts +++ b/src/modules/auth/dtos/login.ts @@ -122,9 +122,7 @@ export class AuthRefreshAccessTokenReq { @ApiProperty() refreshToken: string; @ApiProperty() - accessToken: string; - @ApiProperty() - customerName: string; + userId: string; } export class AuthRefreshAccessTokenRes { @ApiProperty() diff --git a/src/modules/identity-provider/identity-provider.service.ts b/src/modules/identity-provider/identity-provider.service.ts index 22cf673..8e22c43 100644 --- a/src/modules/identity-provider/identity-provider.service.ts +++ b/src/modules/identity-provider/identity-provider.service.ts @@ -1,4 +1,9 @@ -import { BadRequestException, Inject, Injectable, OnModuleInit } from '@nestjs/common'; +import { + BadRequestException, + 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'; @@ -17,7 +22,7 @@ export class IdentityProviderService implements OnModuleInit { private identityProviderService: IdentityProviderProtoService; constructor( @Inject(DucClient.name) private readonly grpcClient: ClientGrpc, - private readonly cacheService: CacheService + private readonly cacheService: CacheService, ) {} onModuleInit() { @@ -41,7 +46,7 @@ export class IdentityProviderService implements OnModuleInit { async loginIdentityProvider(id: string) { const idp = await lastValueFrom( - this.identityProviderService.FindIdentityProvider({ id}), + this.identityProviderService.FindIdentityProvider({ id }), ); const redirectUri = this.getCallbackUrl(); @@ -79,9 +84,9 @@ export class IdentityProviderService implements OnModuleInit { code_challenge, code_challenge_method: 'S256', state, - nonce + nonce, }); - const idpUrl = url + '&identity_provider=' + idp.name + const idpUrl = url + '&identity_provider=' + idp.name; return idpUrl; } @@ -108,34 +113,36 @@ export class IdentityProviderService implements OnModuleInit { client_id: ssoSign.clientId, client_secret: ssoSign.clientSecret, redirect_uris: [callbackUrl], - response_types: ['code'], }); if (ssoSign === null) { throw new BadRequestException('SSO sign-in is expired or not found'); } - const params = client.callbackParams(`${callbackUrl}?code=${code}&state=${state}`); - const tokenSet = await client.callback( - callbackUrl, - params, - { + const params = client.callbackParams( + `${callbackUrl}?code=${code}&state=${state}`, + ); + try { + const tokenSet = await client.callback(callbackUrl, params, { nonce: ssoSign.nonce, code_verifier: ssoSign.codeVerifier, - state: ssoSign.state, - } - ); + state: ssoSign.state + }); - await this.cacheService.delete(ssoSign.state); + await this.cacheService.delete(ssoSign.state); - return await lastValueFrom( - this.identityProviderService.SignInUser({ - accessToken: tokenSet.access_token, - idToken: tokenSet.id_token, - refreshToken: tokenSet.refresh_token, - id: ssoSign.id - }), - ); + return await lastValueFrom( + this.identityProviderService.SignInUser({ + accessToken: tokenSet.access_token, + idToken: tokenSet.id_token, + refreshToken: tokenSet.refresh_token, + id: ssoSign.id, + }), + ); + } catch (error) { + console.log(error); + throw error; + } } async deleteIdentityProvider(id: string, metadata: Metadata) { @@ -144,12 +151,19 @@ export class IdentityProviderService implements OnModuleInit { ); } - async updateIdentityProviders(id: string, body: CreateIdentityProvider, metadata: Metadata) { + async updateIdentityProviders( + id: string, + body: CreateIdentityProvider, + metadata: Metadata, + ) { return await lastValueFrom( - this.identityProviderService.UpdateIdentityProvider({ - id, - ...body, - }, metadata), + this.identityProviderService.UpdateIdentityProvider( + { + id, + ...body, + }, + metadata, + ), ); } @@ -158,5 +172,4 @@ export class IdentityProviderService implements OnModuleInit { this.identityProviderService.GetProviderLinksFromDomain({ frontDomain }), ); } - } From cb66d4f6c09db550ba393cded0cadd5f16f69e5b Mon Sep 17 00:00:00 2001 From: marcos-silva-rodrigues Date: Wed, 16 Jul 2025 11:35:47 -0300 Subject: [PATCH 14/21] FEAT: dynamic callback and issuer urls --- package-lock.json | 8 ++-- package.json | 2 +- .../identity-provider/dto/sso-signin.dto.ts | 1 + .../identity-provider.controller.ts | 39 ++++++++++++++----- .../identity-provider.service.ts | 25 +++--------- 5 files changed, 42 insertions(+), 33 deletions(-) diff --git a/package-lock.json b/package-lock.json index bfc70d3..986889b 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.38.0-beta.9", + "@dadosfera/protospack-v2": "3.38.0-beta.10", "@grpc/grpc-js": "^1.9.3", "@grpc/proto-loader": "^0.7.9", "@nestjs/cli": "^9.5.0", @@ -1407,9 +1407,9 @@ } }, "node_modules/@dadosfera/protospack-v2": { - "version": "3.38.0-beta.9", - "resolved": "https://dadosfera-611330257153.d.codeartifact.us-east-1.amazonaws.com/npm/dadosfera-npm/@dadosfera/protospack-v2/-/protospack-v2-3.38.0-beta.9.tgz", - "integrity": "sha512-9rCSI3g/VVysJUoZ7SdaVe+/UOZUC4qwlnKntf7YxVhb5yVGS3D9U/TjLvjI8zVe5gQ3f711GrhXUgrTGYU/2g==", + "version": "3.38.0-beta.10", + "resolved": "https://dadosfera-611330257153.d.codeartifact.us-east-1.amazonaws.com/npm/dadosfera-npm/@dadosfera/protospack-v2/-/protospack-v2-3.38.0-beta.10.tgz", + "integrity": "sha512-4miwovVFHtBY1VTHdW8BVmSB8m+LXfIA8uigHbeo8IwreMtTHuLpvfenT4MBSPkdVmlo9+0XBKKf+PaSqtdMAg==", "license": "ISC", "dependencies": { "@grpc/grpc-js": "^1.9.3", diff --git a/package.json b/package.json index 9b77603..605dc93 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.38.0-beta.9", + "@dadosfera/protospack-v2": "3.38.0-beta.10", "@grpc/grpc-js": "^1.9.3", "@grpc/proto-loader": "^0.7.9", "@nestjs/cli": "^9.5.0", diff --git a/src/modules/identity-provider/dto/sso-signin.dto.ts b/src/modules/identity-provider/dto/sso-signin.dto.ts index d339a69..4bf26e2 100644 --- a/src/modules/identity-provider/dto/sso-signin.dto.ts +++ b/src/modules/identity-provider/dto/sso-signin.dto.ts @@ -6,4 +6,5 @@ export class SsoSignInDto { readonly clientId: string; readonly clientSecret: string; readonly issuerUrl: string; + readonly redirectUrls: string[]; } \ 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 index 75807d8..813d925 100644 --- a/src/modules/identity-provider/identity-provider.controller.ts +++ b/src/modules/identity-provider/identity-provider.controller.ts @@ -27,7 +27,11 @@ import { } from './dto/identity-provider.dto'; import { Request } from 'express'; import ErrorCodes from 'src/utils/errorCodes'; -import { Authenticated, RequireModule, RequireSomePermission } from 'src/decorators/authentication.decorator'; +import { + Authenticated, + RequireModule, + RequireSomePermission, +} from 'src/decorators/authentication.decorator'; import { PERMISSIONS_GROUPS } from 'src/authentication/permissions.enum'; @Controller('identity-providers') @@ -119,15 +123,24 @@ export class IdentityProviderController { ...user, }); - return await this.identityProviderService.updateIdentityProviders(id, body, metadata); + return await this.identityProviderService.updateIdentityProviders( + id, + body, + metadata, + ); } @Post('/callback') @HttpCode(HttpStatus.OK) - async callbackIdp(@Req() req: Request, @Body() body: { - state: string, - code: string - }) { + async callbackIdp( + @Req() req: Request, + @Language() language: LanguageEnum, + @Body() + body: { + state: string; + code: string; + }, + ) { this.logger.info('GET /identity-providers/callback'); const { code, state } = body; @@ -141,7 +154,13 @@ export class IdentityProviderController { throw new Error(ErrorCodes.IDENTITY_PROVIDER.INVALID_RESPONSE); } - return await this.identityProviderService.getTokenByIdp(code, state); + const origin = req.headers['origin'] as string; + const langHeader = req.headers['dadosfera-lang'] as string; + + const lang = langHeader.substring(0, 2) + langHeader.substring(2).toUpperCase(); + const callbackUrl = process.env.ENV !== "prd" ? `${origin}/auth/callback` : `${origin}/${lang}/auth/callback`; + + return await this.identityProviderService.getTokenByIdp(code, state, callbackUrl); } @Get('/links') @@ -155,7 +174,10 @@ export class IdentityProviderController { throw new Error(ErrorCodes.IDENTITY_PROVIDER.INVALID_HEADER); } - const result = await this.identityProviderService.identityProvidersLinksPerDomain(frontDomain); + const result = + await this.identityProviderService.identityProvidersLinksPerDomain( + frontDomain, + ); return result; } @@ -171,5 +193,4 @@ export class IdentityProviderController { url: redirectUrl, }; } - } diff --git a/src/modules/identity-provider/identity-provider.service.ts b/src/modules/identity-provider/identity-provider.service.ts index 8e22c43..456343c 100644 --- a/src/modules/identity-provider/identity-provider.service.ts +++ b/src/modules/identity-provider/identity-provider.service.ts @@ -49,14 +49,11 @@ export class IdentityProviderService implements OnModuleInit { this.identityProviderService.FindIdentityProvider({ id }), ); - const redirectUri = this.getCallbackUrl(); - const cognitoIssuerURL = this.getCognitoIssuerUrl(); - - const issuer = await Issuer.discover(cognitoIssuerURL); + const issuer = await Issuer.discover(idp.issuerUrl); const client = new issuer.Client({ client_id: idp.clientId, client_secret: idp.clientSecret, - redirect_uris: [redirectUri], + redirect_uris: idp.redirectUrls, response_types: ['code'], }); @@ -75,7 +72,8 @@ export class IdentityProviderService implements OnModuleInit { state, clientId: idp.clientId, clientSecret: idp.clientSecret, - issuerUrl: cognitoIssuerURL, + issuerUrl: idp.issuerUrl, + redirectUrls: idp.redirectUrls, }); const url = client.authorizationUrl({ @@ -90,29 +88,18 @@ export class IdentityProviderService implements OnModuleInit { return idpUrl; } - private getCallbackUrl() { - const AUTH_CALLBACK = process.env.AUTH_CALLBACK; - return AUTH_CALLBACK; - } - - private getCognitoIssuerUrl() { - const COGNITO_ISSUER_URL = process.env.COGNITO_ISSUER_URL; - return COGNITO_ISSUER_URL; - } - - async getTokenByIdp(code: string, state: string) { + async getTokenByIdp(code: string, state: string, callbackUrl: string) { const ssoSign = await this.cacheService.get(state); if (!ssoSign) { throw new BadRequestException('SSO sign-in is expired or not found'); } - const callbackUrl = this.getCallbackUrl(); const issuer = await Issuer.discover(ssoSign.issuerUrl); const client = new issuer.Client({ client_id: ssoSign.clientId, client_secret: ssoSign.clientSecret, - redirect_uris: [callbackUrl], + redirect_uris: ssoSign.redirectUrls, }); if (ssoSign === null) { From c1e4f0b18abf19c314af473faa608e97e00b63c7 Mon Sep 17 00:00:00 2001 From: marcos-silva-rodrigues Date: Wed, 16 Jul 2025 11:48:15 -0300 Subject: [PATCH 15/21] FIX: remove express session --- package-lock.json | 100 -------------------------------- package.json | 1 - src/@types/express-session.d.ts | 8 --- src/main.ts | 15 ++--- 4 files changed, 8 insertions(+), 116 deletions(-) delete mode 100644 src/@types/express-session.d.ts diff --git a/package-lock.json b/package-lock.json index 986889b..4092f52 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,7 +35,6 @@ "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", @@ -7055,75 +7054,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/express-session": { - "version": "1.18.1", - "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.1.tgz", - "integrity": "sha512-a5mtTqEaZvBCL9A9aqkrtfz+3SMDhOVUnjafjo+s7A9Txkq+SVX2DLvSp1Zrv4uCXa3lMSK3viWnh9Gg07PBUA==", - "license": "MIT", - "dependencies": { - "cookie": "0.7.2", - "cookie-signature": "1.0.7", - "debug": "2.6.9", - "depd": "~2.0.0", - "on-headers": "~1.0.2", - "parseurl": "~1.3.3", - "safe-buffer": "5.2.1", - "uid-safe": "~2.1.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/express-session/node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express-session/node_modules/cookie-signature": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", - "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", - "license": "MIT" - }, - "node_modules/express-session/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/express-session/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/express-session/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, "node_modules/express/node_modules/body-parser": { "version": "1.20.3", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", @@ -10375,15 +10305,6 @@ "node": ">= 0.8" } }, - "node_modules/on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -11445,15 +11366,6 @@ "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", "license": "MIT" }, - "node_modules/random-bytes": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", - "integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -13172,18 +13084,6 @@ "node": ">=8" } }, - "node_modules/uid-safe": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", - "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", - "license": "MIT", - "dependencies": { - "random-bytes": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/uid2": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/uid2/-/uid2-0.0.4.tgz", diff --git a/package.json b/package.json index 605dc93..ac37a76 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,6 @@ "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", diff --git a/src/@types/express-session.d.ts b/src/@types/express-session.d.ts deleted file mode 100644 index 6531c85..0000000 --- a/src/@types/express-session.d.ts +++ /dev/null @@ -1,8 +0,0 @@ -import 'express-session'; - -declare module 'express-session' { - interface SessionData { - state: string | undefined; - code_verifier: string | undefined; - } -} \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index eff6350..e6421fc 100644 --- a/src/main.ts +++ b/src/main.ts @@ -3,12 +3,12 @@ import { NestFactory } from '@nestjs/core'; import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; import helmet from 'helmet'; import { DadosferaLogger } from '@dadosfera/dadosfera-logs'; - +import { json, urlencoded } from 'express'; import { AppModule } from './app.module'; import { writeFileSync } from 'fs'; import { execSync } from 'child_process'; import { INestApplication } from '@nestjs/common'; -import session from 'express-session'; + async function bootstrap() { DadosferaLogger.setupLogger({ serviceName: 'maestro', @@ -26,11 +26,12 @@ async function bootstrap() { }, }); app.use(helmet()); - app.use(session({ - secret: process.env.SESSION_SECRET, - resave: false, - saveUninitialized: false, - })); + + if (process.env.ENV === 'prd') { + app.use('/catalog/register-dataset', json({ limit: '10mb' })); + app.use('/catalog/register-dataset', urlencoded({ extended: true, limit: '10mb' })); + } + configureSwagger(app); await app.listen(3333); if (process.env.KILL_AFTER_START) await app.close(); From dda3e8baafd4a2249bb6bcb4be90c1a4d1c135d9 Mon Sep 17 00:00:00 2001 From: marcos-silva-rodrigues Date: Wed, 16 Jul 2025 17:39:15 -0300 Subject: [PATCH 16/21] FIX: add logger --- src/modules/identity-provider/identity-provider.controller.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/modules/identity-provider/identity-provider.controller.ts b/src/modules/identity-provider/identity-provider.controller.ts index 813d925..8c9010d 100644 --- a/src/modules/identity-provider/identity-provider.controller.ts +++ b/src/modules/identity-provider/identity-provider.controller.ts @@ -189,6 +189,8 @@ export class IdentityProviderController { const redirectUrl = await this.identityProviderService.loginIdentityProvider(id); + + this.logger.info(`Redirecting to: ${redirectUrl}`); return { url: redirectUrl, }; From fbeb8dfd913a30ec33c8d4ee940b8b09f73a47dc Mon Sep 17 00:00:00 2001 From: marcos-silva-rodrigues Date: Wed, 16 Jul 2025 17:56:43 -0300 Subject: [PATCH 17/21] FIX: redis env --- maestro/templates/deployment.yaml | 8 ++++++++ maestro/values.yaml | 5 +++++ 2 files changed, 13 insertions(+) diff --git a/maestro/templates/deployment.yaml b/maestro/templates/deployment.yaml index c648514..bcc7da2 100644 --- a/maestro/templates/deployment.yaml +++ b/maestro/templates/deployment.yaml @@ -100,6 +100,14 @@ spec: value: {{ .Values.maestro.open_customer_id }} - name: OPEN_GROUP_ID value: {{ .Values.maestro.open_group_id }} + - name: DEDICATED_PROXY + value: {{ .Values.maestro.dedicated_proxy }} + - name: REDIS_DATABASE + value: "{{ .Values.maestro.redis_database }}" + - name: REDIS_HOST + value: {{ .Values.maestro.redis_host }} + - name: REDIS_PORT + value: "{{ .Values.maestro.redis_port }}" - name: JWT_PRIVATE_KEY valueFrom: secretKeyRef: diff --git a/maestro/values.yaml b/maestro/values.yaml index 14dd9e4..ee59bc5 100644 --- a/maestro/values.yaml +++ b/maestro/values.yaml @@ -42,6 +42,11 @@ maestro: upload_file_agent_connection: cbc2f881-58c4-4d60-8003-0979b0b5b911 open_customer_id: f239718a-a271-4ef9-ae7e-02a2f0f3aa6e open_group_id: 401573bb-334f-44b2-b30e-88d4cea31ae9 + dedicated_proxy: "" + restricted_ip: "" + redis_host: "product-redis-prd.z4xvqj.0001.use1.cache.amazonaws.com" + redis_port: "6379" + redis_database: "0" autoscaling: enabled: false minReplicas: 1 From 3eba3f414f6f3181d26f043a716f4ecc6c244503 Mon Sep 17 00:00:00 2001 From: marcos-silva-rodrigues Date: Wed, 16 Jul 2025 18:15:47 -0300 Subject: [PATCH 18/21] FIX: send redirect uri --- .../identity-provider.controller.ts | 15 ++++++++++----- .../identity-provider.service.ts | 3 ++- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/modules/identity-provider/identity-provider.controller.ts b/src/modules/identity-provider/identity-provider.controller.ts index 8c9010d..6d8aed6 100644 --- a/src/modules/identity-provider/identity-provider.controller.ts +++ b/src/modules/identity-provider/identity-provider.controller.ts @@ -8,7 +8,6 @@ import { HttpStatus, Inject, Param, - Patch, Post, Put, Redirect, @@ -155,9 +154,8 @@ export class IdentityProviderController { } const origin = req.headers['origin'] as string; - const langHeader = req.headers['dadosfera-lang'] as string; - const lang = langHeader.substring(0, 2) + langHeader.substring(2).toUpperCase(); + const lang = language.substring(0, 2) + language.substring(2).toUpperCase(); const callbackUrl = process.env.ENV !== "prd" ? `${origin}/auth/callback` : `${origin}/${lang}/auth/callback`; return await this.identityProviderService.getTokenByIdp(code, state, callbackUrl); @@ -184,11 +182,18 @@ export class IdentityProviderController { @Get(':id') @HttpCode(HttpStatus.OK) @Redirect() - async loginIdp(@Param('id') id: string) { + async loginIdp(@Param('id') id: string, @Req() req: Request, @Language() language: LanguageEnum) { this.logger.info('GET /identity-providers/:id'); + + const origin = req.headers['origin'] as string || req.headers['referer'] as string; + console.log('Origin:', origin); + + const lang = language.substring(0, 2) + language.substring(2).toUpperCase(); + const callbackUrl = process.env.ENV !== "prd" ? `${origin}/auth/callback` : `${origin}/${lang}/auth/callback`; + const redirectUrl = - await this.identityProviderService.loginIdentityProvider(id); + await this.identityProviderService.loginIdentityProvider(id, callbackUrl); this.logger.info(`Redirecting to: ${redirectUrl}`); return { diff --git a/src/modules/identity-provider/identity-provider.service.ts b/src/modules/identity-provider/identity-provider.service.ts index 456343c..4cca0fe 100644 --- a/src/modules/identity-provider/identity-provider.service.ts +++ b/src/modules/identity-provider/identity-provider.service.ts @@ -44,7 +44,7 @@ export class IdentityProviderService implements OnModuleInit { ); } - async loginIdentityProvider(id: string) { + async loginIdentityProvider(id: string, callbackUrl: string) { const idp = await lastValueFrom( this.identityProviderService.FindIdentityProvider({ id }), ); @@ -83,6 +83,7 @@ export class IdentityProviderService implements OnModuleInit { code_challenge_method: 'S256', state, nonce, + redirect_uri: callbackUrl, }); const idpUrl = url + '&identity_provider=' + idp.name; return idpUrl; From e81cfdcce10775a66782aa68102e3f4764051bc5 Mon Sep 17 00:00:00 2001 From: marcos-silva-rodrigues Date: Wed, 16 Jul 2025 18:36:36 -0300 Subject: [PATCH 19/21] FIX: using only header origin --- src/modules/identity-provider/identity-provider.controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/identity-provider/identity-provider.controller.ts b/src/modules/identity-provider/identity-provider.controller.ts index 6d8aed6..b01ed30 100644 --- a/src/modules/identity-provider/identity-provider.controller.ts +++ b/src/modules/identity-provider/identity-provider.controller.ts @@ -186,7 +186,7 @@ export class IdentityProviderController { this.logger.info('GET /identity-providers/:id'); - const origin = req.headers['origin'] as string || req.headers['referer'] as string; + const origin = req.headers['origin'] as string; console.log('Origin:', origin); const lang = language.substring(0, 2) + language.substring(2).toUpperCase(); From 4c7111dcb6d97e9512859884c3ed1f764add93e0 Mon Sep 17 00:00:00 2001 From: marcos-silva-rodrigues Date: Wed, 16 Jul 2025 18:45:47 -0300 Subject: [PATCH 20/21] FIX: using referer headaer --- .../identity-provider/identity-provider.controller.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/modules/identity-provider/identity-provider.controller.ts b/src/modules/identity-provider/identity-provider.controller.ts index b01ed30..26197b6 100644 --- a/src/modules/identity-provider/identity-provider.controller.ts +++ b/src/modules/identity-provider/identity-provider.controller.ts @@ -186,11 +186,14 @@ export class IdentityProviderController { this.logger.info('GET /identity-providers/:id'); - const origin = req.headers['origin'] as string; + const frontDomain = req.headers['origin'] as string || req.headers['referer'] as string; console.log('Origin:', origin); + const host = frontDomain.lastIndexOf('/') !== -1 + ? frontDomain.substring(0, frontDomain.lastIndexOf('/')) + : frontDomain; const lang = language.substring(0, 2) + language.substring(2).toUpperCase(); - const callbackUrl = process.env.ENV !== "prd" ? `${origin}/auth/callback` : `${origin}/${lang}/auth/callback`; + const callbackUrl = process.env.ENV !== "prd" ? `${host}/auth/callback` : `${host}/${lang}/auth/callback`; const redirectUrl = await this.identityProviderService.loginIdentityProvider(id, callbackUrl); From e9880bcf68c74fef1128bdf54860f3a4419d1ca7 Mon Sep 17 00:00:00 2001 From: marcos-silva-rodrigues Date: Wed, 16 Jul 2025 18:49:27 -0300 Subject: [PATCH 21/21] FIX: add logger in header --- src/modules/identity-provider/identity-provider.controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/identity-provider/identity-provider.controller.ts b/src/modules/identity-provider/identity-provider.controller.ts index 26197b6..c25b2b4 100644 --- a/src/modules/identity-provider/identity-provider.controller.ts +++ b/src/modules/identity-provider/identity-provider.controller.ts @@ -187,7 +187,7 @@ export class IdentityProviderController { const frontDomain = req.headers['origin'] as string || req.headers['referer'] as string; - console.log('Origin:', origin); + this.logger.info(`Front domain: ${frontDomain}`); const host = frontDomain.lastIndexOf('/') !== -1 ? frontDomain.substring(0, frontDomain.lastIndexOf('/')) : frontDomain;