Skip to content

Commit

Permalink
feat: act like it is secure
Browse files Browse the repository at this point in the history
Signed-off-by: Ingo Sternberg <[email protected]>
#230
  • Loading branch information
IngoSternberg committed Jul 9, 2024
1 parent 23acd8e commit c7e74f6
Show file tree
Hide file tree
Showing 8 changed files with 126 additions and 107 deletions.
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions apps/frontend/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions apps/frontend/src/environments/environment.prod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
1 change: 1 addition & 0 deletions apps/frontend/src/environments/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
1 change: 1 addition & 0 deletions apps/frontend/src/types/types.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
declare const process: {
env: {
XD_API_URL: string;
XD_SECRET_KEY: string;
};
};
1 change: 1 addition & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
221 changes: 114 additions & 107 deletions libs/common/frontend/models/src/lib/services/authentication.service.ts
Original file line number Diff line number Diff line change
@@ -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: '[email protected]', password: 'siemens' },
{ userMail: '[email protected]', password: 'siemens' },
{ userMail: '[email protected]', password: 'siemens' },
{ userMail: '[email protected]', password: 'siemens' },
{ userMail: '[email protected]', password: 'siemens' },
{ userMail: '[email protected]', password: 'siemens' },
{ userMail: '[email protected]', password: 'siemens' },
{ userMail: '[email protected]', password: 'siemens' },
{ userMail: '[email protected]', password: 'siemens' },
{ userMail: '[email protected]', password: 'siemens' },
{ userMail: '[email protected]', password: 'siemens' },
{ userMail: '[email protected]', password: 'siemens' },
{ userMail: '[email protected]', password: 'siemens' },
{ userMail: '[email protected]', 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;
}
}
}

0 comments on commit c7e74f6

Please sign in to comment.