From 521e799bff38d7aacf10433132e9f326351a576a Mon Sep 17 00:00:00 2001 From: Andreas Knuth Date: Thu, 20 Feb 2025 17:51:54 -0600 Subject: [PATCH] Umstellung auf firebase --- bizmatch-server/.vscode/launch.json | 5 +- bizmatch-server/package.json | 6 +- bizmatch-server/src/app.controller.ts | 4 +- bizmatch-server/src/app.module.ts | 4 - bizmatch-server/src/auth/auth.controller.ts | 57 ++++++--- bizmatch-server/src/event/event.controller.ts | 4 +- bizmatch-server/src/geo/geo.controller.ts | 12 +- bizmatch-server/src/image/image.controller.ts | 14 +-- .../src/jwt-auth/admin-auth.guard.ts | 18 --- bizmatch-server/src/jwt-auth/auth.guard.ts | 27 +++++ .../src/jwt-auth/firebase-admin.ts | 16 +++ .../src/jwt-auth/jwt-auth.guard.ts | 18 --- .../src/jwt-auth/optional-auth.guard.ts | 29 +++++ .../src/jwt-auth/optional-jwt-auth.guard.ts | 13 --- .../listings/broker-listings.controller.ts | 4 +- .../listings/business-listings.controller.ts | 23 ++-- ...commercial-property-listings.controller.ts | 23 ++-- .../listings/unknown-listings.controller.ts | 4 +- bizmatch-server/src/log/log.controller.ts | 4 +- bizmatch-server/src/mail/mail.controller.ts | 9 +- .../src/payment/payment.controller.ts | 51 ++++---- .../select-options.controller.ts | 4 +- bizmatch-server/src/user/user.controller.ts | 30 ++--- bizmatch-server/src/user/user.service.ts | 2 +- bizmatch/package.json | 11 +- bizmatch/proxy.conf.json | 2 +- bizmatch/src/app/app.component.html | 6 +- bizmatch/src/app/app.routes.ts | 10 ++ .../email-authorized.component.html | 16 +++ .../email-authorized.component.ts | 47 ++++++++ .../login-register.component.html | 95 ++++++++++++--- .../login-register.component.ts | 18 ++- .../src/app/interceptors/auth.interceptor.ts | 11 +- .../src/app/pages/home/home.component.html | 4 +- .../account/account.component.html | 8 +- .../subscription/account/account.component.ts | 109 +++++++++--------- bizmatch/src/app/services/auth.service.ts | 15 ++- bizmatch/src/environments/environment.base.ts | 2 +- bizmatch/src/index.html | 3 +- bizmatch/src/styles.scss | 18 +++ 40 files changed, 495 insertions(+), 261 deletions(-) delete mode 100644 bizmatch-server/src/jwt-auth/admin-auth.guard.ts create mode 100644 bizmatch-server/src/jwt-auth/auth.guard.ts create mode 100644 bizmatch-server/src/jwt-auth/firebase-admin.ts delete mode 100644 bizmatch-server/src/jwt-auth/jwt-auth.guard.ts create mode 100644 bizmatch-server/src/jwt-auth/optional-auth.guard.ts delete mode 100644 bizmatch-server/src/jwt-auth/optional-jwt-auth.guard.ts create mode 100644 bizmatch/src/app/components/email-authorized/email-authorized.component.html create mode 100644 bizmatch/src/app/components/email-authorized/email-authorized.component.ts diff --git a/bizmatch-server/.vscode/launch.json b/bizmatch-server/.vscode/launch.json index d1bc0a5..8467ba2 100644 --- a/bizmatch-server/.vscode/launch.json +++ b/bizmatch-server/.vscode/launch.json @@ -13,7 +13,10 @@ "stopOnEntry": false, "console": "integratedTerminal", "env": { - "HOST_NAME": "localhost" + "HOST_NAME": "localhost", + "FIREBASE_PROJECT_ID": "bizmatch-net", + "FIREBASE_CLIENT_EMAIL": "firebase-adminsdk-fbsvc@bizmatch-net.iam.gserviceaccount.com", + "FIREBASE_PRIVATE_KEY": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCsOlDmhG0zi1zh\nlvobM8yAmLDR3P0F7mHcLyAga2rZm9MnPiGcmkoqRtDnxpZXio36PiyEgdKyhJFK\nP+jPJx1Zo/Ko9vb983oCGcz6MWgRKFXwLT4UJXjwjBdNDe/gcl52c+JJtZJR4bwD\n/bBgkoLzU9lF97pJoQypkSXytyxea6yrS2oEDs7SjW7z9JGFsoxFrt7zbMRb8tIs\nyCWe4I9YSgjSrwOw2uXpdrV0qjDkjx1TokuVJHDH9Vi8XhXDBx9y87Ja0hBoYDE9\nJJRLAa70qHQ9ytfdH/H0kucptC1JkdYGmLQHbohoPDuTU/C85JZvqIitwJ4YEH6Y\nfd+gEe5TAgMBAAECggEALrKDI/WNDFhBn1MJzl1dmhKMguKJ4lVPyF0ot1GYv5bu\nCipg/66f5FWeJ/Hi6qqBM3QvKuBuagPixwCMFbrTzO3UijaoIpQlJTOsrbu+rURE\nBOKnfdvpLkO1v6lDPJaWAUULepPWMAhmK6jZ7V1cTzCRbVSteHBH2CQoZ2Z+C71w\nyvzAIr6JRSg4mYbtHrQCXx9odPCRTdiRvxu5QtihiZGFSXnkTfhDNL1DKff7XHKF\nbOaDPumGtE7ypXr+0qyefg8xeTmXxdI4lPdqxd8XTpLFdMU8nW+/sEjdR40G8ikf\nt6nwyMh01YMMNi88t7ZoDvhpLALb4OqHBhDmyMdOWQKBgQDm5I0cqYX18jypC32G\nUhOdOou6IaZlVDNztZUhFPHPrP0P5Qg1PE5E5YybV7GVNXWiNwI/MPPF0JBce/Ie\ngJoXnuQ9kLh7cNZ432Jhz/Nmhytr6RGxoykAMT1fCuVLsTCfuK4e/aDAgVFJ84gS\nsB3TA62t2hak2MMntKoAQeDwWwKBgQC+9K+MRI/Vj1Xl7jwJ+adRQIvOssVz74ZE\nRYwIDZNRdk/c7c63WVHXASCRZbroGvqJgVfnmtwR6XJTnW3tkYqKUl5W9E+FSVbf\ng4aZs1oaVMA/IirVlRbJ4oCT+nDxPPuJ3ceJ4mBcODO82zXaC6pSFCvkpz9k9lc3\nUPlTLk1baQKBgFMbLqODbSFSeH0MErlXL5InMYXkeMT+IqriT/QhWsw6Yrfm4yZu\nN2nbCdocHWIsZNPnYtql3whzgpKXVlWeSlh4K4TxY0WjHr9RAFNeiyh7PKjRsjmz\nFZ3pG0LrZA7zjyHeUmX7OnIv2bd5fZ/kXkfGiiwKVJ4vG0deYtZG4BUDAoGBAJbI\nFRn4RW8HiHdPv37M8E5bXknvpbRfDTE5jVIKjioD9xnneZQTZmkUjcfhgU2nh+8t\n/+B0ypMmN81IgTXW94MzeSTGM0h22a8SZyVUlrA1/bucWiBeYik1vfubBLWoRqLd\nSaNZ6mbHRis5GPO8xFedb+9UFN2/Gq0mNkl1RUYJAoGBALqTxfdr4MXnG6Nhy22V\nWqui9nsHE5RMIvGYBnnq9Kqt8tUEkxB52YkBilx43q/TY4DRMDOeJk2krEbSN3AO\nguTE6BmZacamrt1HIdSAmJ1RktlVDRgIHXMBkBIumCsTCuXaZ+aEjuLOXJDIsIHZ\nEA9ftLrt1h1u+7QPI+E11Fmx\n-----END PRIVATE KEY-----" } // "preLaunchTask": "Start Stripe Listener" }, diff --git a/bizmatch-server/package.json b/bizmatch-server/package.json index e91030f..8a2a37c 100644 --- a/bizmatch-server/package.json +++ b/bizmatch-server/package.json @@ -41,6 +41,8 @@ "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", @@ -53,10 +55,6 @@ "nodemailer": "^6.9.10", "nodemailer-smtp-transport": "^2.7.4", "openai": "^4.52.6", - "passport": "^0.7.0", - "passport-google-oauth20": "^2.0.0", - "passport-jwt": "^4.0.1", - "passport-local": "^1.0.0", "pg": "^8.11.5", "pgvector": "^0.2.0", "reflect-metadata": "^0.2.0", diff --git a/bizmatch-server/src/app.controller.ts b/bizmatch-server/src/app.controller.ts index 91cb182..205fba0 100644 --- a/bizmatch-server/src/app.controller.ts +++ b/bizmatch-server/src/app.controller.ts @@ -1,7 +1,7 @@ import { Controller, Get, Request, UseGuards } from '@nestjs/common'; import { AppService } from './app.service'; import { AuthService } from './auth/auth.service'; -import { JwtAuthGuard } from './jwt-auth/jwt-auth.guard'; +import { AuthGuard } from './jwt-auth/auth.guard'; @Controller() export class AppController { @@ -10,7 +10,7 @@ export class AppController { private authService: AuthService, ) {} - @UseGuards(JwtAuthGuard) + @UseGuards(AuthGuard) @Get() getHello(@Request() req): string { return req.user; diff --git a/bizmatch-server/src/app.module.ts b/bizmatch-server/src/app.module.ts index 928533f..2282248 100644 --- a/bizmatch-server/src/app.module.ts +++ b/bizmatch-server/src/app.module.ts @@ -1,6 +1,5 @@ import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; -import { PassportModule } from '@nestjs/passport'; import { utilities as nestWinstonModuleUtilities, WinstonModule } from 'nest-winston'; import * as winston from 'winston'; import { AiModule } from './ai/ai.module'; @@ -16,7 +15,6 @@ import { LogModule } from './log/log.module'; import dotenvFlow from 'dotenv-flow'; import { EventModule } from './event/event.module'; -import { JwtStrategy } from './jwt.strategy'; import { MailModule } from './mail/mail.module'; import { APP_INTERCEPTOR } from '@nestjs/core'; @@ -66,7 +64,6 @@ console.log(JSON.stringify(process.env, null, 2)); ListingsModule, SelectOptionsModule, ImageModule, - PassportModule, AiModule, LogModule, // PaymentModule, @@ -76,7 +73,6 @@ console.log(JSON.stringify(process.env, null, 2)); providers: [ AppService, FileService, - JwtStrategy, { provide: APP_INTERCEPTOR, useClass: UserInterceptor, // Registriere den Interceptor global diff --git a/bizmatch-server/src/auth/auth.controller.ts b/bizmatch-server/src/auth/auth.controller.ts index f1fb6fa..f7a35b4 100644 --- a/bizmatch-server/src/auth/auth.controller.ts +++ b/bizmatch-server/src/auth/auth.controller.ts @@ -1,35 +1,62 @@ -import { Body, Controller, Get, Param, Put, UseGuards } from '@nestjs/common'; -import { JwtAuthGuard } from 'src/jwt-auth/jwt-auth.guard'; +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 { AdminAuthGuard } from '../jwt-auth/admin-auth.guard'; import { AuthService } from './auth.service'; @Controller('auth') export class AuthController { constructor(private readonly authService: AuthService) {} - @UseGuards(AdminAuthGuard) - @Get() - async getAccessToken(): Promise { - return await this.authService.getAccessToken(); - } + // @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(AdminAuthGuard) + // @Get('user/all') + // async getUsers(): Promise { + // return await this.authService.getUsers(); + // } - @UseGuards(JwtAuthGuard) + @UseGuards(AuthGuard) @Get('users/:userid') async getUser(@Param('userid') userId: string): Promise { return await this.authService.getUser(userId); } - @UseGuards(JwtAuthGuard) + @UseGuards(AuthGuard) @Put('users/:userid') async updateKeycloakUser(@Body() keycloakUser: KeycloakUser): Promise { return await this.authService.updateKeycloakUser(keycloakUser); } + + @Post('verify-email') + async verifyEmail(@Body('oobCode') oobCode: string, @Body('email') email: string) { + if (!oobCode || !email) { + throw new HttpException('oobCode and email are required', HttpStatus.BAD_REQUEST); + } + + try { + // Schritt 1: Hole den Benutzer anhand der E-Mail-Adresse + const userRecord = await admin.auth().getUserByEmail(email); + + if (userRecord.emailVerified) { + return { message: 'Email is already verified' }; + } + + // 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, { + emailVerified: true, + }); + + return { message: 'Email successfully verified' }; + } catch (error) { + 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 { diff --git a/bizmatch-server/src/event/event.controller.ts b/bizmatch-server/src/event/event.controller.ts index 13df3cb..cb155b4 100644 --- a/bizmatch-server/src/event/event.controller.ts +++ b/bizmatch-server/src/event/event.controller.ts @@ -1,6 +1,6 @@ import { Body, Controller, Headers, Post, UseGuards } from '@nestjs/common'; import { RealIp } from 'src/decorators/real-ip.decorator'; -import { OptionalJwtAuthGuard } from 'src/jwt-auth/optional-jwt-auth.guard'; +import { OptionalAuthGuard } from 'src/jwt-auth/optional-auth.guard'; import { ListingEvent } from 'src/models/db.model'; import { RealIpInfo } from 'src/models/main.model'; import { EventService } from './event.service'; @@ -9,7 +9,7 @@ import { EventService } from './event.service'; export class EventController { constructor(private eventService: EventService) {} - @UseGuards(OptionalJwtAuthGuard) + @UseGuards(OptionalAuthGuard) @Post() async createEvent( @Body() event: ListingEvent, // Struktur des Body-Objekts entsprechend anpassen diff --git a/bizmatch-server/src/geo/geo.controller.ts b/bizmatch-server/src/geo/geo.controller.ts index 500d061..6e705ca 100644 --- a/bizmatch-server/src/geo/geo.controller.ts +++ b/bizmatch-server/src/geo/geo.controller.ts @@ -1,6 +1,6 @@ import { Body, Controller, Get, Param, Post, UseGuards } from '@nestjs/common'; import { RealIp } from 'src/decorators/real-ip.decorator'; -import { OptionalJwtAuthGuard } from 'src/jwt-auth/optional-jwt-auth.guard'; +import { OptionalAuthGuard } from 'src/jwt-auth/optional-auth.guard'; import { RealIpInfo } from 'src/models/main.model'; import { CountyRequest } from 'src/models/server.model'; import { GeoService } from './geo.service'; @@ -9,31 +9,31 @@ import { GeoService } from './geo.service'; export class GeoController { constructor(private geoService: GeoService) {} - @UseGuards(OptionalJwtAuthGuard) + @UseGuards(OptionalAuthGuard) @Get(':prefix') findByPrefix(@Param('prefix') prefix: string): any { return this.geoService.findCitiesStartingWith(prefix); } - @UseGuards(OptionalJwtAuthGuard) + @UseGuards(OptionalAuthGuard) @Get('citiesandstates/:prefix') findByCitiesAndStatesByPrefix(@Param('prefix') prefix: string): any { return this.geoService.findCitiesAndStatesStartingWith(prefix); } - @UseGuards(OptionalJwtAuthGuard) + @UseGuards(OptionalAuthGuard) @Get(':prefix/:state') findByPrefixAndState(@Param('prefix') prefix: string, @Param('state') state: string): any { return this.geoService.findCitiesStartingWith(prefix, state); } - @UseGuards(OptionalJwtAuthGuard) + @UseGuards(OptionalAuthGuard) @Post('counties') findByPrefixAndStates(@Body() countyRequest: CountyRequest): any { return this.geoService.findCountiesStartingWith(countyRequest.prefix, countyRequest.states); } - @UseGuards(OptionalJwtAuthGuard) + @UseGuards(OptionalAuthGuard) @Get('ipinfo/georesult/wysiwyg') async fetchIpAndGeoLocation(@RealIp() ipInfo: RealIpInfo): Promise { return await this.geoService.fetchIpAndGeoLocation(ipInfo); diff --git a/bizmatch-server/src/image/image.controller.ts b/bizmatch-server/src/image/image.controller.ts index 93a4233..2766e17 100644 --- a/bizmatch-server/src/image/image.controller.ts +++ b/bizmatch-server/src/image/image.controller.ts @@ -1,7 +1,7 @@ import { Controller, Delete, Inject, Param, Post, UploadedFile, UseGuards, UseInterceptors } from '@nestjs/common'; import { FileInterceptor } from '@nestjs/platform-express'; import { WINSTON_MODULE_PROVIDER } from 'nest-winston'; -import { JwtAuthGuard } from 'src/jwt-auth/jwt-auth.guard'; +import { AuthGuard } from 'src/jwt-auth/auth.guard'; import { Logger } from 'winston'; import { FileService } from '../file/file.service'; import { CommercialPropertyService } from '../listings/commercial-property.service'; @@ -18,14 +18,14 @@ export class ImageController { // ############ // Property // ############ - @UseGuards(JwtAuthGuard) + @UseGuards(AuthGuard) @Post('uploadPropertyPicture/:imagePath/:serial') @UseInterceptors(FileInterceptor('file')) async uploadPropertyPicture(@UploadedFile() file: Express.Multer.File, @Param('imagePath') imagePath: string, @Param('serial') serial: string) { const imagename = await this.fileService.storePropertyPicture(file, imagePath, serial); await this.listingService.addImage(imagePath, serial, imagename); } - @UseGuards(JwtAuthGuard) + @UseGuards(AuthGuard) @Delete('propertyPicture/:imagePath/:serial/:imagename') async deletePropertyImagesById(@Param('imagePath') imagePath: string, @Param('serial') serial: string, @Param('imagename') imagename: string): Promise { this.fileService.deleteImage(`pictures/property/${imagePath}/${serial}/${imagename}`); @@ -34,13 +34,13 @@ export class ImageController { // ############ // Profile // ############ - @UseGuards(JwtAuthGuard) + @UseGuards(AuthGuard) @Post('uploadProfile/:email') @UseInterceptors(FileInterceptor('file')) async uploadProfile(@UploadedFile() file: Express.Multer.File, @Param('email') adjustedEmail: string) { await this.fileService.storeProfilePicture(file, adjustedEmail); } - @UseGuards(JwtAuthGuard) + @UseGuards(AuthGuard) @Delete('profile/:email/') async deleteProfileImagesById(@Param('email') email: string): Promise { this.fileService.deleteImage(`pictures/profile/${email}.avif`); @@ -48,13 +48,13 @@ export class ImageController { // ############ // Logo // ############ - @UseGuards(JwtAuthGuard) + @UseGuards(AuthGuard) @Post('uploadCompanyLogo/:email') @UseInterceptors(FileInterceptor('file')) async uploadCompanyLogo(@UploadedFile() file: Express.Multer.File, @Param('email') adjustedEmail: string) { await this.fileService.storeCompanyLogo(file, adjustedEmail); } - @UseGuards(JwtAuthGuard) + @UseGuards(AuthGuard) @Delete('logo/:email/') async deleteLogoImagesById(@Param('email') adjustedEmail: string): Promise { this.fileService.deleteImage(`pictures/logo/${adjustedEmail}.avif`); diff --git a/bizmatch-server/src/jwt-auth/admin-auth.guard.ts b/bizmatch-server/src/jwt-auth/admin-auth.guard.ts deleted file mode 100644 index 045fad5..0000000 --- a/bizmatch-server/src/jwt-auth/admin-auth.guard.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common'; -import { AuthGuard } from '@nestjs/passport'; - -@Injectable() -export class AdminAuthGuard extends AuthGuard('jwt') implements CanActivate { - canActivate(context: ExecutionContext) { - // Add your custom authentication logic here - // for example, call super.logIn(request) to establish a session. - return super.canActivate(context); - } - handleRequest(err, user, info) { - // You can throw an exception based on either "info" or "err" arguments - if (err || !user || !user.roles.includes('ADMIN')) { - throw err || new UnauthorizedException(info); - } - return user; - } -} diff --git a/bizmatch-server/src/jwt-auth/auth.guard.ts b/bizmatch-server/src/jwt-auth/auth.guard.ts new file mode 100644 index 0000000..344478c --- /dev/null +++ b/bizmatch-server/src/jwt-auth/auth.guard.ts @@ -0,0 +1,27 @@ +import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common'; +import admin from './firebase-admin'; + +@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 new file mode 100644 index 0000000..1af1bd1 --- /dev/null +++ b/bizmatch-server/src/jwt-auth/firebase-admin.ts @@ -0,0 +1,16 @@ +import * as admin from 'firebase-admin'; +import { ServiceAccount } from 'firebase-admin'; + +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), + }); +} + +export default admin; diff --git a/bizmatch-server/src/jwt-auth/jwt-auth.guard.ts b/bizmatch-server/src/jwt-auth/jwt-auth.guard.ts deleted file mode 100644 index 45d4ac7..0000000 --- a/bizmatch-server/src/jwt-auth/jwt-auth.guard.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common'; -import { AuthGuard } from '@nestjs/passport'; - -@Injectable() -export class JwtAuthGuard extends AuthGuard('jwt') implements CanActivate { - canActivate(context: ExecutionContext) { - // Add your custom authentication logic here - // for example, call super.logIn(request) to establish a session. - return super.canActivate(context); - } - handleRequest(err, user, info) { - // You can throw an exception based on either "info" or "err" arguments - if (err || !user) { - throw err || new UnauthorizedException(info); - } - return user; - } -} diff --git a/bizmatch-server/src/jwt-auth/optional-auth.guard.ts b/bizmatch-server/src/jwt-auth/optional-auth.guard.ts new file mode 100644 index 0000000..369dedf --- /dev/null +++ b/bizmatch-server/src/jwt-auth/optional-auth.guard.ts @@ -0,0 +1,29 @@ +import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; +import admin from './firebase-admin'; + +@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-auth/optional-jwt-auth.guard.ts b/bizmatch-server/src/jwt-auth/optional-jwt-auth.guard.ts deleted file mode 100644 index 26dcccd..0000000 --- a/bizmatch-server/src/jwt-auth/optional-jwt-auth.guard.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { AuthGuard } from '@nestjs/passport'; - -@Injectable() -export class OptionalJwtAuthGuard extends AuthGuard('jwt') { - handleRequest(err, user, info) { - // Wenn der Benutzer nicht authentifiziert ist, aber kein Fehler vorliegt, geben Sie null zurück - if (err || !user) { - return null; - } - return user; - } -} diff --git a/bizmatch-server/src/listings/broker-listings.controller.ts b/bizmatch-server/src/listings/broker-listings.controller.ts index f50aa63..c4511ac 100644 --- a/bizmatch-server/src/listings/broker-listings.controller.ts +++ b/bizmatch-server/src/listings/broker-listings.controller.ts @@ -1,6 +1,6 @@ import { Body, Controller, Inject, Post, UseGuards } from '@nestjs/common'; import { WINSTON_MODULE_PROVIDER } from 'nest-winston'; -import { OptionalJwtAuthGuard } from 'src/jwt-auth/optional-jwt-auth.guard'; +import { OptionalAuthGuard } from 'src/jwt-auth/optional-auth.guard'; import { UserListingCriteria } from 'src/models/main.model'; import { Logger } from 'winston'; import { UserService } from '../user/user.service'; @@ -12,7 +12,7 @@ export class BrokerListingsController { @Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger, ) {} - @UseGuards(OptionalJwtAuthGuard) + @UseGuards(OptionalAuthGuard) @Post('search') async find(@Body() criteria: UserListingCriteria): Promise { return await this.userService.searchUserListings(criteria); diff --git a/bizmatch-server/src/listings/business-listings.controller.ts b/bizmatch-server/src/listings/business-listings.controller.ts index 3a1591a..84d93d9 100644 --- a/bizmatch-server/src/listings/business-listings.controller.ts +++ b/bizmatch-server/src/listings/business-listings.controller.ts @@ -1,8 +1,9 @@ import { Body, Controller, Delete, Get, Inject, Param, Post, Put, Request, UseGuards } from '@nestjs/common'; import { WINSTON_MODULE_PROVIDER } from 'nest-winston'; +import { AuthGuard } from 'src/jwt-auth/auth.guard'; import { Logger } from 'winston'; -import { JwtAuthGuard } from '../jwt-auth/jwt-auth.guard'; -import { OptionalJwtAuthGuard } from '../jwt-auth/optional-jwt-auth.guard'; + +import { OptionalAuthGuard } from 'src/jwt-auth/optional-auth.guard'; import { BusinessListing } from '../models/db.model'; import { BusinessListingCriteria, JwtUser } from '../models/main.model'; import { BusinessListingService } from './business-listing.service'; @@ -14,52 +15,52 @@ export class BusinessListingsController { @Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger, ) {} - @UseGuards(OptionalJwtAuthGuard) + @UseGuards(OptionalAuthGuard) @Get(':id') async findById(@Request() req, @Param('id') id: string): Promise { return await this.listingsService.findBusinessesById(id, req.user as JwtUser); } - @UseGuards(JwtAuthGuard) + @UseGuards(AuthGuard) @Get('favorites/all') async findFavorites(@Request() req): Promise { return await this.listingsService.findFavoriteListings(req.user as JwtUser); } - @UseGuards(OptionalJwtAuthGuard) + @UseGuards(OptionalAuthGuard) @Get('user/:userid') async findByUserId(@Request() req, @Param('userid') userid: string): Promise { return await this.listingsService.findBusinessesByEmail(userid, req.user as JwtUser); } - @UseGuards(OptionalJwtAuthGuard) + @UseGuards(OptionalAuthGuard) @Post('find') async find(@Request() req, @Body() criteria: BusinessListingCriteria): Promise { return await this.listingsService.searchBusinessListings(criteria, req.user as JwtUser); } - @UseGuards(OptionalJwtAuthGuard) + @UseGuards(OptionalAuthGuard) @Post('findTotal') async findTotal(@Request() req, @Body() criteria: BusinessListingCriteria): Promise { return await this.listingsService.getBusinessListingsCount(criteria, req.user as JwtUser); } - @UseGuards(OptionalJwtAuthGuard) + @UseGuards(OptionalAuthGuard) @Post() async create(@Body() listing: any) { return await this.listingsService.createListing(listing); } - @UseGuards(OptionalJwtAuthGuard) + @UseGuards(OptionalAuthGuard) @Put() async update(@Body() listing: any) { return await this.listingsService.updateBusinessListing(listing.id, listing); } - @UseGuards(OptionalJwtAuthGuard) + @UseGuards(OptionalAuthGuard) @Delete('listing/:id') async deleteById(@Param('id') id: string) { await this.listingsService.deleteListing(id); } - @UseGuards(JwtAuthGuard) + @UseGuards(AuthGuard) @Delete('favorite/:id') async deleteFavorite(@Request() req, @Param('id') id: string) { await this.listingsService.deleteFavorite(id, req.user as JwtUser); diff --git a/bizmatch-server/src/listings/commercial-property-listings.controller.ts b/bizmatch-server/src/listings/commercial-property-listings.controller.ts index 217deaa..3e98787 100644 --- a/bizmatch-server/src/listings/commercial-property-listings.controller.ts +++ b/bizmatch-server/src/listings/commercial-property-listings.controller.ts @@ -2,8 +2,9 @@ import { Body, Controller, Delete, Get, Inject, Param, Post, Put, Request, UseGu import { WINSTON_MODULE_PROVIDER } from 'nest-winston'; import { Logger } from 'winston'; import { FileService } from '../file/file.service'; -import { JwtAuthGuard } from '../jwt-auth/jwt-auth.guard'; -import { OptionalJwtAuthGuard } from '../jwt-auth/optional-jwt-auth.guard'; + +import { AuthGuard } from 'src/jwt-auth/auth.guard'; +import { OptionalAuthGuard } from 'src/jwt-auth/optional-auth.guard'; import { CommercialPropertyListing } from '../models/db.model'; import { CommercialPropertyListingCriteria, JwtUser } from '../models/main.model'; import { CommercialPropertyService } from './commercial-property.service'; @@ -16,54 +17,54 @@ export class CommercialPropertyListingsController { @Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger, ) {} - @UseGuards(OptionalJwtAuthGuard) + @UseGuards(OptionalAuthGuard) @Get(':id') async findById(@Request() req, @Param('id') id: string): Promise { return await this.listingsService.findCommercialPropertiesById(id, req.user as JwtUser); } - @UseGuards(JwtAuthGuard) + @UseGuards(AuthGuard) @Get('favorites/all') async findFavorites(@Request() req): Promise { return await this.listingsService.findFavoriteListings(req.user as JwtUser); } - @UseGuards(OptionalJwtAuthGuard) + @UseGuards(OptionalAuthGuard) @Get('user/:email') async findByEmail(@Request() req, @Param('email') email: string): Promise { return await this.listingsService.findCommercialPropertiesByEmail(email, req.user as JwtUser); } - @UseGuards(OptionalJwtAuthGuard) + @UseGuards(OptionalAuthGuard) @Post('find') async find(@Request() req, @Body() criteria: CommercialPropertyListingCriteria): Promise { return await this.listingsService.searchCommercialProperties(criteria, req.user as JwtUser); } - @UseGuards(OptionalJwtAuthGuard) + @UseGuards(OptionalAuthGuard) @Post('findTotal') async findTotal(@Request() req, @Body() criteria: CommercialPropertyListingCriteria): Promise { return await this.listingsService.getCommercialPropertiesCount(criteria, req.user as JwtUser); } - @UseGuards(OptionalJwtAuthGuard) + @UseGuards(OptionalAuthGuard) @Post() async create(@Body() listing: any) { return await this.listingsService.createListing(listing); } - @UseGuards(OptionalJwtAuthGuard) + @UseGuards(OptionalAuthGuard) @Put() async update(@Body() listing: any) { return await this.listingsService.updateCommercialPropertyListing(listing.id, listing); } - @UseGuards(OptionalJwtAuthGuard) + @UseGuards(OptionalAuthGuard) @Delete('listing/:id/:imagePath') async deleteById(@Param('id') id: string, @Param('imagePath') imagePath: string) { await this.listingsService.deleteListing(id); this.fileService.deleteDirectoryIfExists(imagePath); } - @UseGuards(JwtAuthGuard) + @UseGuards(AuthGuard) @Delete('favorite/:id') async deleteFavorite(@Request() req, @Param('id') id: string) { await this.listingsService.deleteFavorite(id, req.user as JwtUser); diff --git a/bizmatch-server/src/listings/unknown-listings.controller.ts b/bizmatch-server/src/listings/unknown-listings.controller.ts index 3ec87ee..d85a8ae 100644 --- a/bizmatch-server/src/listings/unknown-listings.controller.ts +++ b/bizmatch-server/src/listings/unknown-listings.controller.ts @@ -1,7 +1,7 @@ import { Controller, Get, Inject, Param, Request, UseGuards } from '@nestjs/common'; import { WINSTON_MODULE_PROVIDER } from 'nest-winston'; +import { OptionalAuthGuard } from 'src/jwt-auth/optional-auth.guard'; import { Logger } from 'winston'; -import { OptionalJwtAuthGuard } from '../jwt-auth/optional-jwt-auth.guard'; import { BusinessListingService } from './business-listing.service'; import { CommercialPropertyService } from './commercial-property.service'; @@ -13,7 +13,7 @@ export class UnknownListingsController { private readonly propertyListingsService: CommercialPropertyService, ) {} - @UseGuards(OptionalJwtAuthGuard) + @UseGuards(OptionalAuthGuard) @Get(':id') async findById(@Request() req, @Param('id') id: string): Promise { try { diff --git a/bizmatch-server/src/log/log.controller.ts b/bizmatch-server/src/log/log.controller.ts index f9289a6..b5f2719 100644 --- a/bizmatch-server/src/log/log.controller.ts +++ b/bizmatch-server/src/log/log.controller.ts @@ -1,13 +1,13 @@ import { Body, Controller, Inject, Post, Request, UseGuards } from '@nestjs/common'; import { WINSTON_MODULE_PROVIDER } from 'nest-winston'; +import { OptionalAuthGuard } from 'src/jwt-auth/optional-auth.guard'; import { Logger } from 'winston'; -import { OptionalJwtAuthGuard } from '../jwt-auth/optional-jwt-auth.guard'; import { LogMessage } from '../models/main.model'; @Controller('log') export class LogController { constructor(@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger) {} - @UseGuards(OptionalJwtAuthGuard) + @UseGuards(OptionalAuthGuard) @Post() log(@Request() req, @Body() message: LogMessage) { if (message.severity === 'info') { diff --git a/bizmatch-server/src/mail/mail.controller.ts b/bizmatch-server/src/mail/mail.controller.ts index d8cf345..570f4ab 100644 --- a/bizmatch-server/src/mail/mail.controller.ts +++ b/bizmatch-server/src/mail/mail.controller.ts @@ -1,5 +1,6 @@ import { Body, Controller, Post, UseGuards } from '@nestjs/common'; -import { OptionalJwtAuthGuard } from 'src/jwt-auth/optional-jwt-auth.guard'; + +import { OptionalAuthGuard } from 'src/jwt-auth/optional-auth.guard'; import { ShareByEMail, User } from 'src/models/db.model'; import { ErrorResponse, MailInfo } from '../models/main.model'; import { MailService } from './mail.service'; @@ -8,7 +9,7 @@ import { MailService } from './mail.service'; export class MailController { constructor(private mailService: MailService) {} - @UseGuards(OptionalJwtAuthGuard) + @UseGuards(OptionalAuthGuard) @Post() async sendEMail(@Body() mailInfo: MailInfo): Promise { if (mailInfo.listing) { @@ -18,13 +19,13 @@ export class MailController { } } - @UseGuards(OptionalJwtAuthGuard) + @UseGuards(OptionalAuthGuard) @Post('subscriptionConfirmation') async sendSubscriptionConfirmation(@Body() user: User): Promise { return await this.mailService.sendSubscriptionConfirmation(user); } - @UseGuards(OptionalJwtAuthGuard) + @UseGuards(OptionalAuthGuard) @Post('send2Friend') async send2Friend(@Body() shareByEMail: ShareByEMail): Promise { return await this.mailService.send2Friend(shareByEMail); diff --git a/bizmatch-server/src/payment/payment.controller.ts b/bizmatch-server/src/payment/payment.controller.ts index 9b6fcd0..f3486b4 100644 --- a/bizmatch-server/src/payment/payment.controller.ts +++ b/bizmatch-server/src/payment/payment.controller.ts @@ -1,7 +1,6 @@ -import { Body, Controller, Delete, Get, HttpCode, HttpException, HttpStatus, Param, Post, Req, Res, UseGuards } from '@nestjs/common'; +import { Body, Controller, Get, HttpException, HttpStatus, Param, Post, Req, Res, UseGuards } from '@nestjs/common'; import { Request, Response } from 'express'; -import { AdminAuthGuard } from 'src/jwt-auth/admin-auth.guard'; -import { OptionalJwtAuthGuard } from 'src/jwt-auth/optional-jwt-auth.guard'; +import { OptionalAuthGuard } from 'src/jwt-auth/optional-auth.guard'; import { Checkout } from 'src/models/main.model'; import Stripe from 'stripe'; import { PaymentService } from './payment.service'; @@ -15,25 +14,25 @@ export class PaymentController { // return this.paymentService.createSubscription(subscriptionData); // } - @UseGuards(AdminAuthGuard) - @Get('user/all') - async getAllStripeCustomer(): Promise { - return await this.paymentService.getAllStripeCustomer(); - } + // @UseGuards(AdminAuthGuard) + // @Get('user/all') + // async getAllStripeCustomer(): Promise { + // return await this.paymentService.getAllStripeCustomer(); + // } - @UseGuards(AdminAuthGuard) - @Get('subscription/all') - async getAllStripeSubscriptions(): Promise { - return await this.paymentService.getAllStripeSubscriptions(); - } + // @UseGuards(AdminAuthGuard) + // @Get('subscription/all') + // async getAllStripeSubscriptions(): Promise { + // return await this.paymentService.getAllStripeSubscriptions(); + // } - @UseGuards(AdminAuthGuard) - @Get('paymentmethod/:email') - async getStripePaymentMethods(@Param('email') email: string): Promise { - return await this.paymentService.getStripePaymentMethod(email); - } + // @UseGuards(AdminAuthGuard) + // @Get('paymentmethod/:email') + // async getStripePaymentMethods(@Param('email') email: string): Promise { + // return await this.paymentService.getStripePaymentMethod(email); + // } - @UseGuards(OptionalJwtAuthGuard) + @UseGuards(OptionalAuthGuard) @Post('create-checkout-session') async createCheckoutSession(@Body() checkout: Checkout) { return await this.paymentService.createCheckoutSession(checkout); @@ -59,7 +58,7 @@ export class PaymentController { } } - @UseGuards(OptionalJwtAuthGuard) + @UseGuards(OptionalAuthGuard) @Get('subscriptions/:email') async findSubscriptionsById(@Param('email') email: string): Promise { return await this.paymentService.getSubscription(email); @@ -68,10 +67,10 @@ export class PaymentController { * Endpoint zum Löschen eines Stripe-Kunden. * Beispiel: DELETE /stripe/customer/cus_12345 */ - @UseGuards(AdminAuthGuard) - @Delete('customer/:id') - @HttpCode(HttpStatus.NO_CONTENT) - async deleteCustomer(@Param('id') customerId: string): Promise { - await this.paymentService.deleteCustomerCompletely(customerId); - } + // @UseGuards(AdminAuthGuard) + // @Delete('customer/:id') + // @HttpCode(HttpStatus.NO_CONTENT) + // async deleteCustomer(@Param('id') customerId: string): Promise { + // await this.paymentService.deleteCustomerCompletely(customerId); + // } } diff --git a/bizmatch-server/src/select-options/select-options.controller.ts b/bizmatch-server/src/select-options/select-options.controller.ts index bcbb02e..819d2ed 100644 --- a/bizmatch-server/src/select-options/select-options.controller.ts +++ b/bizmatch-server/src/select-options/select-options.controller.ts @@ -1,12 +1,12 @@ import { Controller, Get, UseGuards } from '@nestjs/common'; -import { OptionalJwtAuthGuard } from 'src/jwt-auth/optional-jwt-auth.guard'; +import { OptionalAuthGuard } from 'src/jwt-auth/optional-auth.guard'; import { SelectOptionsService } from './select-options.service'; @Controller('select-options') export class SelectOptionsController { constructor(private selectOptionsService: SelectOptionsService) {} - @UseGuards(OptionalJwtAuthGuard) + @UseGuards(OptionalAuthGuard) @Get() getSelectOption(): any { return { diff --git a/bizmatch-server/src/user/user.controller.ts b/bizmatch-server/src/user/user.controller.ts index 8dff143..8500330 100644 --- a/bizmatch-server/src/user/user.controller.ts +++ b/bizmatch-server/src/user/user.controller.ts @@ -1,11 +1,11 @@ import { BadRequestException, Body, Controller, Get, Inject, Param, Post, Query, Request, UseGuards } from '@nestjs/common'; import { WINSTON_MODULE_PROVIDER } from 'nest-winston'; -import { AdminAuthGuard } from 'src/jwt-auth/admin-auth.guard'; import { Logger } from 'winston'; import { ZodError } from 'zod'; import { FileService } from '../file/file.service'; -import { JwtAuthGuard } from '../jwt-auth/jwt-auth.guard'; -import { OptionalJwtAuthGuard } from '../jwt-auth/optional-jwt-auth.guard'; + +import { AuthGuard } from 'src/jwt-auth/auth.guard'; +import { OptionalAuthGuard } from 'src/jwt-auth/optional-auth.guard'; import { User } from '../models/db.model'; import { JwtUser, Subscription, UserListingCriteria } from '../models/main.model'; import { UserService } from './user.service'; @@ -18,26 +18,26 @@ export class UserController { @Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger, ) {} - @UseGuards(OptionalJwtAuthGuard) + @UseGuards(OptionalAuthGuard) @Get() async findByMail(@Request() req, @Query('mail') mail: string): Promise { const user = await this.userService.getUserByMail(mail, req.user as JwtUser); return user; } - @UseGuards(OptionalJwtAuthGuard) + @UseGuards(OptionalAuthGuard) @Get(':id') async findById(@Param('id') id: string): Promise { const user = await this.userService.getUserById(id); return user; } - @UseGuards(AdminAuthGuard) - @Get('user/all') - async getAllUser(): Promise { - return await this.userService.getAllUser(); - } + // @UseGuards(AdminAuthGuard) + // @Get('user/all') + // async getAllUser(): Promise { + // return await this.userService.getAllUser(); + // } - @UseGuards(OptionalJwtAuthGuard) + @UseGuards(OptionalAuthGuard) @Post() async save(@Body() user: any): Promise { try { @@ -57,27 +57,27 @@ export class UserController { } } - @UseGuards(OptionalJwtAuthGuard) + @UseGuards(OptionalAuthGuard) @Post('guaranteed') async saveGuaranteed(@Body() user: any): Promise { const savedUser = await this.userService.saveUser(user, false); return savedUser; } - @UseGuards(OptionalJwtAuthGuard) + @UseGuards(OptionalAuthGuard) @Post('search') async find(@Body() criteria: UserListingCriteria): Promise<{ results: User[]; totalCount: number }> { const foundUsers = await this.userService.searchUserListings(criteria); return foundUsers; } - @UseGuards(OptionalJwtAuthGuard) + @UseGuards(OptionalAuthGuard) @Post('findTotal') async findTotal(@Body() criteria: UserListingCriteria): Promise { return await this.userService.getUserListingsCount(criteria); } - @UseGuards(JwtAuthGuard) + @UseGuards(AuthGuard) @Get('subscriptions/:id') async findSubscriptionsById(@Param('id') id: string): Promise { const subscriptions = []; diff --git a/bizmatch-server/src/user/user.service.ts b/bizmatch-server/src/user/user.service.ts index 2d797ba..011e20b 100644 --- a/bizmatch-server/src/user/user.service.ts +++ b/bizmatch-server/src/user/user.service.ts @@ -106,7 +106,7 @@ export class UserService { .from(schema.users) .where(sql`email = ${email}`)) as User[]; if (users.length === 0) { - const user: User = { id: undefined, customerType: 'buyer', ...createDefaultUser(email, jwtuser.firstname, jwtuser.lastname, null) }; + const user: User = { id: undefined, customerType: 'professional', ...createDefaultUser(email, jwtuser.firstname ? jwtuser.firstname : '', jwtuser.lastname ? jwtuser.lastname : '', null) }; const u = await this.saveUser(user, false); return u; } else { diff --git a/bizmatch/package.json b/bizmatch/package.json index 4b2decb..f5cd830 100644 --- a/bizmatch/package.json +++ b/bizmatch/package.json @@ -26,11 +26,10 @@ "@angular/router": "^18.1.3", "@bluehalo/ngx-leaflet": "^18.0.2", "@fortawesome/angular-fontawesome": "^0.15.0", - "@fortawesome/fontawesome-free": "^6.5.2", - "@fortawesome/fontawesome-svg-core": "^6.5.2", - "@fortawesome/free-brands-svg-icons": "^6.5.2", - "@fortawesome/free-regular-svg-icons": "^6.5.2", - "@fortawesome/free-solid-svg-icons": "^6.5.2", + "@fortawesome/fontawesome-free": "^6.7.2", + "@fortawesome/fontawesome-svg-core": "^6.7.2", + "@fortawesome/free-brands-svg-icons": "^6.7.2", + "@fortawesome/free-solid-svg-icons": "^6.7.2", "@ng-select/ng-select": "^13.4.1", "@ngneat/until-destroy": "^10.0.0", "@stripe/stripe-js": "^4.3.0", @@ -77,4 +76,4 @@ "tailwindcss": "^3.4.4", "typescript": "~5.4.5" } -} \ No newline at end of file +} diff --git a/bizmatch/proxy.conf.json b/bizmatch/proxy.conf.json index cdc833d..4fc2167 100644 --- a/bizmatch/proxy.conf.json +++ b/bizmatch/proxy.conf.json @@ -1,5 +1,5 @@ { - "/api": { + "/bizmatch": { "target": "http://localhost:3000", "secure": false, "changeOrigin": true, diff --git a/bizmatch/src/app/app.component.html b/bizmatch/src/app/app.component.html index 0523329..d877810 100644 --- a/bizmatch/src/app/app.component.html +++ b/bizmatch/src/app/app.component.html @@ -1,9 +1,9 @@ -
- @if (actualRoute !=='home' && actualRoute !=='login'){ +
+ @if (actualRoute !=='home' && actualRoute !=='login' && actualRoute!=='emailVerification' && actualRoute!=='email-authorized'){
} -
+
diff --git a/bizmatch/src/app/app.routes.ts b/bizmatch/src/app/app.routes.ts index b4f912c..3b0bdd6 100644 --- a/bizmatch/src/app/app.routes.ts +++ b/bizmatch/src/app/app.routes.ts @@ -2,6 +2,8 @@ import { Routes } from '@angular/router'; import { LogoutComponent } from './components/logout/logout.component'; import { NotFoundComponent } from './components/not-found/not-found.component'; +import { EmailAuthorizedComponent } from './components/email-authorized/email-authorized.component'; +import { EmailVerificationComponent } from './components/email-verification/email-verification.component'; import { LoginRegisterComponent } from './components/login-register/login-register.component'; import { AuthGuard } from './guards/auth.guard'; import { ListingCategoryGuard } from './guards/listing-category.guard'; @@ -147,6 +149,14 @@ export const routes: Routes = [ path: 'pricing', component: PricingComponent, }, + { + path: 'emailVerification', + component: EmailVerificationComponent, + }, + { + path: 'email-authorized', + component: EmailAuthorizedComponent, + }, { path: 'pricingOverview', component: PricingComponent, diff --git a/bizmatch/src/app/components/email-authorized/email-authorized.component.html b/bizmatch/src/app/components/email-authorized/email-authorized.component.html new file mode 100644 index 0000000..95eefc4 --- /dev/null +++ b/bizmatch/src/app/components/email-authorized/email-authorized.component.html @@ -0,0 +1,16 @@ +
+ +

Verifying your email...

+
+ + +

Your email has been verified

+ + Follow this link to access your Account Page +
+ + +

Verification failed

+

{{ errorMessage }}

+
+
diff --git a/bizmatch/src/app/components/email-authorized/email-authorized.component.ts b/bizmatch/src/app/components/email-authorized/email-authorized.component.ts new file mode 100644 index 0000000..395f0d2 --- /dev/null +++ b/bizmatch/src/app/components/email-authorized/email-authorized.component.ts @@ -0,0 +1,47 @@ +import { CommonModule } from '@angular/common'; +import { HttpClient } from '@angular/common/http'; +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute, RouterModule } from '@angular/router'; +import { environment } from '../../../environments/environment'; +import { AuthService } from '../../services/auth.service'; +import { UserService } from '../../services/user.service'; + +@Component({ + selector: 'app-email-authorized', + standalone: true, + imports: [CommonModule, RouterModule], + templateUrl: './email-authorized.component.html', +}) +export class EmailAuthorizedComponent implements OnInit { + verificationStatus: 'pending' | 'success' | 'error' = 'pending'; + errorMessage: string | null = null; + + constructor(private route: ActivatedRoute, private http: HttpClient, private authService: AuthService, private userService: UserService) {} + + ngOnInit(): void { + const oobCode = this.route.snapshot.queryParamMap.get('oobCode'); + const email = this.route.snapshot.queryParamMap.get('email'); + const mode = this.route.snapshot.queryParamMap.get('mode'); + + if (mode === 'verifyEmail' && oobCode && email) { + this.verifyEmail(oobCode, email); + } else { + this.verificationStatus = 'error'; + this.errorMessage = 'Invalid verification link'; + } + } + + private verifyEmail(oobCode: string, email: string): void { + this.http.post(`${environment.apiBaseUrl}/bizmatch/auth/verify-email`, { oobCode, email }).subscribe({ + next: async () => { + this.verificationStatus = 'success'; + await this.authService.refreshToken(); + const user = await this.userService.getByMail(email); + }, + error: err => { + this.verificationStatus = 'error'; + this.errorMessage = err.error?.message || 'Verification failed'; + }, + }); + } +} diff --git a/bizmatch/src/app/components/login-register/login-register.component.html b/bizmatch/src/app/components/login-register/login-register.component.html index c3f5c6a..123ff74 100644 --- a/bizmatch/src/app/components/login-register/login-register.component.html +++ b/bizmatch/src/app/components/login-register/login-register.component.html @@ -1,7 +1,7 @@
-
-

- {{ isLoginMode ? 'Login' : 'Registrierung' }} +
+

+ {{ isLoginMode ? 'Login' : 'Sign Up' }}

@@ -18,34 +18,99 @@
- - + +
+ + +
- - + +
+ + +
- - + +
+ + +
-
+ + +
{{ errorMessage }}
- +
- - or - + + or +
- + + +
diff --git a/bizmatch/src/app/components/login-register/login-register.component.ts b/bizmatch/src/app/components/login-register/login-register.component.ts index 35664e4..f14d1e3 100644 --- a/bizmatch/src/app/components/login-register/login-register.component.ts +++ b/bizmatch/src/app/components/login-register/login-register.component.ts @@ -2,12 +2,14 @@ import { CommonModule } from '@angular/common'; import { Component } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { ActivatedRoute, Router } from '@angular/router'; +import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; +import { faArrowRight, faEnvelope, faLock, faUserPlus } from '@fortawesome/free-solid-svg-icons'; import { AuthService } from '../../services/auth.service'; - +import { LoadingService } from '../../services/loading.service'; @Component({ selector: 'app-login-register', standalone: true, - imports: [CommonModule, FormsModule], + imports: [CommonModule, FormsModule, FontAwesomeModule], templateUrl: './login-register.component.html', }) export class LoginRegisterComponent { @@ -16,8 +18,11 @@ export class LoginRegisterComponent { confirmPassword: string = ''; isLoginMode: boolean = true; // true: Login, false: Registration errorMessage: string = ''; - - constructor(private authService: AuthService, private route: ActivatedRoute, private router: Router) {} + envelope = faEnvelope; + lock = faLock; + arrowRight = faArrowRight; + userplus = faUserPlus; + constructor(private authService: AuthService, private route: ActivatedRoute, private router: Router, private loadingService: LoadingService) {} ngOnInit(): void { // Set mode based on query parameter "mode" @@ -53,12 +58,16 @@ export class LoginRegisterComponent { this.errorMessage = 'Passwords do not match.'; return; } + this.loadingService.startLoading('googleAuth'); this.authService .registerWithEmail(this.email, this.password) .then(userCredential => { console.log('Successfully registered:', userCredential); + this.loadingService.stopLoading('googleAuth'); + this.router.navigate(['emailVerification']); }) .catch(error => { + this.loadingService.stopLoading('googleAuth'); console.error('Error during registration:', error); if (error.code === 'auth/email-already-in-use') { this.errorMessage = 'This email address is already in use. Please try logging in.'; @@ -76,6 +85,7 @@ export class LoginRegisterComponent { .loginWithGoogle() .then(userCredential => { console.log('Successfully logged in with Google:', userCredential); + this.router.navigate([`home`]); }) .catch(error => { console.error('Error during Google login:', error); diff --git a/bizmatch/src/app/interceptors/auth.interceptor.ts b/bizmatch/src/app/interceptors/auth.interceptor.ts index d60f564..5317069 100644 --- a/bizmatch/src/app/interceptors/auth.interceptor.ts +++ b/bizmatch/src/app/interceptors/auth.interceptor.ts @@ -1,8 +1,8 @@ -// auth.interceptor.ts import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { Observable, from } from 'rxjs'; import { switchMap } from 'rxjs/operators'; +import { environment } from '../../environments/environment'; import { AuthService } from '../services/auth.service'; @Injectable() @@ -10,6 +10,15 @@ export class AuthInterceptor implements HttpInterceptor { constructor(private authService: AuthService) {} intercept(req: HttpRequest, next: HttpHandler): Observable> { + // Prüfe, ob die Anfrage an die apiBaseUrl gerichtet ist + const isApiRequest = req.url.startsWith(environment.apiBaseUrl); + + if (!isApiRequest) { + // Wenn es keine API-Anfrage ist, leite die Anfrage unverändert weiter + return next.handle(req); + } + + // Wenn es eine API-Anfrage ist, füge den Token hinzu (falls vorhanden) return from(this.authService.getToken()).pipe( switchMap(token => { if (token) { diff --git a/bizmatch/src/app/pages/home/home.component.html b/bizmatch/src/app/pages/home/home.component.html index 6388e3f..e0bb896 100644 --- a/bizmatch/src/app/pages/home/home.component.html +++ b/bizmatch/src/app/pages/home/home.component.html @@ -1,4 +1,4 @@ -
+
Logo

-
+
diff --git a/bizmatch/src/app/pages/subscription/account/account.component.html b/bizmatch/src/app/pages/subscription/account/account.component.html index e985160..5c7e17b 100644 --- a/bizmatch/src/app/pages/subscription/account/account.component.html +++ b/bizmatch/src/app/pages/subscription/account/account.component.html @@ -226,7 +226,7 @@
-
+ +
}
diff --git a/bizmatch/src/app/pages/subscription/account/account.component.ts b/bizmatch/src/app/pages/subscription/account/account.component.ts index 8e2ba40..773fcbb 100644 --- a/bizmatch/src/app/pages/subscription/account/account.component.ts +++ b/bizmatch/src/app/pages/subscription/account/account.component.ts @@ -10,7 +10,7 @@ import { ImageCropperComponent } from 'ngx-image-cropper'; import { QuillModule } from 'ngx-quill'; import { lastValueFrom } from 'rxjs'; import { User } from '../../../../../../bizmatch-server/src/models/db.model'; -import { AutoCompleteCompleteEvent, Invoice, StripeSubscription, UploadParams, ValidationMessage, emailToDirName } from '../../../../../../bizmatch-server/src/models/main.model'; +import { AutoCompleteCompleteEvent, Invoice, UploadParams, ValidationMessage, emailToDirName } from '../../../../../../bizmatch-server/src/models/main.model'; import { environment } from '../../../../environments/environment'; import { ConfirmationComponent } from '../../../components/confirmation/confirmation.component'; import { ConfirmationService } from '../../../components/confirmation/confirmation.service'; @@ -31,10 +31,9 @@ import { ImageService } from '../../../services/image.service'; import { LoadingService } from '../../../services/loading.service'; import { SelectOptionsService } from '../../../services/select-options.service'; import { SharedService } from '../../../services/shared.service'; -import { SubscriptionsService } from '../../../services/subscriptions.service'; import { UserService } from '../../../services/user.service'; import { SharedModule } from '../../../shared/shared/shared.module'; -import { checkAndUpdate, isAdmin, map2User } from '../../../utils/utils'; +import { isAdmin, map2User } from '../../../utils/utils'; import { TOOLBAR_OPTIONS } from '../../utils/defaults'; @Component({ selector: 'app-account', @@ -79,7 +78,7 @@ export class AccountComponent { customerSubTypeOptions: Array<{ value: string; label: string }> = []; tooltipTargetAreasServed = 'tooltip-areasServed'; tooltipTargetLicensed = 'tooltip-licensedIn'; - subscriptions: StripeSubscription[] | any[]; + // subscriptions: StripeSubscription[] | any[]; constructor( public userService: UserService, private geoService: GeoService, @@ -94,7 +93,7 @@ export class AccountComponent { private sharedService: SharedService, private titleCasePipe: TitleCasePipe, private validationMessagesService: ValidationMessagesService, - private subscriptionService: SubscriptionsService, + // private subscriptionService: SubscriptionsService, private datePipe: DatePipe, private router: Router, private authService: AuthService, @@ -112,57 +111,57 @@ export class AccountComponent { this.user = await this.userService.getByMail(email); } - this.subscriptions = await lastValueFrom(this.subscriptionService.getAllSubscriptions(this.user.email)); - await this.synchronizeSubscriptions(this.subscriptions); + // this.subscriptions = await lastValueFrom(this.subscriptionService.getAllSubscriptions(this.user.email)); + // await this.synchronizeSubscriptions(this.subscriptions); this.profileUrl = this.user.hasProfile ? `${this.env.imageBaseUrl}/pictures/profile/${emailToDirName(this.user.email)}.avif?_ts=${new Date().getTime()}` : `/assets/images/placeholder.png`; this.companyLogoUrl = this.user.hasCompanyLogo ? `${this.env.imageBaseUrl}/pictures/logo/${emailToDirName(this.user.email)}.avif?_ts=${new Date().getTime()}` : `/assets/images/placeholder.png`; this.customerTypeOptions = this.selectOptions.customerTypes - .filter(ct => ct.value === 'buyer' || ct.value === 'seller' || this.user.customerType === 'professional') + // .filter(ct => ct.value === 'buyer' || ct.value === 'seller' || this.user.customerType === 'professional') .map(type => ({ value: type.value, label: this.titleCasePipe.transform(type.name), })); this.customerSubTypeOptions = this.selectOptions.customerSubTypes - .filter(ct => ct.value !== 'broker' || this.user.customerSubType === 'broker') + // .filter(ct => ct.value !== 'broker' || this.user.customerSubType === 'broker') .map(type => ({ value: type.value, label: this.titleCasePipe.transform(type.name), })); } - async synchronizeSubscriptions(subscriptions: StripeSubscription[]) { - let changed = false; - if (this.isAdmin()) { - return; - } - if (this.subscriptions.length === 0) { - if (!this.user.subscriptionPlan) { - this.router.navigate(['pricing']); - } else { - this.subscriptions = [{ ended_at: null, start_date: Math.floor(new Date(this.user.created).getTime() / 1000), status: null, metadata: { plan: 'Free Plan' } }]; - changed = checkAndUpdate(changed, this.user.customerType !== 'buyer' && this.user.customerType !== 'seller', () => (this.user.customerType = 'buyer')); - changed = checkAndUpdate(changed, !!this.user.customerSubType, () => (this.user.customerSubType = null)); - changed = checkAndUpdate(changed, this.user.subscriptionPlan !== 'free', () => (this.user.subscriptionPlan = 'free')); - changed = checkAndUpdate(changed, !!this.user.subscriptionId, () => (this.user.subscriptionId = null)); - } - } else { - const subscription = subscriptions[0]; - changed = checkAndUpdate(changed, subscription.metadata['plan'] === 'Broker Plan' && this.user.customerType !== 'professional', () => (this.user.customerType = 'professional')); - changed = checkAndUpdate(changed, subscription.metadata['plan'] === 'Broker Plan' && this.user.customerSubType !== 'broker', () => (this.user.customerSubType = 'broker')); - changed = checkAndUpdate(changed, subscription.metadata['plan'] === 'Broker Plan' && this.user.subscriptionPlan !== 'broker', () => (this.user.subscriptionPlan = 'broker')); - changed = checkAndUpdate(changed, subscription.metadata['plan'] === 'Broker Plan' && !this.user.subscriptionId, () => (this.user.subscriptionId = subscription.id)); + // async synchronizeSubscriptions(subscriptions: StripeSubscription[]) { + // let changed = false; + // if (this.isAdmin()) { + // return; + // } + // if (this.subscriptions.length === 0) { + // if (!this.user.subscriptionPlan) { + // this.router.navigate(['pricing']); + // } else { + // this.subscriptions = [{ ended_at: null, start_date: Math.floor(new Date(this.user.created).getTime() / 1000), status: null, metadata: { plan: 'Free Plan' } }]; + // changed = checkAndUpdate(changed, this.user.customerType !== 'buyer' && this.user.customerType !== 'seller', () => (this.user.customerType = 'buyer')); + // changed = checkAndUpdate(changed, !!this.user.customerSubType, () => (this.user.customerSubType = null)); + // changed = checkAndUpdate(changed, this.user.subscriptionPlan !== 'free', () => (this.user.subscriptionPlan = 'free')); + // changed = checkAndUpdate(changed, !!this.user.subscriptionId, () => (this.user.subscriptionId = null)); + // } + // } else { + // const subscription = subscriptions[0]; + // changed = checkAndUpdate(changed, subscription.metadata['plan'] === 'Broker Plan' && this.user.customerType !== 'professional', () => (this.user.customerType = 'professional')); + // changed = checkAndUpdate(changed, subscription.metadata['plan'] === 'Broker Plan' && this.user.customerSubType !== 'broker', () => (this.user.customerSubType = 'broker')); + // changed = checkAndUpdate(changed, subscription.metadata['plan'] === 'Broker Plan' && this.user.subscriptionPlan !== 'broker', () => (this.user.subscriptionPlan = 'broker')); + // changed = checkAndUpdate(changed, subscription.metadata['plan'] === 'Broker Plan' && !this.user.subscriptionId, () => (this.user.subscriptionId = subscription.id)); - changed = checkAndUpdate(changed, subscription.metadata['plan'] === 'Professional Plan' && this.user.customerType !== 'professional', () => (this.user.customerType = 'professional')); - changed = checkAndUpdate(changed, subscription.metadata['plan'] === 'Professional Plan' && this.user.subscriptionPlan !== 'professional', () => (this.user.subscriptionPlan = 'professional')); - changed = checkAndUpdate(changed, subscription.metadata['plan'] === 'Professional Plan' && this.user.subscriptionId !== 'professional', () => (this.user.subscriptionId = subscription.id)); - } - if (changed) { - await this.userService.saveGuaranteed(this.user); - this.cdref.detectChanges(); - this.cdref.markForCheck(); - } - } + // changed = checkAndUpdate(changed, subscription.metadata['plan'] === 'Professional Plan' && this.user.customerType !== 'professional', () => (this.user.customerType = 'professional')); + // changed = checkAndUpdate(changed, subscription.metadata['plan'] === 'Professional Plan' && this.user.subscriptionPlan !== 'professional', () => (this.user.subscriptionPlan = 'professional')); + // changed = checkAndUpdate(changed, subscription.metadata['plan'] === 'Professional Plan' && this.user.subscriptionId !== 'professional', () => (this.user.subscriptionId = subscription.id)); + // } + // if (changed) { + // await this.userService.saveGuaranteed(this.user); + // this.cdref.detectChanges(); + // this.cdref.markForCheck(); + // } + // } ngOnDestroy() { this.validationMessagesService.clearMessages(); // Löschen Sie alle bestehenden Validierungsnachrichten @@ -273,19 +272,19 @@ export class AccountComponent { this.user.areasServed[index].county = null; } } - getLevel(i: number) { - return this.subscriptions[i].metadata.plan; - } - getStartDate(i: number) { - return this.datePipe.transform(new Date(this.subscriptions[i].start_date * 1000)); - } - getEndDate(i: number) { - return this.subscriptions[i].status === 'trialing' ? this.datePipe.transform(new Date(this.subscriptions[i].current_period_end * 1000)) : '---'; - } - getNextSettlement(i: number) { - return this.subscriptions[i].status === 'active' ? this.datePipe.transform(new Date(this.subscriptions[i].current_period_end * 1000)) : '---'; - } - getStatus(i: number) { - return this.subscriptions[i].status ? this.subscriptions[i].status : ''; - } + // getLevel(i: number) { + // return this.subscriptions[i].metadata.plan; + // } + // getStartDate(i: number) { + // return this.datePipe.transform(new Date(this.subscriptions[i].start_date * 1000)); + // } + // getEndDate(i: number) { + // return this.subscriptions[i].status === 'trialing' ? this.datePipe.transform(new Date(this.subscriptions[i].current_period_end * 1000)) : '---'; + // } + // getNextSettlement(i: number) { + // return this.subscriptions[i].status === 'active' ? this.datePipe.transform(new Date(this.subscriptions[i].current_period_end * 1000)) : '---'; + // } + // getStatus(i: number) { + // return this.subscriptions[i].status ? this.subscriptions[i].status : ''; + // } } diff --git a/bizmatch/src/app/services/auth.service.ts b/bizmatch/src/app/services/auth.service.ts index 1f25561..cccd351 100644 --- a/bizmatch/src/app/services/auth.service.ts +++ b/bizmatch/src/app/services/auth.service.ts @@ -86,7 +86,16 @@ export class AuthService { return false; } } - + private isEMailVerified(token: string): boolean { + try { + const payloadBase64 = token.split('.')[1]; + const payloadJson = atob(payloadBase64.replace(/-/g, '+').replace(/_/g, '/')); + const payload = JSON.parse(payloadJson); + return payload.email_verified; + } catch (e) { + return false; + } + } // Versucht, mit dem RefreshToken einen neuen Access Token zu erhalten async refreshToken(): Promise { const storedRefreshToken = localStorage.getItem('refreshToken'); @@ -122,7 +131,9 @@ export class AuthService { */ async getToken(): Promise { const token = localStorage.getItem('authToken'); - if (token && this.isTokenValid(token)) { + if (token && !this.isEMailVerified(token)) { + return null; + } else if (token && this.isTokenValid(token) && this.isEMailVerified(token)) { return token; } else { return await this.refreshToken(); diff --git a/bizmatch/src/environments/environment.base.ts b/bizmatch/src/environments/environment.base.ts index 67b6833..f440557 100644 --- a/bizmatch/src/environments/environment.base.ts +++ b/bizmatch/src/environments/environment.base.ts @@ -1,7 +1,7 @@ export const hostname = window.location.hostname; export const environment_base = { // apiBaseUrl: 'http://localhost:3000', - apiBaseUrl: `http://${hostname}:3000`, + apiBaseUrl: `http://${hostname}:4200`, imageBaseUrl: 'https://dev.bizmatch.net', buildVersion: '', mailinfoUrl: 'https://dev.bizmatch.net', diff --git a/bizmatch/src/index.html b/bizmatch/src/index.html index d56735d..4d33945 100644 --- a/bizmatch/src/index.html +++ b/bizmatch/src/index.html @@ -22,10 +22,11 @@ + - + diff --git a/bizmatch/src/styles.scss b/bizmatch/src/styles.scss index 560b287..523b5c0 100644 --- a/bizmatch/src/styles.scss +++ b/bizmatch/src/styles.scss @@ -56,6 +56,24 @@ textarea { -moz-osx-font-smoothing: grayscale; } +.wrapper { + min-height: 100%; + display: flex; + flex-direction: column; +} + +header { + height: 64px; /* Feste Höhe */ +} + +main { + flex: 1 0 auto; /* Füllt den verfügbaren Platz */ +} + +footer { + flex-shrink: 0; /* Verhindert Schrumpfen */ +} + *:focus, .p-focus { box-shadow: none !important;