From 27242819e2e1f98b8583505d35e89dd0b582eecd Mon Sep 17 00:00:00 2001 From: Andreas Knuth Date: Fri, 28 Feb 2025 23:54:57 +0100 Subject: [PATCH] update packages, using FirebaseAdminModule --- bizmatch-server/.vscode/launch.json | 3 +- bizmatch-server/package.json | 39 ++----- bizmatch-server/src/app.controller.ts | 3 +- bizmatch-server/src/app.module.ts | 17 +-- bizmatch-server/src/auth/auth.controller.ts | 46 ++------ bizmatch-server/src/auth/auth.module.ts | 10 +- bizmatch-server/src/auth/auth.service.ts | 101 ------------------ bizmatch-server/src/drizzle/drizzle.module.ts | 4 +- bizmatch-server/src/drizzle/export.ts | 1 - bizmatch-server/src/event/event.module.ts | 3 +- .../firebase-admin/firebase-admin.module.ts | 30 ++++++ bizmatch-server/src/geo/geo.module.ts | 2 + bizmatch-server/src/image/image.module.ts | 3 +- bizmatch-server/src/jwt-auth/auth.guard.ts | 37 ++++++- .../src/jwt-auth/firebase-admin.ts | 28 ++--- .../src/jwt-auth/optional-auth.guard.ts | 47 ++++++-- bizmatch-server/src/jwt.strategy.ts | 55 ---------- .../src/listings/business-listing.service.ts | 6 +- .../listings/commercial-property.service.ts | 6 +- .../src/listings/listings.module.ts | 3 +- bizmatch-server/src/log/log.module.ts | 2 + bizmatch-server/src/mail/mail.module.ts | 2 + bizmatch-server/src/main.ts | 3 +- bizmatch-server/src/payment/payment.module.ts | 7 +- .../src/payment/payment.service.ts | 46 ++++---- .../select-options/select-options.module.ts | 2 + bizmatch-server/src/user/user.module.ts | 3 +- .../details-business-listing.component.ts | 2 +- ...s-commercial-property-listing.component.ts | 2 +- .../account/account.component.html | 4 +- bizmatch/src/app/services/auth.service.ts | 57 ++++++---- 31 files changed, 247 insertions(+), 327 deletions(-) delete mode 100644 bizmatch-server/src/auth/auth.service.ts create mode 100644 bizmatch-server/src/firebase-admin/firebase-admin.module.ts delete mode 100644 bizmatch-server/src/jwt.strategy.ts diff --git a/bizmatch-server/.vscode/launch.json b/bizmatch-server/.vscode/launch.json index 8467ba2..c6e7238 100644 --- a/bizmatch-server/.vscode/launch.json +++ b/bizmatch-server/.vscode/launch.json @@ -5,7 +5,8 @@ "type": "node", "request": "launch", "name": "Debug Nest Framework", - "runtimeExecutable": "npm", + //"runtimeExecutable": "npm", + "runtimeExecutable": "/home/aknuth/.nvm/versions/node/v22.14.0/bin/npm", "runtimeArgs": ["run", "start:debug", "--", "--inspect-brk"], "autoAttachChildProcesses": true, "restart": true, diff --git a/bizmatch-server/package.json b/bizmatch-server/package.json index 8a2a37c..69f0ee4 100644 --- a/bizmatch-server/package.json +++ b/bizmatch-server/package.json @@ -26,32 +26,23 @@ "generateTypes": "tsx src/drizzle/generateTypes.ts src/drizzle/schema.ts src/models/db.model.ts" }, "dependencies": { - "@nestjs-modules/mailer": "^1.10.3", - "@nestjs/common": "^10.0.0", - "@nestjs/config": "^3.2.0", - "@nestjs/core": "^10.0.0", - "@nestjs/jwt": "^10.2.0", - "@nestjs/passport": "^10.0.3", - "@nestjs/platform-express": "^10.0.0", - "@nestjs/serve-static": "^4.0.1", + "@nestjs-modules/mailer": "^2.0.2", + "@nestjs/common": "^11.0.11", + "@nestjs/config": "^4.0.0", + "@nestjs/core": "^11.0.11", + "@nestjs/platform-express": "^11.0.11", "@types/stripe": "^8.0.417", "body-parser": "^1.20.2", "cls-hooked": "^4.2.2", "cors": "^2.8.5", - "dotenv": "^16.4.5", - "dotenv-flow": "^4.1.0", "drizzle-orm": "^0.32.0", "firebase": "^11.3.1", "firebase-admin": "^13.1.0", "fs-extra": "^11.2.0", "groq-sdk": "^0.5.0", "handlebars": "^4.7.8", - "jsonwebtoken": "^9.0.2", - "jwk-to-pem": "^2.0.6", - "jwks-rsa": "^3.1.0", - "ky": "^1.4.0", "nest-winston": "^1.9.4", - "nestjs-cls": "^4.4.1", + "nestjs-cls": "^5.4.0", "nodemailer": "^6.9.10", "nodemailer-smtp-transport": "^2.7.4", "openai": "^4.52.6", @@ -69,30 +60,20 @@ "devDependencies": { "@babel/parser": "^7.24.4", "@babel/traverse": "^7.24.1", - "@nestjs/cli": "^10.0.0", - "@nestjs/schematics": "^10.0.0", - "@nestjs/testing": "^10.0.0", + "@nestjs/cli": "^11.0.5", + "@nestjs/schematics": "^11.0.1", + "@nestjs/testing": "^11.0.11", "@types/express": "^4.17.17", - "@types/jest": "^29.5.2", - "@types/jsonwebtoken": "^9.0.6", - "@types/jwk-to-pem": "^2.0.3", "@types/multer": "^1.4.11", "@types/node": "^20.11.19", "@types/nodemailer": "^6.4.14", - "@types/passport-google-oauth20": "^2.0.14", - "@types/passport-jwt": "^4.0.1", - "@types/passport-local": "^1.0.38", "@types/pg": "^8.11.5", - "@types/supertest": "^6.0.0", - "@typescript-eslint/eslint-plugin": "^6.0.0", - "@typescript-eslint/parser": "^6.0.0", "commander": "^12.0.0", "drizzle-kit": "^0.23.0", "esbuild-register": "^3.5.0", "eslint": "^8.42.0", "eslint-config-prettier": "^9.0.0", "eslint-plugin-prettier": "^5.0.0", - "jest": "^29.5.0", "kysely-codegen": "^0.15.0", "pg-to-ts": "^4.1.1", "prettier": "^3.0.0", @@ -122,4 +103,4 @@ "coverageDirectory": "../coverage", "testEnvironment": "node" } -} +} \ No newline at end of file diff --git a/bizmatch-server/src/app.controller.ts b/bizmatch-server/src/app.controller.ts index 205fba0..43da7fd 100644 --- a/bizmatch-server/src/app.controller.ts +++ b/bizmatch-server/src/app.controller.ts @@ -1,13 +1,12 @@ import { Controller, Get, Request, UseGuards } from '@nestjs/common'; import { AppService } from './app.service'; -import { AuthService } from './auth/auth.service'; + import { AuthGuard } from './jwt-auth/auth.guard'; @Controller() export class AppController { constructor( private readonly appService: AppService, - private authService: AuthService, ) {} @UseGuards(AuthGuard) diff --git a/bizmatch-server/src/app.module.ts b/bizmatch-server/src/app.module.ts index 2282248..94c5ba6 100644 --- a/bizmatch-server/src/app.module.ts +++ b/bizmatch-server/src/app.module.ts @@ -1,5 +1,4 @@ import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common'; -import { ConfigModule } from '@nestjs/config'; import { utilities as nestWinstonModuleUtilities, WinstonModule } from 'nest-winston'; import * as winston from 'winston'; import { AiModule } from './ai/ai.module'; @@ -13,32 +12,33 @@ import { ListingsModule } from './listings/listings.module'; import { LogController } from './log/log.controller'; import { LogModule } from './log/log.module'; -import dotenvFlow from 'dotenv-flow'; import { EventModule } from './event/event.module'; import { MailModule } from './mail/mail.module'; +import { ConfigModule } from '@nestjs/config'; import { APP_INTERCEPTOR } from '@nestjs/core'; import { ClsMiddleware, ClsModule } from 'nestjs-cls'; +import path from 'path'; import { LoggingInterceptor } from './interceptors/logging.interceptor'; import { UserInterceptor } from './interceptors/user.interceptor'; import { RequestDurationMiddleware } from './request-duration/request-duration.middleware'; import { SelectOptionsModule } from './select-options/select-options.module'; import { UserModule } from './user/user.module'; +import { FirebaseAdminModule } from './firebase-admin/firebase-admin.module'; //loadEnvFiles(); -dotenvFlow.config(); console.log('Loaded environment variables:'); -console.log(JSON.stringify(process.env, null, 2)); +//console.log(JSON.stringify(process.env, null, 2)); @Module({ imports: [ ClsModule.forRoot({ global: true, // Macht den ClsService global verfügbar middleware: { mount: true }, // Registriert automatisch die ClsMiddleware - // setup: clsService => { - // // Optional: zusätzliche Setup-Logik - // }, }), - ConfigModule.forRoot({ isGlobal: true }), + //ConfigModule.forRoot({ envFilePath: '.env' }), + ConfigModule.forRoot({ + envFilePath: [path.resolve(__dirname, '..', '.env')], + }), MailModule, AuthModule, WinstonModule.forRoot({ @@ -68,6 +68,7 @@ console.log(JSON.stringify(process.env, null, 2)); LogModule, // PaymentModule, EventModule, + FirebaseAdminModule, ], controllers: [AppController, LogController], providers: [ diff --git a/bizmatch-server/src/auth/auth.controller.ts b/bizmatch-server/src/auth/auth.controller.ts index f7a35b4..febfb94 100644 --- a/bizmatch-server/src/auth/auth.controller.ts +++ b/bizmatch-server/src/auth/auth.controller.ts @@ -1,36 +1,12 @@ -import { Body, Controller, Get, HttpException, HttpStatus, Param, Post, Put, UseGuards } from '@nestjs/common'; -import { AuthGuard } from 'src/jwt-auth/auth.guard'; -import admin from 'src/jwt-auth/firebase-admin'; -import { KeycloakUser } from 'src/models/main.model'; -import { AuthService } from './auth.service'; +import { Body, Controller, HttpException, HttpStatus, Inject, Post } from '@nestjs/common'; +import * as admin from 'firebase-admin'; @Controller('auth') export class AuthController { - constructor(private readonly authService: AuthService) {} - - // @UseGuards(AdminAuthGuard) - // @Get() - // async getAccessToken(): Promise { - // return await this.authService.getAccessToken(); - // } - - // @UseGuards(AdminAuthGuard) - // @Get('user/all') - // async getUsers(): Promise { - // return await this.authService.getUsers(); - // } - - @UseGuards(AuthGuard) - @Get('users/:userid') - async getUser(@Param('userid') userId: string): Promise { - return await this.authService.getUser(userId); - } - @UseGuards(AuthGuard) - @Put('users/:userid') - async updateKeycloakUser(@Body() keycloakUser: KeycloakUser): Promise { - return await this.authService.updateKeycloakUser(keycloakUser); - } - + constructor( + @Inject('FIREBASE_ADMIN') + private readonly firebaseAdmin: typeof admin, + ) {} @Post('verify-email') async verifyEmail(@Body('oobCode') oobCode: string, @Body('email') email: string) { if (!oobCode || !email) { @@ -39,7 +15,7 @@ export class AuthController { try { // Schritt 1: Hole den Benutzer anhand der E-Mail-Adresse - const userRecord = await admin.auth().getUserByEmail(email); + const userRecord = await this.firebaseAdmin.auth().getUserByEmail(email); if (userRecord.emailVerified) { return { message: 'Email is already verified' }; @@ -48,7 +24,7 @@ export class AuthController { // Schritt 2: Aktualisiere den Benutzerstatus // Hinweis: Wir können den oobCode nicht serverseitig validieren. // Wir nehmen an, dass der oobCode korrekt ist, da er von Firebase generiert wurde. - await admin.auth().updateUser(userRecord.uid, { + await this.firebaseAdmin.auth().updateUser(userRecord.uid, { emailVerified: true, }); @@ -57,9 +33,5 @@ export class AuthController { throw new HttpException(error.message || 'Failed to verify email', HttpStatus.BAD_REQUEST); } } - // @UseGuards(AdminAuthGuard) - // @Get('user/:userid/lastlogin') //e0811669-c7eb-4e5e-a699-e8334d5c5b01 -> aknuth - // getLastLogin(@Param('userid') userId: string): any { - // return this.authService.getLastLogin(userId); - // } + } diff --git a/bizmatch-server/src/auth/auth.module.ts b/bizmatch-server/src/auth/auth.module.ts index a5e6116..13a2bf1 100644 --- a/bizmatch-server/src/auth/auth.module.ts +++ b/bizmatch-server/src/auth/auth.module.ts @@ -1,12 +1,12 @@ import { Module } from '@nestjs/common'; -import { PassportModule } from '@nestjs/passport'; +import { ConfigModule } from '@nestjs/config'; +import { FirebaseAdminModule } from 'src/firebase-admin/firebase-admin.module'; import { AuthController } from './auth.controller'; -import { AuthService } from './auth.service'; + @Module({ - imports: [PassportModule], - providers: [AuthService], + imports: [ConfigModule.forRoot({ envFilePath: '.env' }),FirebaseAdminModule], controllers: [AuthController], - exports: [AuthService], + exports: [], }) export class AuthModule {} diff --git a/bizmatch-server/src/auth/auth.service.ts b/bizmatch-server/src/auth/auth.service.ts deleted file mode 100644 index 6ce4893..0000000 --- a/bizmatch-server/src/auth/auth.service.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { KeycloakUser } from 'src/models/main.model'; -import urlcat from 'urlcat'; -@Injectable() -export class AuthService { - public async getAccessToken() { - try { - const params = new URLSearchParams(); - params.append('grant_type', 'password'); - params.append('username', process.env.KEYCLOAK_ADMIN_USER); - params.append('password', process.env.KEYCLOAK_ADMIN_PASSWORD); - const URL = `${process.env.KEYCLOAK_HOST}${process.env.KEYCLOAK_TOKEN_URL}`; - - const response = await fetch(URL, { - method: 'POST', - body: params.toString(), - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - Authorization: process.env.KEYCLOAK_ADMIN_TOKEN, - }, - }); - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - const data = await response.json(); - return (data).access_token; - } catch (error) { - if (error.name === 'HTTPError') { - const errorJson = await error.response.json(); - console.error('Fehlerantwort vom Server:', errorJson); - } else { - console.error('Allgemeiner Fehler:', error); - } - } - } - - public async getUsers(): Promise { - const token = await this.getAccessToken(); - const URL = `${process.env.KEYCLOAK_HOST}${process.env.KEYCLOAK_ADMIN_REALM}${process.env.REALM}${process.env.KEYCLOAK_USERS_URL}`; - const response = await fetch(URL, { - method: 'GET', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - Authorization: `Bearer ${token}`, - }, - }); - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - const data = await response.json(); - return data as KeycloakUser[]; - } - public async getUser(userid: string): Promise { - const token = await this.getAccessToken(); - const URLPATH = `${process.env.KEYCLOAK_ADMIN_REALM}${process.env.REALM}${process.env.KEYCLOAK_USER_URL}`; - const URL = urlcat(process.env.KEYCLOAK_HOST, URLPATH, { userid }); - const response = await fetch(URL, { - method: 'GET', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - Authorization: `Bearer ${token}`, - }, - }); - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - const data = await response.json(); - return data as KeycloakUser; - } - public async updateKeycloakUser(keycloakUser: KeycloakUser): Promise { - const token = await this.getAccessToken(); - const userid = keycloakUser.id; - const URLPATH = `${process.env.KEYCLOAK_ADMIN_REALM}${process.env.REALM}${process.env.KEYCLOAK_USER_URL}`; - const URL = urlcat(process.env.KEYCLOAK_HOST, URLPATH, { userid }); - const response = await fetch(URL, { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${token}`, - }, - body: JSON.stringify(keycloakUser), - }); - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - } - // public async getLastLogin(userid: string) { - // const token = await this.getAccessToken(); - // const URLPATH = `${process.env.KEYCLOAK_ADMIN_REALM}${process.env.REALM}${process.env.KEYCLOAK_LASTLOGIN_URL}`; - // const URL = urlcat(process.env.KEYCLOAK_HOST, URLPATH, { userid }); - // const response = await ky - // .get(URL, { - // headers: { - // 'Content-Type': 'application/x-www-form-urlencoded', - // Authorization: `Bearer ${token}`, - // }, - // }) - // .json(); - // return response; - // } -} diff --git a/bizmatch-server/src/drizzle/drizzle.module.ts b/bizmatch-server/src/drizzle/drizzle.module.ts index 74bd1e3..2e94f52 100644 --- a/bizmatch-server/src/drizzle/drizzle.module.ts +++ b/bizmatch-server/src/drizzle/drizzle.module.ts @@ -1,5 +1,5 @@ import { Module } from '@nestjs/common'; -import { ConfigService } from '@nestjs/config'; +import { ConfigModule, ConfigService } from '@nestjs/config'; import { drizzle } from 'drizzle-orm/node-postgres'; import { WINSTON_MODULE_PROVIDER } from 'nest-winston'; import { ClsService } from 'nestjs-cls'; @@ -9,12 +9,14 @@ import * as schema from './schema'; import { PG_CONNECTION } from './schema'; const { Pool } = pkg; @Module({ + imports: [ConfigModule], providers: [ { provide: PG_CONNECTION, inject: [ConfigService, WINSTON_MODULE_PROVIDER, ClsService], useFactory: async (configService: ConfigService, logger: Logger, cls: ClsService) => { const connectionString = configService.get('DATABASE_URL'); + console.log('--->',connectionString) const pool = new Pool({ connectionString, // ssl: true, // Falls benötigt diff --git a/bizmatch-server/src/drizzle/export.ts b/bizmatch-server/src/drizzle/export.ts index 4a3a589..4adf757 100644 --- a/bizmatch-server/src/drizzle/export.ts +++ b/bizmatch-server/src/drizzle/export.ts @@ -1,4 +1,3 @@ -import 'dotenv/config'; import { drizzle } from 'drizzle-orm/node-postgres'; import { promises as fs } from 'fs'; import { Pool } from 'pg'; diff --git a/bizmatch-server/src/event/event.module.ts b/bizmatch-server/src/event/event.module.ts index c4ca997..ba1cc62 100644 --- a/bizmatch-server/src/event/event.module.ts +++ b/bizmatch-server/src/event/event.module.ts @@ -1,10 +1,11 @@ import { Module } from '@nestjs/common'; import { DrizzleModule } from 'src/drizzle/drizzle.module'; +import { FirebaseAdminModule } from 'src/firebase-admin/firebase-admin.module'; import { EventController } from './event.controller'; import { EventService } from './event.service'; @Module({ - imports: [DrizzleModule], + imports: [DrizzleModule,FirebaseAdminModule], controllers: [EventController], providers: [EventService], }) diff --git a/bizmatch-server/src/firebase-admin/firebase-admin.module.ts b/bizmatch-server/src/firebase-admin/firebase-admin.module.ts new file mode 100644 index 0000000..1495afc --- /dev/null +++ b/bizmatch-server/src/firebase-admin/firebase-admin.module.ts @@ -0,0 +1,30 @@ +import { Module } from '@nestjs/common'; +import { ConfigModule, ConfigService } from '@nestjs/config'; +import * as admin from 'firebase-admin'; + +@Module({ + imports: [ConfigModule], + providers: [ + { + provide: 'FIREBASE_ADMIN', + inject: [ConfigService], + useFactory: (configService: ConfigService) => { + const serviceAccount = { + projectId: configService.get('FIREBASE_PROJECT_ID'), + clientEmail: configService.get('FIREBASE_CLIENT_EMAIL'), + privateKey: configService.get('FIREBASE_PRIVATE_KEY')?.replace(/\\n/g, '\n'), + }; + + if (!admin.apps.length) { + admin.initializeApp({ + credential: admin.credential.cert(serviceAccount), + }); + } + + return admin; + }, + }, + ], + exports: ['FIREBASE_ADMIN'], +}) +export class FirebaseAdminModule {} diff --git a/bizmatch-server/src/geo/geo.module.ts b/bizmatch-server/src/geo/geo.module.ts index 52b96f2..443d1e3 100644 --- a/bizmatch-server/src/geo/geo.module.ts +++ b/bizmatch-server/src/geo/geo.module.ts @@ -1,8 +1,10 @@ import { Module } from '@nestjs/common'; +import { FirebaseAdminModule } from 'src/firebase-admin/firebase-admin.module'; import { GeoController } from './geo.controller'; import { GeoService } from './geo.service'; @Module({ + imports: [FirebaseAdminModule], controllers: [GeoController], providers: [GeoService], }) diff --git a/bizmatch-server/src/image/image.module.ts b/bizmatch-server/src/image/image.module.ts index 3df6077..f23311a 100644 --- a/bizmatch-server/src/image/image.module.ts +++ b/bizmatch-server/src/image/image.module.ts @@ -1,4 +1,5 @@ import { Module } from '@nestjs/common'; +import { FirebaseAdminModule } from 'src/firebase-admin/firebase-admin.module'; import { FileService } from '../file/file.service'; import { ListingsModule } from '../listings/listings.module'; import { SelectOptionsService } from '../select-options/select-options.service'; @@ -6,7 +7,7 @@ import { ImageController } from './image.controller'; import { ImageService } from './image.service'; @Module({ - imports: [ListingsModule], + imports: [ListingsModule,FirebaseAdminModule], controllers: [ImageController], providers: [ImageService, FileService, SelectOptionsService], }) diff --git a/bizmatch-server/src/jwt-auth/auth.guard.ts b/bizmatch-server/src/jwt-auth/auth.guard.ts index 344478c..3b4d646 100644 --- a/bizmatch-server/src/jwt-auth/auth.guard.ts +++ b/bizmatch-server/src/jwt-auth/auth.guard.ts @@ -1,8 +1,13 @@ -import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common'; -import admin from './firebase-admin'; +import { CanActivate, ExecutionContext, Inject, Injectable, UnauthorizedException } from '@nestjs/common'; +import * as admin from 'firebase-admin'; @Injectable() export class AuthGuard implements CanActivate { + constructor( + @Inject('FIREBASE_ADMIN') + private readonly firebaseAdmin: typeof admin, + ) {} + async canActivate(context: ExecutionContext): Promise { const request = context.switchToHttp().getRequest(); const token = this.extractTokenFromHeader(request); @@ -12,8 +17,8 @@ export class AuthGuard implements CanActivate { } try { - const decodedToken = await admin.auth().verifyIdToken(token); - request['user'] = decodedToken; // Fügen Sie die Benutzerdaten dem Request-Objekt hinzu + const decodedToken = await this.firebaseAdmin.auth().verifyIdToken(token); + request['user'] = decodedToken; return true; } catch (error) { throw new UnauthorizedException('Invalid token'); @@ -25,3 +30,27 @@ export class AuthGuard implements CanActivate { return type === 'Bearer' ? token : undefined; } } +// @Injectable() +// export class AuthGuard implements CanActivate { +// async canActivate(context: ExecutionContext): Promise { +// const request = context.switchToHttp().getRequest(); +// const token = this.extractTokenFromHeader(request); + +// if (!token) { +// throw new UnauthorizedException('No token provided'); +// } + +// try { +// const decodedToken = await admin.auth().verifyIdToken(token); +// request['user'] = decodedToken; // Fügen Sie die Benutzerdaten dem Request-Objekt hinzu +// return true; +// } catch (error) { +// throw new UnauthorizedException('Invalid token'); +// } +// } + +// private extractTokenFromHeader(request: Request): string | undefined { +// const [type, token] = request.headers['authorization']?.split(' ') ?? []; +// return type === 'Bearer' ? token : undefined; +// } +// } diff --git a/bizmatch-server/src/jwt-auth/firebase-admin.ts b/bizmatch-server/src/jwt-auth/firebase-admin.ts index 1af1bd1..faf842b 100644 --- a/bizmatch-server/src/jwt-auth/firebase-admin.ts +++ b/bizmatch-server/src/jwt-auth/firebase-admin.ts @@ -1,16 +1,16 @@ -import * as admin from 'firebase-admin'; -import { ServiceAccount } from 'firebase-admin'; +// import * as admin from 'firebase-admin'; +// import { ServiceAccount } from 'firebase-admin'; +// console.log('--> '+process.env['FIREBASE_PROJECT_ID']) +// const serviceAccount: ServiceAccount = { +// projectId: process.env['FIREBASE_PROJECT_ID'], +// clientEmail: process.env['FIREBASE_CLIENT_EMAIL'], +// privateKey: process.env['FIREBASE_PRIVATE_KEY']?.replace(/\\n/g, '\n'), // Ersetzen Sie escaped newlines +// }; -const serviceAccount: ServiceAccount = { - projectId: process.env['FIREBASE_PROJECT_ID'], - clientEmail: process.env['FIREBASE_CLIENT_EMAIL'], - privateKey: process.env['FIREBASE_PRIVATE_KEY']?.replace(/\\n/g, '\n'), // Ersetzen Sie escaped newlines -}; +// if (!admin.apps.length) { +// admin.initializeApp({ +// credential: admin.credential.cert(serviceAccount), +// }); +// } -if (!admin.apps.length) { - admin.initializeApp({ - credential: admin.credential.cert(serviceAccount), - }); -} - -export default admin; +// export default admin; diff --git a/bizmatch-server/src/jwt-auth/optional-auth.guard.ts b/bizmatch-server/src/jwt-auth/optional-auth.guard.ts index 369dedf..e442e50 100644 --- a/bizmatch-server/src/jwt-auth/optional-auth.guard.ts +++ b/bizmatch-server/src/jwt-auth/optional-auth.guard.ts @@ -1,25 +1,30 @@ -import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; -import admin from './firebase-admin'; +import { CanActivate, ExecutionContext, Inject, Injectable } from '@nestjs/common'; +import * as admin from 'firebase-admin'; @Injectable() export class OptionalAuthGuard implements CanActivate { + constructor( + @Inject('FIREBASE_ADMIN') + private readonly firebaseAdmin: typeof admin, + ) {} + async canActivate(context: ExecutionContext): Promise { const request = context.switchToHttp().getRequest(); const token = this.extractTokenFromHeader(request); if (!token) { - return true; // Kein Token vorhanden, aber Zugriff erlaubt + return true; } try { - const decodedToken = await admin.auth().verifyIdToken(token); - request['user'] = decodedToken; // Benutzerdaten zum Request hinzufügen, wenn Token gültig + const decodedToken = await this.firebaseAdmin.auth().verifyIdToken(token); + request['user'] = decodedToken; + return true; } catch (error) { - // Bei ungültigem Token wird kein Fehler geworfen, sondern einfach kein User gesetzt + //throw new UnauthorizedException('Invalid token'); request['user'] = null; + return true; } - - return true; // Zugriff wird immer erlaubt } private extractTokenFromHeader(request: Request): string | undefined { @@ -27,3 +32,29 @@ export class OptionalAuthGuard implements CanActivate { return type === 'Bearer' ? token : undefined; } } +// @Injectable() +// export class OptionalAuthGuard implements CanActivate { +// async canActivate(context: ExecutionContext): Promise { +// const request = context.switchToHttp().getRequest(); +// const token = this.extractTokenFromHeader(request); + +// if (!token) { +// return true; // Kein Token vorhanden, aber Zugriff erlaubt +// } + +// try { +// const decodedToken = await admin.auth().verifyIdToken(token); +// request['user'] = decodedToken; // Benutzerdaten zum Request hinzufügen, wenn Token gültig +// } catch (error) { +// // Bei ungültigem Token wird kein Fehler geworfen, sondern einfach kein User gesetzt +// request['user'] = null; +// } + +// return true; // Zugriff wird immer erlaubt +// } + +// private extractTokenFromHeader(request: Request): string | undefined { +// const [type, token] = request.headers['authorization']?.split(' ') ?? []; +// return type === 'Bearer' ? token : undefined; +// } +// } diff --git a/bizmatch-server/src/jwt.strategy.ts b/bizmatch-server/src/jwt.strategy.ts deleted file mode 100644 index a768050..0000000 --- a/bizmatch-server/src/jwt.strategy.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { Inject, Injectable, UnauthorizedException } from '@nestjs/common'; -import { ConfigService } from '@nestjs/config'; -import { PassportStrategy } from '@nestjs/passport'; -import fs from 'fs'; -import { passportJwtSecret } from 'jwks-rsa'; -import { WINSTON_MODULE_PROVIDER } from 'nest-winston'; -import { ExtractJwt, Strategy } from 'passport-jwt'; -import path from 'path'; -import { Logger } from 'winston'; -import { JwtPayload, JwtUser } from './models/main.model'; -// const logger = winston.createLogger({ -// transports: [new winston.transports.Console()], -// }); -// const pemCache = new Map(); -@Injectable() -export class JwtStrategy extends PassportStrategy(Strategy) { - constructor( - configService: ConfigService, - @Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger, - ) { - const realm = configService.get('REALM'); - // const staticCerts = loadStaticCerts(); - super({ - jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), - ignoreExpiration: false, - secretOrKeyProvider: passportJwtSecret({ - cache: true, - rateLimit: false, - // jwksRequestsPerMinute: 5, - jwksUri: `https://auth.bizmatch.net/realms/${realm}/protocol/openid-connect/certs`, - }), - audience: 'account', // Keycloak Client ID - authorize: '', - issuer: `https://auth.bizmatch.net/realms/${realm}`, - algorithms: ['RS256'], - }); - } - async validate(payload: JwtPayload): Promise { - if (!payload) { - this.logger.error('Invalid payload'); - throw new UnauthorizedException(); - } - if (!payload.sub || !payload.preferred_username) { - this.logger.error('Missing required claims'); - throw new UnauthorizedException(); - } - const result = { userId: payload.sub, firstname: payload.given_name, lastname: payload.family_name, username: payload.preferred_username, roles: payload.realm_access?.roles }; - return result; - } -} -export function loadStaticCerts() { - const certsPath = path.join(__dirname, '../', 'assets', 'keycloak-certs.json'); - const certsData = fs.readFileSync(certsPath, 'utf8'); - return JSON.parse(certsData); -} diff --git a/bizmatch-server/src/listings/business-listing.service.ts b/bizmatch-server/src/listings/business-listing.service.ts index 1dbfda6..5014b3a 100644 --- a/bizmatch-server/src/listings/business-listing.service.ts +++ b/bizmatch-server/src/listings/business-listing.service.ts @@ -103,7 +103,7 @@ export class BusinessListingService { whereConditions.push(and(ilike(schema.users.firstname, `%${firstname}%`), ilike(schema.users.lastname, `%${lastname}%`))); } } - if (!user?.roles?.includes('ADMIN') ?? false) { + if (!user?.roles?.includes('ADMIN')) { whereConditions.push(or(eq(businesses.email, user?.username), ne(businesses.draft, true))); } whereConditions.push(and(eq(schema.users.customerType, 'professional'), eq(schema.users.customerSubType, 'broker'))); @@ -186,7 +186,7 @@ export class BusinessListingService { async findBusinessesById(id: string, user: JwtUser): Promise { const conditions = []; - if (!user?.roles?.includes('ADMIN') ?? false) { + if (!user?.roles?.includes('ADMIN')) { conditions.push(or(eq(businesses.email, user?.username), ne(businesses.draft, true))); } conditions.push(sql`${businesses.id} = ${id}`); @@ -204,7 +204,7 @@ export class BusinessListingService { async findBusinessesByEmail(email: string, user: JwtUser): Promise { const conditions = []; conditions.push(eq(businesses.email, email)); - if (email !== user?.username && (!user?.roles?.includes('ADMIN') ?? false)) { + if (email !== user?.username && (!user?.roles?.includes('ADMIN'))) { conditions.push(ne(businesses.draft, true)); } const listings = (await this.conn diff --git a/bizmatch-server/src/listings/commercial-property.service.ts b/bizmatch-server/src/listings/commercial-property.service.ts index 7e23ece..fcb7e7d 100644 --- a/bizmatch-server/src/listings/commercial-property.service.ts +++ b/bizmatch-server/src/listings/commercial-property.service.ts @@ -49,7 +49,7 @@ export class CommercialPropertyService { if (criteria.title) { whereConditions.push(or(ilike(schema.commercials.title, `%${criteria.title}%`), ilike(schema.commercials.description, `%${criteria.title}%`))); } - if (!user?.roles?.includes('ADMIN') ?? false) { + if (!user?.roles?.includes('ADMIN')) { whereConditions.push(or(eq(commercials.email, user?.username), ne(commercials.draft, true))); } // whereConditions.push(and(eq(schema.users.customerType, 'professional'))); @@ -113,7 +113,7 @@ export class CommercialPropertyService { // #### Find by ID ######################################## async findCommercialPropertiesById(id: string, user: JwtUser): Promise { const conditions = []; - if (!user?.roles?.includes('ADMIN') ?? false) { + if (!user?.roles?.includes('ADMIN')) { conditions.push(or(eq(commercials.email, user?.username), ne(commercials.draft, true))); } conditions.push(sql`${commercials.id} = ${id}`); @@ -132,7 +132,7 @@ export class CommercialPropertyService { async findCommercialPropertiesByEmail(email: string, user: JwtUser): Promise { const conditions = []; conditions.push(eq(commercials.email, email)); - if (email !== user?.username && (!user?.roles?.includes('ADMIN') ?? false)) { + if (email !== user?.username && (!user?.roles?.includes('ADMIN'))) { conditions.push(ne(commercials.draft, true)); } const listings = (await this.conn diff --git a/bizmatch-server/src/listings/listings.module.ts b/bizmatch-server/src/listings/listings.module.ts index 9914b53..d922189 100644 --- a/bizmatch-server/src/listings/listings.module.ts +++ b/bizmatch-server/src/listings/listings.module.ts @@ -7,6 +7,7 @@ import { BrokerListingsController } from './broker-listings.controller'; import { BusinessListingsController } from './business-listings.controller'; import { CommercialPropertyListingsController } from './commercial-property-listings.controller'; +import { FirebaseAdminModule } from 'src/firebase-admin/firebase-admin.module'; import { GeoModule } from '../geo/geo.module'; import { GeoService } from '../geo/geo.service'; import { BusinessListingService } from './business-listing.service'; @@ -14,7 +15,7 @@ import { CommercialPropertyService } from './commercial-property.service'; import { UnknownListingsController } from './unknown-listings.controller'; @Module({ - imports: [DrizzleModule, AuthModule, GeoModule], + imports: [DrizzleModule, AuthModule, GeoModule,FirebaseAdminModule], controllers: [BusinessListingsController, CommercialPropertyListingsController, UnknownListingsController, BrokerListingsController], providers: [BusinessListingService, CommercialPropertyService, FileService, UserService, BusinessListingService, CommercialPropertyService, GeoService], exports: [BusinessListingService, CommercialPropertyService], diff --git a/bizmatch-server/src/log/log.module.ts b/bizmatch-server/src/log/log.module.ts index 1c6816b..c3e8d8a 100644 --- a/bizmatch-server/src/log/log.module.ts +++ b/bizmatch-server/src/log/log.module.ts @@ -1,7 +1,9 @@ import { Module } from '@nestjs/common'; +import { FirebaseAdminModule } from 'src/firebase-admin/firebase-admin.module'; import { LogController } from './log.controller'; @Module({ + imports: [FirebaseAdminModule], controllers: [LogController], }) export class LogModule {} diff --git a/bizmatch-server/src/mail/mail.module.ts b/bizmatch-server/src/mail/mail.module.ts index 9f20409..3a6c135 100644 --- a/bizmatch-server/src/mail/mail.module.ts +++ b/bizmatch-server/src/mail/mail.module.ts @@ -2,6 +2,7 @@ import { MailerModule } from '@nestjs-modules/mailer'; import { HandlebarsAdapter } from '@nestjs-modules/mailer/dist/adapters/handlebars.adapter'; import { Module } from '@nestjs/common'; import { join } from 'path'; +import { FirebaseAdminModule } from 'src/firebase-admin/firebase-admin.module'; import { DrizzleModule } from '../drizzle/drizzle.module'; import { FileService } from '../file/file.service'; import { GeoModule } from '../geo/geo.module'; @@ -18,6 +19,7 @@ import { MailService } from './mail.service'; DrizzleModule, UserModule, GeoModule, + FirebaseAdminModule, // ConfigModule.forFeature(mailConfig), // MailerModule.forRoot({ // transport: { diff --git a/bizmatch-server/src/main.ts b/bizmatch-server/src/main.ts index dffabdf..3ced0df 100644 --- a/bizmatch-server/src/main.ts +++ b/bizmatch-server/src/main.ts @@ -1,6 +1,5 @@ import { LoggerService } from '@nestjs/common'; import { NestFactory } from '@nestjs/core'; -import bodyParser from 'body-parser'; import express from 'express'; import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston'; import { AppModule } from './app.module'; @@ -12,7 +11,7 @@ async function bootstrap() { // const logger = app.get(WINSTON_MODULE_NEST_PROVIDER); const logger = app.get(WINSTON_MODULE_NEST_PROVIDER); app.useLogger(logger); - app.use('/bizmatch/payment/webhook', bodyParser.raw({ type: 'application/json' })); + //app.use('/bizmatch/payment/webhook', bodyParser.raw({ type: 'application/json' })); app.setGlobalPrefix('bizmatch'); app.enableCors({ diff --git a/bizmatch-server/src/payment/payment.module.ts b/bizmatch-server/src/payment/payment.module.ts index dfcbec9..2239ea7 100644 --- a/bizmatch-server/src/payment/payment.module.ts +++ b/bizmatch-server/src/payment/payment.module.ts @@ -1,6 +1,7 @@ import { Module } from '@nestjs/common'; import { AuthModule } from '../auth/auth.module'; -import { AuthService } from '../auth/auth.service'; + +import { FirebaseAdminModule } from 'src/firebase-admin/firebase-admin.module'; import { DrizzleModule } from '../drizzle/drizzle.module'; import { FileService } from '../file/file.service'; import { GeoService } from '../geo/geo.service'; @@ -12,8 +13,8 @@ import { PaymentController } from './payment.controller'; import { PaymentService } from './payment.service'; @Module({ - imports: [DrizzleModule, UserModule, MailModule, AuthModule], - providers: [PaymentService, UserService, MailService, FileService, GeoService, AuthService], + imports: [DrizzleModule, UserModule, MailModule, AuthModule,FirebaseAdminModule], + providers: [PaymentService, UserService, MailService, FileService, GeoService], controllers: [PaymentController], }) export class PaymentModule {} diff --git a/bizmatch-server/src/payment/payment.service.ts b/bizmatch-server/src/payment/payment.service.ts index 660a2b1..d626556 100644 --- a/bizmatch-server/src/payment/payment.service.ts +++ b/bizmatch-server/src/payment/payment.service.ts @@ -3,7 +3,6 @@ import { NodePgDatabase } from 'drizzle-orm/node-postgres/driver'; import { WINSTON_MODULE_PROVIDER } from 'nest-winston'; import Stripe from 'stripe'; import { Logger } from 'winston'; -import { AuthService } from '../auth/auth.service'; import * as schema from '../drizzle/schema'; import { PG_CONNECTION } from '../drizzle/schema'; import { MailService } from '../mail/mail.service'; @@ -22,7 +21,6 @@ export class PaymentService { @Inject(PG_CONNECTION) private conn: NodePgDatabase, private readonly userService: UserService, private readonly mailService: MailService, - private readonly authService: AuthService, ) { this.stripe = new Stripe(process.env.STRIPE_SECRET_KEY, { apiVersion: '2024-06-20', @@ -91,28 +89,28 @@ export class PaymentService { return this.stripe.webhooks.constructEvent(body, signature, process.env.STRIPE_WEBHOOK_SECRET!); } async handleCheckoutSessionCompleted(session: Stripe.Checkout.Session): Promise { - try { - const keycloakUsers = await this.authService.getUsers(); - const keycloakUser = keycloakUsers.find(u => u.email === session.customer_details.email); - const user = await this.userService.getUserByMail(session.customer_details.email, { - userId: keycloakUser.id, - firstname: keycloakUser.firstName, - lastname: keycloakUser.lastName, - username: keycloakUser.email, - roles: [], - }); - user.subscriptionId = session.subscription as string; - const subscription = await this.stripe.subscriptions.retrieve(user.subscriptionId); - user.customerType = 'professional'; - if (subscription.metadata['plan'] === 'Broker Plan') { - user.customerSubType = 'broker'; - } - user.subscriptionPlan = subscription.metadata['plan'] === 'Broker Plan' ? 'broker' : 'professional'; //session.metadata['subscriptionPlan'] as 'free' | 'professional' | 'broker'; - await this.userService.saveUser(user, false); - await this.mailService.sendSubscriptionConfirmation(user); - } catch (error) { - this.logger.error(error); - } + // try { + // const keycloakUsers = await this.authService.getUsers(); + // const keycloakUser = keycloakUsers.find(u => u.email === session.customer_details.email); + // const user = await this.userService.getUserByMail(session.customer_details.email, { + // userId: keycloakUser.id, + // firstname: keycloakUser.firstName, + // lastname: keycloakUser.lastName, + // username: keycloakUser.email, + // roles: [], + // }); + // user.subscriptionId = session.subscription as string; + // const subscription = await this.stripe.subscriptions.retrieve(user.subscriptionId); + // user.customerType = 'professional'; + // if (subscription.metadata['plan'] === 'Broker Plan') { + // user.customerSubType = 'broker'; + // } + // user.subscriptionPlan = subscription.metadata['plan'] === 'Broker Plan' ? 'broker' : 'professional'; //session.metadata['subscriptionPlan'] as 'free' | 'professional' | 'broker'; + // await this.userService.saveUser(user, false); + // await this.mailService.sendSubscriptionConfirmation(user); + // } catch (error) { + // this.logger.error(error); + // } } async getSubscription(email: string): Promise { const existingCustomers = await this.stripe.customers.list({ diff --git a/bizmatch-server/src/select-options/select-options.module.ts b/bizmatch-server/src/select-options/select-options.module.ts index 757ede0..4643434 100644 --- a/bizmatch-server/src/select-options/select-options.module.ts +++ b/bizmatch-server/src/select-options/select-options.module.ts @@ -1,8 +1,10 @@ import { Module } from '@nestjs/common'; +import { FirebaseAdminModule } from '../firebase-admin/firebase-admin.module'; import { SelectOptionsController } from './select-options.controller'; import { SelectOptionsService } from './select-options.service'; @Module({ + imports: [FirebaseAdminModule], controllers: [SelectOptionsController], providers: [SelectOptionsService], }) diff --git a/bizmatch-server/src/user/user.module.ts b/bizmatch-server/src/user/user.module.ts index 92a458a..fb34c98 100644 --- a/bizmatch-server/src/user/user.module.ts +++ b/bizmatch-server/src/user/user.module.ts @@ -1,4 +1,5 @@ import { Module } from '@nestjs/common'; +import { FirebaseAdminModule } from 'src/firebase-admin/firebase-admin.module'; import { DrizzleModule } from '../drizzle/drizzle.module'; import { FileService } from '../file/file.service'; import { GeoModule } from '../geo/geo.module'; @@ -7,7 +8,7 @@ import { UserController } from './user.controller'; import { UserService } from './user.service'; @Module({ - imports: [DrizzleModule, GeoModule], + imports: [DrizzleModule, GeoModule,FirebaseAdminModule], controllers: [UserController], providers: [UserService, FileService, GeoService], }) diff --git a/bizmatch/src/app/pages/details/details-business-listing/details-business-listing.component.ts b/bizmatch/src/app/pages/details/details-business-listing/details-business-listing.component.ts index a194145..36e5914 100644 --- a/bizmatch/src/app/pages/details/details-business-listing/details-business-listing.component.ts +++ b/bizmatch/src/app/pages/details/details-business-listing/details-business-listing.component.ts @@ -116,7 +116,7 @@ export class DetailsBusinessListingComponent extends BaseDetailsComponent { this.validationMessagesService.clearMessages(); // Löschen Sie alle bestehenden Validierungsnachrichten } isAdmin() { - return isAdmin(this.keycloakUser.email); //this.keycloakService.getUserRoles(true).includes('ADMIN'); + return isAdmin(this.keycloakUser?.email); //this.keycloakService.getUserRoles(true).includes('ADMIN'); } async mail() { try { diff --git a/bizmatch/src/app/pages/details/details-commercial-property-listing/details-commercial-property-listing.component.ts b/bizmatch/src/app/pages/details/details-commercial-property-listing/details-commercial-property-listing.component.ts index 81c447b..94c0a48 100644 --- a/bizmatch/src/app/pages/details/details-commercial-property-listing/details-commercial-property-listing.component.ts +++ b/bizmatch/src/app/pages/details/details-commercial-property-listing/details-commercial-property-listing.component.ts @@ -140,7 +140,7 @@ export class DetailsCommercialPropertyListingComponent extends BaseDetailsCompon }); } isAdmin() { - return isAdmin(this.keycloakUser.email); + return isAdmin(this.keycloakUser?.email); } async mail() { try { diff --git a/bizmatch/src/app/pages/subscription/account/account.component.html b/bizmatch/src/app/pages/subscription/account/account.component.html index 5c7e17b..98d0028 100644 --- a/bizmatch/src/app/pages/subscription/account/account.component.html +++ b/bizmatch/src/app/pages/subscription/account/account.component.html @@ -78,7 +78,7 @@ }@else{ - + } @if (isProfessional){ - + } @if (isProfessional){ diff --git a/bizmatch/src/app/services/auth.service.ts b/bizmatch/src/app/services/auth.service.ts index cccd351..89b548f 100644 --- a/bizmatch/src/app/services/auth.service.ts +++ b/bizmatch/src/app/services/auth.service.ts @@ -14,25 +14,46 @@ export class AuthService { private auth = getAuth(this.app); private http = inject(HttpClient); - // Registrierung mit Email und Passwort - async registerWithEmail(email: string, password: string): Promise { - const userCredential = await createUserWithEmailAndPassword(this.auth, email, password); - - // E-Mail-Verifizierung senden - if (userCredential.user) { - await sendEmailVerification(userCredential.user); - } - - // Token, RefreshToken und ggf. photoURL speichern - const token = await userCredential.user.getIdToken(); - localStorage.setItem('authToken', token); - localStorage.setItem('refreshToken', userCredential.user.refreshToken); - if (userCredential.user.photoURL) { - localStorage.setItem('photoURL', userCredential.user.photoURL); - } - - return userCredential; +// Registrierung mit Email und Passwort +async registerWithEmail(email: string, password: string): Promise { + // Bestimmen der aktuellen Umgebung/Domain für die Verifizierungs-URL + let verificationUrl = ''; + + // Prüfen der aktuellen Umgebung basierend auf dem Host + const currentHost = window.location.hostname; + + if (currentHost.includes('localhost')) { + verificationUrl = 'http://localhost:4200/email-authorized'; + } else if (currentHost.includes('dev.bizmatch.net')) { + verificationUrl = 'https://dev.bizmatch.net/email-authorized'; + } else { + verificationUrl = 'https://www.bizmatch.net/email-authorized'; } + + // ActionCode-Einstellungen mit der dynamischen URL + const actionCodeSettings = { + url: `${verificationUrl}?email=${email}`, + handleCodeInApp: true + }; + + // Benutzer erstellen + const userCredential = await createUserWithEmailAndPassword(this.auth, email, password); + + // E-Mail-Verifizierung mit den angepassten ActionCode-Einstellungen senden + if (userCredential.user) { + await sendEmailVerification(userCredential.user, actionCodeSettings); + } + + // Token, RefreshToken und ggf. photoURL speichern + const token = await userCredential.user.getIdToken(); + localStorage.setItem('authToken', token); + localStorage.setItem('refreshToken', userCredential.user.refreshToken); + if (userCredential.user.photoURL) { + localStorage.setItem('photoURL', userCredential.user.photoURL); + } + + return userCredential; +} // Login mit Email und Passwort loginWithEmail(email: string, password: string): Promise {