From c7e74f699d95efafe14184d1e2e1e0eb30606e9d Mon Sep 17 00:00:00 2001 From: Ingo Sternberg Date: Tue, 9 Jul 2024 17:23:41 +0200 Subject: [PATCH] feat: act like it is secure Signed-off-by: Ingo Sternberg #230 --- .env.example | 1 + apps/frontend/Dockerfile | 2 + .../src/environments/environment.prod.ts | 1 + apps/frontend/src/environments/environment.ts | 1 + apps/frontend/src/types/types.d.ts | 1 + docker-compose.yml | 1 + .../interfaces/app-environment.interface.ts | 5 + .../lib/services/authentication.service.ts | 221 +++++++++--------- 8 files changed, 126 insertions(+), 107 deletions(-) diff --git a/.env.example b/.env.example index c250c1c9..335fb2b2 100644 --- a/.env.example +++ b/.env.example @@ -20,6 +20,7 @@ DATABASE_URL=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:$ # Frontend XD_API_URL=http://${BACKEND_HOST}:${BACKEND_PORT} +SECRET_KEY=SecretKey # Swagger # Just the subpath of the URL where the Swagger UI will be available diff --git a/apps/frontend/Dockerfile b/apps/frontend/Dockerfile index 0b10f9d0..c0f29392 100644 --- a/apps/frontend/Dockerfile +++ b/apps/frontend/Dockerfile @@ -18,9 +18,11 @@ RUN pnpm install --frozen-lockfile FROM base as builder ARG XD_API_URL +ARG XD_SECRET_KEY ENV NX_DEAMON="false" ENV XD_API_URL=$XD_API_URL +ENV XD_SECRET_KEY=$XD_SECRET_KEY COPY --from=installer /usr/src/app/node_modules ./node_modules diff --git a/apps/frontend/src/environments/environment.prod.ts b/apps/frontend/src/environments/environment.prod.ts index 4ada9b48..b60d247d 100644 --- a/apps/frontend/src/environments/environment.prod.ts +++ b/apps/frontend/src/environments/environment.prod.ts @@ -3,4 +3,5 @@ import { IAppEnvironment } from 'common-frontend-models'; export const environment: IAppEnvironment = { production: true, apiUrl: process.env.XD_API_URL, + secretKey: process.env.XD_SECRET_KEY, }; diff --git a/apps/frontend/src/environments/environment.ts b/apps/frontend/src/environments/environment.ts index f90ce1c3..5cbf89f7 100644 --- a/apps/frontend/src/environments/environment.ts +++ b/apps/frontend/src/environments/environment.ts @@ -3,4 +3,5 @@ import { IAppEnvironment } from 'common-frontend-models'; export const environment: IAppEnvironment = { production: false, apiUrl: process.env.XD_API_URL, + secretKey: process.env.XD_SECRET_KEY, }; diff --git a/apps/frontend/src/types/types.d.ts b/apps/frontend/src/types/types.d.ts index 1d6ecfdc..0d2f374b 100644 --- a/apps/frontend/src/types/types.d.ts +++ b/apps/frontend/src/types/types.d.ts @@ -1,5 +1,6 @@ declare const process: { env: { XD_API_URL: string; + XD_SECRET_KEY: string; }; }; diff --git a/docker-compose.yml b/docker-compose.yml index 11ce1d21..05c456b0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -54,6 +54,7 @@ services: dockerfile: apps/frontend/Dockerfile args: XD_API_URL: localhost:3000 + XD_SECRET_KEY: XceleratorDemoAppSecretKey restart: unless-stopped ports: - '4200:80' diff --git a/libs/common/frontend/models/src/lib/interfaces/app-environment.interface.ts b/libs/common/frontend/models/src/lib/interfaces/app-environment.interface.ts index fe17f325..a88218eb 100644 --- a/libs/common/frontend/models/src/lib/interfaces/app-environment.interface.ts +++ b/libs/common/frontend/models/src/lib/interfaces/app-environment.interface.ts @@ -11,4 +11,9 @@ export interface IAppEnvironment { * The url of the main api of the frontend application */ apiUrl: string; + + /** + * the secretKey for user authentication + */ + secretKey: string; } diff --git a/libs/common/frontend/models/src/lib/services/authentication.service.ts b/libs/common/frontend/models/src/lib/services/authentication.service.ts index 0a23a943..a2e9de49 100644 --- a/libs/common/frontend/models/src/lib/services/authentication.service.ts +++ b/libs/common/frontend/models/src/lib/services/authentication.service.ts @@ -1,113 +1,120 @@ -import { Injectable } from '@angular/core'; +import { inject, Injectable } from '@angular/core'; import * as CryptoJS from 'crypto-js'; +import { APP_CONFIG } from '../tokens'; + @Injectable({ - providedIn: 'root', + providedIn: 'root', }) export class AuthenticationService { - - private readonly tokenKey = 'authToken'; - private readonly tokenExpirationTime = 1000 * 60 * 60; - - // this is a demo app, don't actually store these things in the code - private readonly secretKey = 'XceleratorDemoApp'; - private readonly userDataBase = [ - { userMail: 'alex.lorenz.1@gmx.de', password: 'siemens' }, - { userMail: 'lama.l.rajjo@fau.de', password: 'siemens' }, - { userMail: 'ingo.sternberg@fau.de', password: 'siemens' }, - { userMail: 'cecilia.betancourt@fau.de', password: 'siemens' }, - { userMail: 'jonas@mennicke.com', password: 'siemens' }, - { userMail: 'krugm03@gmail.com', password: 'siemens' }, - { userMail: 'nasirsharaz@gmail.com', password: 'siemens' }, - { userMail: 'patrick.m.schmidt@fau.de', password: 'siemens' }, - { userMail: 'saleb2k@gmail.com', password: 'siemens' }, - { userMail: 'd.schmidt@campus.tu-berlin.de', password: 'siemens' }, - { userMail: 'david_schmidt_privat@outlook.com', password: 'siemens' }, - { userMail: 'igor.milovanovic@siemens.com', password: 'siemens' }, - { userMail: 'buchner@group.riehle.org', password: 'siemens' }, - { userMail: 'riehle@group.riehle.org', password: 'siemens' }, - ]; - - login(email: string, password: string) { - - if (this.checkCredentials(email, password)) { - const token = this.generateToken(email, password); - localStorage.setItem(this.tokenKey, token); - return true; - } - - return false; - } - - logout() { - localStorage.removeItem(this.tokenKey); - } - - isLoggedIn() { - const token = localStorage.getItem(this.tokenKey); - if (!token) { - return false; - } - - const userData = this.decryptToken(token); - if (!userData) { - return false; - } - - if (!this.checkCredentials(userData.userMail, userData.password)) { - return false; - } - - if (new Date().getTime() - userData.time > this.tokenExpirationTime) { - this.logout(); - return false; - } - - return true; - } - - getUserMail() { - const token = localStorage.getItem(this.tokenKey); - if (!token) { - return false; - } - - const userData = this.decryptToken(token); - if (!userData) { - return false; - } - - return userData.userMail; - } - - private checkCredentials(email: string, password: string) { - return this.userDataBase.some(user => user.userMail === email && user.password === password); - } - - private generateToken(email: string, password: string) { - // ':' is not allowed in the email, so we can use it as a separator - const time = new Date().getTime(); - const tokenData = `${email}:${password}:${time}`; - return CryptoJS.AES.encrypt(tokenData, this.secretKey).toString(); - } - - private decryptToken(token: string) { - try { - const bytes = CryptoJS.AES.decrypt(token, this.secretKey); - const decryptedData = bytes.toString(CryptoJS.enc.Utf8); - - // the token should have the format email:password:time, where password could contain : - const firstColonIndex = decryptedData.indexOf(':'); - const userMail = decryptedData.slice(0, firstColonIndex); - - const lastColonIndex = decryptedData.lastIndexOf(':'); - const password = decryptedData.slice(firstColonIndex + 1, lastColonIndex); - const time = decryptedData.slice(lastColonIndex + 1); - - return { userMail: userMail, password: password, time: parseInt(time) }; - } catch (error) { - return null; - } - } - + private readonly tokenKey = 'authToken'; + private readonly tokenExpirationTime = 1000 * 60 * 60; + + private readonly secretKey = inject(APP_CONFIG).secretKey; + + // this is a demo app, don't actually store these in the code + private readonly userDataBase = [ + 'U2FsdGVkX1/9RSRUE4j4qGAQ48gPSr8iLF242snE0hwJmOsluUk/hPzDuXgcgkSGNa8YCrm2GW8lxAxwCCDq1w==', + 'U2FsdGVkX1+ifYJJA7x2MVnkOiqx8DywlVyKYOzPM6HPftzUdoGrg8X2c9i2f2ofZKF8I7sKZGFhuJQtVqBeGA==', + 'U2FsdGVkX18yC09eQaBPI4LllwUbBAJTYWZoDmBROU4tOaHfB6n9+FCWMOUYGhbVJCV370aVvszr6YaFywC5AQ==', + 'U2FsdGVkX19BSG6V31LwzAGRkfYg2lFJ73Y2azUbT2E/qhUXXoqnjzsmBSUuOrJJFapTiYrAiVLzD5aVlAwSyA==', + 'U2FsdGVkX198xdVbzi9qI8e7hsQb6R7Tqr+H0TC0rXt/0fHYDzFAYZYvPNYbj9v/9kz9gIDcJ0ltqr0ykYoVfQ==', + 'U2FsdGVkX18OT9osxUQklFvha2WUhpKHMABSczBqbZM/aIwOpsOrmP+nLM40iKhQy93uRHYH9cDSRiRKXysYJg==', + 'U2FsdGVkX18Q9+qDzKhowDLaeKfC3JYs7bchhCrc1e1XHfme0WnWT8fmrjJYTPhlVcty1uRhxUSplmSqPbHI1g==', + 'U2FsdGVkX19jahQtTcRewdjzN9VinNEA5O5GAc42IcuMJ8u9qq2SoPlSZAVQKq0S55a33vFT0gE5KdB6sMw1kA==', + 'U2FsdGVkX18z8urywB/i1cCYvUs9QtcmiGIT7fMr8/romPQswAtQT0br6/Q5pyd5dyt9MmiIRzj5/3Hpbe0DEg==', + 'U2FsdGVkX19scTGfMHvQ9zQU2DnoQ3+OUHcZneou5CBC3oEiVGOk9j9tSG62qMXoGLV/kEPNRB0os3RrEaQkAK91JwiyJXkIK/OZQibRvnE=', + 'U2FsdGVkX19ZUtXbsjHxFCLMsf80K1lEUoavBNrqfrDLlKCqQ8lvvB69/oj3pCOeqS5krHHa2dfp+KtEfwzhYey6WeWBsg02XhnHdPL6uhQ=', + 'U2FsdGVkX1+TsxHJS6SCdDRXDrSmcSTLYFUt1eU+vOt3akqoeakFakr2kyxXUloxeCvEGtK54eDyPGssF2o+7xkWxPW+kHKgMdVk7aD7Ma4=', + 'U2FsdGVkX1/7vbTIhc5KsLi+Mv81zqX2905hWUa0mlZhkcxiTKcddtBR42SnW3/CmenCGGpQJOFJmyOZtFMCuA==', + 'U2FsdGVkX1805OkL41+N6QhaS38fAmqxTPsEUg1j30Y/x6sV9iyfQHYxMYhEtb/B231AfFTzPar9cF6N4AU6UQ==', + ]; + + login(email: string, password: string) { + if (this.checkCredentials(email, password)) { + const token = this.generateToken(email, password); + localStorage.setItem(this.tokenKey, token); + return true; + } + + return false; + } + + logout() { + localStorage.removeItem(this.tokenKey); + } + + isLoggedIn() { + const token = localStorage.getItem(this.tokenKey); + if (!token) { + return false; + } + + const userData = this.decryptToken(token); + if (!userData) { + return false; + } + + if (!this.checkCredentials(userData.userMail, userData.password)) { + return false; + } + + if (new Date().getTime() - userData.time > this.tokenExpirationTime) { + this.logout(); + return false; + } + + return true; + } + + getUserMail() { + const token = localStorage.getItem(this.tokenKey); + if (!token) { + return false; + } + + const userData = this.decryptToken(token); + if (!userData) { + return false; + } + + return userData.userMail; + } + + private checkCredentials(email: string, password: string) { + return this.userDataBase.some((encryptedUser) => { + const user = this.decryptToken(encryptedUser); + if (!user) { + return false; + } + + return user.userMail === email && user.password === password; + }); + } + + private generateToken(email: string, password: string) { + // ':' is not allowed in the email, so we can use it as a separator + const time = new Date().getTime(); + const tokenData = `${email}:${password}:${time}`; + return CryptoJS.AES.encrypt(tokenData, this.secretKey).toString(); + } + + private decryptToken(token: string) { + try { + const bytes = CryptoJS.AES.decrypt(token, this.secretKey); + const decryptedData = bytes.toString(CryptoJS.enc.Utf8); + + // the token should have the format email:password:time, where password could contain : + const firstColonIndex = decryptedData.indexOf(':'); + const userMail = decryptedData.slice(0, firstColonIndex); + + const lastColonIndex = decryptedData.lastIndexOf(':'); + const password = decryptedData.slice(firstColonIndex + 1, lastColonIndex); + const time = decryptedData.slice(lastColonIndex + 1); + + return { userMail: userMail, password: password, time: parseInt(time) }; + } catch (error) { + return null; + } + } }