diff --git a/angular.json b/angular.json index 90d80e5..f23dd59 100644 --- a/angular.json +++ b/angular.json @@ -1,113 +1,113 @@ { - "$schema": "./node_modules/@angular/cli/lib/config/schema.json", - "version": 1, - "newProjectRoot": "projects", - "projects": { - "Website": { - "projectType": "application", - "schematics": { - "@schematics/angular:component": { - "style": "scss" - }, - "@schematics/angular:application": { - "strict": true - } - }, - "root": "", - "sourceRoot": "src", - "prefix": "app", - "architect": { - "build": { - "builder": "@angular-devkit/build-angular:browser", - "options": { - "outputPath": "dist/Website", - "index": "src/index.html", - "main": "src/main.ts", - "polyfills": "src/polyfills.ts", - "tsConfig": "tsconfig.app.json", - "inlineStyleLanguage": "scss", - "assets": [ - "src/favicon.ico", - "src/assets" - ], - "styles": [ - "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", - "src/styles.scss" - ], - "scripts": [] - }, - "configurations": { - "production": { - "budgets": [ - { - "type": "initial", - "maximumWarning": "500kb", - "maximumError": "1mb" + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "newProjectRoot": "projects", + "projects": { + "Website": { + "projectType": "application", + "schematics": { + "@schematics/angular:component": { + "style": "scss" }, - { - "type": "anyComponentStyle", - "maximumWarning": "2kb", - "maximumError": "4kb" + "@schematics/angular:application": { + "strict": true } - ], - "fileReplacements": [ - { - "replace": "src/environments/environment.ts", - "with": "src/environments/environment.prod.ts" + }, + "root": "", + "sourceRoot": "src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:browser", + "options": { + "outputPath": "dist/Website", + "index": "src/index.html", + "main": "src/main.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "tsconfig.app.json", + "inlineStyleLanguage": "scss", + "assets": [ + "src/favicon.ico", + "src/assets" + ], + "styles": [ + "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", + "src/styles.scss" + ], + "scripts": [] + }, + "configurations": { + "production": { + "budgets": [{ + "type": "initial", + "maximumWarning": "500kb", + "maximumError": "1mb" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "2kb", + "maximumError": "4kb" + } + ], + "fileReplacements": [{ + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.prod.ts" + }], + "outputHashing": "all" + }, + "development": { + "buildOptimizer": false, + "optimization": false, + "vendorChunk": true, + "extractLicenses": false, + "sourceMap": true, + "namedChunks": true + } + }, + "defaultConfiguration": "production" + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "configurations": { + "production": { + "browserTarget": "Website:build:production" + }, + "development": { + "browserTarget": "Website:build:development" + } + }, + "options": { + "proxyConfig": "src/proxy.conf.json" + }, + "defaultConfiguration": "development" + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n", + "options": { + "browserTarget": "Website:build" + } + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "main": "src/test.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "tsconfig.spec.json", + "karmaConfig": "karma.conf.js", + "inlineStyleLanguage": "scss", + "assets": [ + "src/favicon.ico", + "src/assets" + ], + "styles": [ + "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", + "src/styles.scss" + ], + "scripts": [] + } } - ], - "outputHashing": "all" - }, - "development": { - "buildOptimizer": false, - "optimization": false, - "vendorChunk": true, - "extractLicenses": false, - "sourceMap": true, - "namedChunks": true } - }, - "defaultConfiguration": "production" - }, - "serve": { - "builder": "@angular-devkit/build-angular:dev-server", - "configurations": { - "production": { - "browserTarget": "Website:build:production" - }, - "development": { - "browserTarget": "Website:build:development" - } - }, - "defaultConfiguration": "development" - }, - "extract-i18n": { - "builder": "@angular-devkit/build-angular:extract-i18n", - "options": { - "browserTarget": "Website:build" - } - }, - "test": { - "builder": "@angular-devkit/build-angular:karma", - "options": { - "main": "src/test.ts", - "polyfills": "src/polyfills.ts", - "tsConfig": "tsconfig.spec.json", - "karmaConfig": "karma.conf.js", - "inlineStyleLanguage": "scss", - "assets": [ - "src/favicon.ico", - "src/assets" - ], - "styles": [ - "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", - "src/styles.scss" - ], - "scripts": [] - } } - } - } - }, - "defaultProject": "Website" -} + }, + "defaultProject": "Website" +} \ No newline at end of file diff --git a/angular.json b/angular.json index 90d80e5..f23dd59 100644 --- a/angular.json +++ b/angular.json @@ -1,113 +1,113 @@ { - "$schema": "./node_modules/@angular/cli/lib/config/schema.json", - "version": 1, - "newProjectRoot": "projects", - "projects": { - "Website": { - "projectType": "application", - "schematics": { - "@schematics/angular:component": { - "style": "scss" - }, - "@schematics/angular:application": { - "strict": true - } - }, - "root": "", - "sourceRoot": "src", - "prefix": "app", - "architect": { - "build": { - "builder": "@angular-devkit/build-angular:browser", - "options": { - "outputPath": "dist/Website", - "index": "src/index.html", - "main": "src/main.ts", - "polyfills": "src/polyfills.ts", - "tsConfig": "tsconfig.app.json", - "inlineStyleLanguage": "scss", - "assets": [ - "src/favicon.ico", - "src/assets" - ], - "styles": [ - "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", - "src/styles.scss" - ], - "scripts": [] - }, - "configurations": { - "production": { - "budgets": [ - { - "type": "initial", - "maximumWarning": "500kb", - "maximumError": "1mb" + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "newProjectRoot": "projects", + "projects": { + "Website": { + "projectType": "application", + "schematics": { + "@schematics/angular:component": { + "style": "scss" }, - { - "type": "anyComponentStyle", - "maximumWarning": "2kb", - "maximumError": "4kb" + "@schematics/angular:application": { + "strict": true } - ], - "fileReplacements": [ - { - "replace": "src/environments/environment.ts", - "with": "src/environments/environment.prod.ts" + }, + "root": "", + "sourceRoot": "src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:browser", + "options": { + "outputPath": "dist/Website", + "index": "src/index.html", + "main": "src/main.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "tsconfig.app.json", + "inlineStyleLanguage": "scss", + "assets": [ + "src/favicon.ico", + "src/assets" + ], + "styles": [ + "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", + "src/styles.scss" + ], + "scripts": [] + }, + "configurations": { + "production": { + "budgets": [{ + "type": "initial", + "maximumWarning": "500kb", + "maximumError": "1mb" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "2kb", + "maximumError": "4kb" + } + ], + "fileReplacements": [{ + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.prod.ts" + }], + "outputHashing": "all" + }, + "development": { + "buildOptimizer": false, + "optimization": false, + "vendorChunk": true, + "extractLicenses": false, + "sourceMap": true, + "namedChunks": true + } + }, + "defaultConfiguration": "production" + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "configurations": { + "production": { + "browserTarget": "Website:build:production" + }, + "development": { + "browserTarget": "Website:build:development" + } + }, + "options": { + "proxyConfig": "src/proxy.conf.json" + }, + "defaultConfiguration": "development" + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n", + "options": { + "browserTarget": "Website:build" + } + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "main": "src/test.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "tsconfig.spec.json", + "karmaConfig": "karma.conf.js", + "inlineStyleLanguage": "scss", + "assets": [ + "src/favicon.ico", + "src/assets" + ], + "styles": [ + "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", + "src/styles.scss" + ], + "scripts": [] + } } - ], - "outputHashing": "all" - }, - "development": { - "buildOptimizer": false, - "optimization": false, - "vendorChunk": true, - "extractLicenses": false, - "sourceMap": true, - "namedChunks": true } - }, - "defaultConfiguration": "production" - }, - "serve": { - "builder": "@angular-devkit/build-angular:dev-server", - "configurations": { - "production": { - "browserTarget": "Website:build:production" - }, - "development": { - "browserTarget": "Website:build:development" - } - }, - "defaultConfiguration": "development" - }, - "extract-i18n": { - "builder": "@angular-devkit/build-angular:extract-i18n", - "options": { - "browserTarget": "Website:build" - } - }, - "test": { - "builder": "@angular-devkit/build-angular:karma", - "options": { - "main": "src/test.ts", - "polyfills": "src/polyfills.ts", - "tsConfig": "tsconfig.spec.json", - "karmaConfig": "karma.conf.js", - "inlineStyleLanguage": "scss", - "assets": [ - "src/favicon.ico", - "src/assets" - ], - "styles": [ - "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", - "src/styles.scss" - ], - "scripts": [] - } } - } - } - }, - "defaultProject": "Website" -} + }, + "defaultProject": "Website" +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 3713633..5807647 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "@angular/router": "~12.2.0", "hammerjs": "^2.0.8", "rxjs": "~6.6.0", + "sha.js": "^2.4.11", "tslib": "^2.3.0", "zone.js": "~0.11.4" }, @@ -29,6 +30,7 @@ "@angular/compiler-cli": "~12.2.0", "@types/jasmine": "~3.8.0", "@types/node": "^12.11.1", + "@types/sha.js": "^2.4.0", "jasmine-core": "~3.8.0", "karma": "~6.3.0", "karma-chrome-launcher": "~3.1.0", @@ -2614,6 +2616,15 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "node_modules/@types/sha.js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/sha.js/-/sha.js-2.4.0.tgz", + "integrity": "sha512-amxKgPy6WJTKuw8mpUwjX2BSxuBtBmZfRwIUDIuPJKNwGN8CWDli8JTg5ONTWOtcTkHIstvT7oAhhYXqEjStHQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/source-list-map": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", @@ -7279,8 +7290,7 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/ini": { "version": "2.0.0", @@ -12765,8 +12775,7 @@ "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "node_modules/safe-regex": { "version": "1.1.0", @@ -13106,6 +13115,18 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "dev": true }, + "node_modules/sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "bin": { + "sha.js": "bin.js" + } + }, "node_modules/shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", @@ -17552,6 +17573,15 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "@types/sha.js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/sha.js/-/sha.js-2.4.0.tgz", + "integrity": "sha512-amxKgPy6WJTKuw8mpUwjX2BSxuBtBmZfRwIUDIuPJKNwGN8CWDli8JTg5ONTWOtcTkHIstvT7oAhhYXqEjStHQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/source-list-map": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", @@ -21129,8 +21159,7 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "ini": { "version": "2.0.0", @@ -25219,8 +25248,7 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "safe-regex": { "version": "1.1.0", @@ -25494,6 +25522,15 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "dev": true }, + "sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, "shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", diff --git a/angular.json b/angular.json index 90d80e5..f23dd59 100644 --- a/angular.json +++ b/angular.json @@ -1,113 +1,113 @@ { - "$schema": "./node_modules/@angular/cli/lib/config/schema.json", - "version": 1, - "newProjectRoot": "projects", - "projects": { - "Website": { - "projectType": "application", - "schematics": { - "@schematics/angular:component": { - "style": "scss" - }, - "@schematics/angular:application": { - "strict": true - } - }, - "root": "", - "sourceRoot": "src", - "prefix": "app", - "architect": { - "build": { - "builder": "@angular-devkit/build-angular:browser", - "options": { - "outputPath": "dist/Website", - "index": "src/index.html", - "main": "src/main.ts", - "polyfills": "src/polyfills.ts", - "tsConfig": "tsconfig.app.json", - "inlineStyleLanguage": "scss", - "assets": [ - "src/favicon.ico", - "src/assets" - ], - "styles": [ - "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", - "src/styles.scss" - ], - "scripts": [] - }, - "configurations": { - "production": { - "budgets": [ - { - "type": "initial", - "maximumWarning": "500kb", - "maximumError": "1mb" + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "newProjectRoot": "projects", + "projects": { + "Website": { + "projectType": "application", + "schematics": { + "@schematics/angular:component": { + "style": "scss" }, - { - "type": "anyComponentStyle", - "maximumWarning": "2kb", - "maximumError": "4kb" + "@schematics/angular:application": { + "strict": true } - ], - "fileReplacements": [ - { - "replace": "src/environments/environment.ts", - "with": "src/environments/environment.prod.ts" + }, + "root": "", + "sourceRoot": "src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:browser", + "options": { + "outputPath": "dist/Website", + "index": "src/index.html", + "main": "src/main.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "tsconfig.app.json", + "inlineStyleLanguage": "scss", + "assets": [ + "src/favicon.ico", + "src/assets" + ], + "styles": [ + "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", + "src/styles.scss" + ], + "scripts": [] + }, + "configurations": { + "production": { + "budgets": [{ + "type": "initial", + "maximumWarning": "500kb", + "maximumError": "1mb" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "2kb", + "maximumError": "4kb" + } + ], + "fileReplacements": [{ + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.prod.ts" + }], + "outputHashing": "all" + }, + "development": { + "buildOptimizer": false, + "optimization": false, + "vendorChunk": true, + "extractLicenses": false, + "sourceMap": true, + "namedChunks": true + } + }, + "defaultConfiguration": "production" + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "configurations": { + "production": { + "browserTarget": "Website:build:production" + }, + "development": { + "browserTarget": "Website:build:development" + } + }, + "options": { + "proxyConfig": "src/proxy.conf.json" + }, + "defaultConfiguration": "development" + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n", + "options": { + "browserTarget": "Website:build" + } + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "main": "src/test.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "tsconfig.spec.json", + "karmaConfig": "karma.conf.js", + "inlineStyleLanguage": "scss", + "assets": [ + "src/favicon.ico", + "src/assets" + ], + "styles": [ + "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", + "src/styles.scss" + ], + "scripts": [] + } } - ], - "outputHashing": "all" - }, - "development": { - "buildOptimizer": false, - "optimization": false, - "vendorChunk": true, - "extractLicenses": false, - "sourceMap": true, - "namedChunks": true } - }, - "defaultConfiguration": "production" - }, - "serve": { - "builder": "@angular-devkit/build-angular:dev-server", - "configurations": { - "production": { - "browserTarget": "Website:build:production" - }, - "development": { - "browserTarget": "Website:build:development" - } - }, - "defaultConfiguration": "development" - }, - "extract-i18n": { - "builder": "@angular-devkit/build-angular:extract-i18n", - "options": { - "browserTarget": "Website:build" - } - }, - "test": { - "builder": "@angular-devkit/build-angular:karma", - "options": { - "main": "src/test.ts", - "polyfills": "src/polyfills.ts", - "tsConfig": "tsconfig.spec.json", - "karmaConfig": "karma.conf.js", - "inlineStyleLanguage": "scss", - "assets": [ - "src/favicon.ico", - "src/assets" - ], - "styles": [ - "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", - "src/styles.scss" - ], - "scripts": [] - } } - } - } - }, - "defaultProject": "Website" -} + }, + "defaultProject": "Website" +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 3713633..5807647 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "@angular/router": "~12.2.0", "hammerjs": "^2.0.8", "rxjs": "~6.6.0", + "sha.js": "^2.4.11", "tslib": "^2.3.0", "zone.js": "~0.11.4" }, @@ -29,6 +30,7 @@ "@angular/compiler-cli": "~12.2.0", "@types/jasmine": "~3.8.0", "@types/node": "^12.11.1", + "@types/sha.js": "^2.4.0", "jasmine-core": "~3.8.0", "karma": "~6.3.0", "karma-chrome-launcher": "~3.1.0", @@ -2614,6 +2616,15 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "node_modules/@types/sha.js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/sha.js/-/sha.js-2.4.0.tgz", + "integrity": "sha512-amxKgPy6WJTKuw8mpUwjX2BSxuBtBmZfRwIUDIuPJKNwGN8CWDli8JTg5ONTWOtcTkHIstvT7oAhhYXqEjStHQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/source-list-map": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", @@ -7279,8 +7290,7 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/ini": { "version": "2.0.0", @@ -12765,8 +12775,7 @@ "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "node_modules/safe-regex": { "version": "1.1.0", @@ -13106,6 +13115,18 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "dev": true }, + "node_modules/sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "bin": { + "sha.js": "bin.js" + } + }, "node_modules/shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", @@ -17552,6 +17573,15 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "@types/sha.js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/sha.js/-/sha.js-2.4.0.tgz", + "integrity": "sha512-amxKgPy6WJTKuw8mpUwjX2BSxuBtBmZfRwIUDIuPJKNwGN8CWDli8JTg5ONTWOtcTkHIstvT7oAhhYXqEjStHQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/source-list-map": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", @@ -21129,8 +21159,7 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "ini": { "version": "2.0.0", @@ -25219,8 +25248,7 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "safe-regex": { "version": "1.1.0", @@ -25494,6 +25522,15 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "dev": true }, + "sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, "shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", diff --git a/package.json b/package.json index 092ece5..2c9a675 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "@angular/router": "~12.2.0", "hammerjs": "^2.0.8", "rxjs": "~6.6.0", + "sha.js": "^2.4.11", "tslib": "^2.3.0", "zone.js": "~0.11.4" }, @@ -31,6 +32,7 @@ "@angular/compiler-cli": "~12.2.0", "@types/jasmine": "~3.8.0", "@types/node": "^12.11.1", + "@types/sha.js": "^2.4.0", "jasmine-core": "~3.8.0", "karma": "~6.3.0", "karma-chrome-launcher": "~3.1.0", diff --git a/angular.json b/angular.json index 90d80e5..f23dd59 100644 --- a/angular.json +++ b/angular.json @@ -1,113 +1,113 @@ { - "$schema": "./node_modules/@angular/cli/lib/config/schema.json", - "version": 1, - "newProjectRoot": "projects", - "projects": { - "Website": { - "projectType": "application", - "schematics": { - "@schematics/angular:component": { - "style": "scss" - }, - "@schematics/angular:application": { - "strict": true - } - }, - "root": "", - "sourceRoot": "src", - "prefix": "app", - "architect": { - "build": { - "builder": "@angular-devkit/build-angular:browser", - "options": { - "outputPath": "dist/Website", - "index": "src/index.html", - "main": "src/main.ts", - "polyfills": "src/polyfills.ts", - "tsConfig": "tsconfig.app.json", - "inlineStyleLanguage": "scss", - "assets": [ - "src/favicon.ico", - "src/assets" - ], - "styles": [ - "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", - "src/styles.scss" - ], - "scripts": [] - }, - "configurations": { - "production": { - "budgets": [ - { - "type": "initial", - "maximumWarning": "500kb", - "maximumError": "1mb" + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "newProjectRoot": "projects", + "projects": { + "Website": { + "projectType": "application", + "schematics": { + "@schematics/angular:component": { + "style": "scss" }, - { - "type": "anyComponentStyle", - "maximumWarning": "2kb", - "maximumError": "4kb" + "@schematics/angular:application": { + "strict": true } - ], - "fileReplacements": [ - { - "replace": "src/environments/environment.ts", - "with": "src/environments/environment.prod.ts" + }, + "root": "", + "sourceRoot": "src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:browser", + "options": { + "outputPath": "dist/Website", + "index": "src/index.html", + "main": "src/main.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "tsconfig.app.json", + "inlineStyleLanguage": "scss", + "assets": [ + "src/favicon.ico", + "src/assets" + ], + "styles": [ + "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", + "src/styles.scss" + ], + "scripts": [] + }, + "configurations": { + "production": { + "budgets": [{ + "type": "initial", + "maximumWarning": "500kb", + "maximumError": "1mb" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "2kb", + "maximumError": "4kb" + } + ], + "fileReplacements": [{ + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.prod.ts" + }], + "outputHashing": "all" + }, + "development": { + "buildOptimizer": false, + "optimization": false, + "vendorChunk": true, + "extractLicenses": false, + "sourceMap": true, + "namedChunks": true + } + }, + "defaultConfiguration": "production" + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "configurations": { + "production": { + "browserTarget": "Website:build:production" + }, + "development": { + "browserTarget": "Website:build:development" + } + }, + "options": { + "proxyConfig": "src/proxy.conf.json" + }, + "defaultConfiguration": "development" + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n", + "options": { + "browserTarget": "Website:build" + } + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "main": "src/test.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "tsconfig.spec.json", + "karmaConfig": "karma.conf.js", + "inlineStyleLanguage": "scss", + "assets": [ + "src/favicon.ico", + "src/assets" + ], + "styles": [ + "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", + "src/styles.scss" + ], + "scripts": [] + } } - ], - "outputHashing": "all" - }, - "development": { - "buildOptimizer": false, - "optimization": false, - "vendorChunk": true, - "extractLicenses": false, - "sourceMap": true, - "namedChunks": true } - }, - "defaultConfiguration": "production" - }, - "serve": { - "builder": "@angular-devkit/build-angular:dev-server", - "configurations": { - "production": { - "browserTarget": "Website:build:production" - }, - "development": { - "browserTarget": "Website:build:development" - } - }, - "defaultConfiguration": "development" - }, - "extract-i18n": { - "builder": "@angular-devkit/build-angular:extract-i18n", - "options": { - "browserTarget": "Website:build" - } - }, - "test": { - "builder": "@angular-devkit/build-angular:karma", - "options": { - "main": "src/test.ts", - "polyfills": "src/polyfills.ts", - "tsConfig": "tsconfig.spec.json", - "karmaConfig": "karma.conf.js", - "inlineStyleLanguage": "scss", - "assets": [ - "src/favicon.ico", - "src/assets" - ], - "styles": [ - "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", - "src/styles.scss" - ], - "scripts": [] - } } - } - } - }, - "defaultProject": "Website" -} + }, + "defaultProject": "Website" +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 3713633..5807647 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "@angular/router": "~12.2.0", "hammerjs": "^2.0.8", "rxjs": "~6.6.0", + "sha.js": "^2.4.11", "tslib": "^2.3.0", "zone.js": "~0.11.4" }, @@ -29,6 +30,7 @@ "@angular/compiler-cli": "~12.2.0", "@types/jasmine": "~3.8.0", "@types/node": "^12.11.1", + "@types/sha.js": "^2.4.0", "jasmine-core": "~3.8.0", "karma": "~6.3.0", "karma-chrome-launcher": "~3.1.0", @@ -2614,6 +2616,15 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "node_modules/@types/sha.js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/sha.js/-/sha.js-2.4.0.tgz", + "integrity": "sha512-amxKgPy6WJTKuw8mpUwjX2BSxuBtBmZfRwIUDIuPJKNwGN8CWDli8JTg5ONTWOtcTkHIstvT7oAhhYXqEjStHQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/source-list-map": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", @@ -7279,8 +7290,7 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/ini": { "version": "2.0.0", @@ -12765,8 +12775,7 @@ "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "node_modules/safe-regex": { "version": "1.1.0", @@ -13106,6 +13115,18 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "dev": true }, + "node_modules/sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "bin": { + "sha.js": "bin.js" + } + }, "node_modules/shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", @@ -17552,6 +17573,15 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "@types/sha.js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/sha.js/-/sha.js-2.4.0.tgz", + "integrity": "sha512-amxKgPy6WJTKuw8mpUwjX2BSxuBtBmZfRwIUDIuPJKNwGN8CWDli8JTg5ONTWOtcTkHIstvT7oAhhYXqEjStHQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/source-list-map": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", @@ -21129,8 +21159,7 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "ini": { "version": "2.0.0", @@ -25219,8 +25248,7 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "safe-regex": { "version": "1.1.0", @@ -25494,6 +25522,15 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "dev": true }, + "sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, "shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", diff --git a/package.json b/package.json index 092ece5..2c9a675 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "@angular/router": "~12.2.0", "hammerjs": "^2.0.8", "rxjs": "~6.6.0", + "sha.js": "^2.4.11", "tslib": "^2.3.0", "zone.js": "~0.11.4" }, @@ -31,6 +32,7 @@ "@angular/compiler-cli": "~12.2.0", "@types/jasmine": "~3.8.0", "@types/node": "^12.11.1", + "@types/sha.js": "^2.4.0", "jasmine-core": "~3.8.0", "karma": "~6.3.0", "karma-chrome-launcher": "~3.1.0", diff --git a/src/app/account.service.spec.ts b/src/app/account.service.spec.ts new file mode 100644 index 0000000..2ffad6f --- /dev/null +++ b/src/app/account.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { AccountService } from './account.service'; + +describe('AccountService', () => { + let service: AccountService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(AccountService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/angular.json b/angular.json index 90d80e5..f23dd59 100644 --- a/angular.json +++ b/angular.json @@ -1,113 +1,113 @@ { - "$schema": "./node_modules/@angular/cli/lib/config/schema.json", - "version": 1, - "newProjectRoot": "projects", - "projects": { - "Website": { - "projectType": "application", - "schematics": { - "@schematics/angular:component": { - "style": "scss" - }, - "@schematics/angular:application": { - "strict": true - } - }, - "root": "", - "sourceRoot": "src", - "prefix": "app", - "architect": { - "build": { - "builder": "@angular-devkit/build-angular:browser", - "options": { - "outputPath": "dist/Website", - "index": "src/index.html", - "main": "src/main.ts", - "polyfills": "src/polyfills.ts", - "tsConfig": "tsconfig.app.json", - "inlineStyleLanguage": "scss", - "assets": [ - "src/favicon.ico", - "src/assets" - ], - "styles": [ - "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", - "src/styles.scss" - ], - "scripts": [] - }, - "configurations": { - "production": { - "budgets": [ - { - "type": "initial", - "maximumWarning": "500kb", - "maximumError": "1mb" + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "newProjectRoot": "projects", + "projects": { + "Website": { + "projectType": "application", + "schematics": { + "@schematics/angular:component": { + "style": "scss" }, - { - "type": "anyComponentStyle", - "maximumWarning": "2kb", - "maximumError": "4kb" + "@schematics/angular:application": { + "strict": true } - ], - "fileReplacements": [ - { - "replace": "src/environments/environment.ts", - "with": "src/environments/environment.prod.ts" + }, + "root": "", + "sourceRoot": "src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:browser", + "options": { + "outputPath": "dist/Website", + "index": "src/index.html", + "main": "src/main.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "tsconfig.app.json", + "inlineStyleLanguage": "scss", + "assets": [ + "src/favicon.ico", + "src/assets" + ], + "styles": [ + "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", + "src/styles.scss" + ], + "scripts": [] + }, + "configurations": { + "production": { + "budgets": [{ + "type": "initial", + "maximumWarning": "500kb", + "maximumError": "1mb" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "2kb", + "maximumError": "4kb" + } + ], + "fileReplacements": [{ + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.prod.ts" + }], + "outputHashing": "all" + }, + "development": { + "buildOptimizer": false, + "optimization": false, + "vendorChunk": true, + "extractLicenses": false, + "sourceMap": true, + "namedChunks": true + } + }, + "defaultConfiguration": "production" + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "configurations": { + "production": { + "browserTarget": "Website:build:production" + }, + "development": { + "browserTarget": "Website:build:development" + } + }, + "options": { + "proxyConfig": "src/proxy.conf.json" + }, + "defaultConfiguration": "development" + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n", + "options": { + "browserTarget": "Website:build" + } + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "main": "src/test.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "tsconfig.spec.json", + "karmaConfig": "karma.conf.js", + "inlineStyleLanguage": "scss", + "assets": [ + "src/favicon.ico", + "src/assets" + ], + "styles": [ + "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", + "src/styles.scss" + ], + "scripts": [] + } } - ], - "outputHashing": "all" - }, - "development": { - "buildOptimizer": false, - "optimization": false, - "vendorChunk": true, - "extractLicenses": false, - "sourceMap": true, - "namedChunks": true } - }, - "defaultConfiguration": "production" - }, - "serve": { - "builder": "@angular-devkit/build-angular:dev-server", - "configurations": { - "production": { - "browserTarget": "Website:build:production" - }, - "development": { - "browserTarget": "Website:build:development" - } - }, - "defaultConfiguration": "development" - }, - "extract-i18n": { - "builder": "@angular-devkit/build-angular:extract-i18n", - "options": { - "browserTarget": "Website:build" - } - }, - "test": { - "builder": "@angular-devkit/build-angular:karma", - "options": { - "main": "src/test.ts", - "polyfills": "src/polyfills.ts", - "tsConfig": "tsconfig.spec.json", - "karmaConfig": "karma.conf.js", - "inlineStyleLanguage": "scss", - "assets": [ - "src/favicon.ico", - "src/assets" - ], - "styles": [ - "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", - "src/styles.scss" - ], - "scripts": [] - } } - } - } - }, - "defaultProject": "Website" -} + }, + "defaultProject": "Website" +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 3713633..5807647 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "@angular/router": "~12.2.0", "hammerjs": "^2.0.8", "rxjs": "~6.6.0", + "sha.js": "^2.4.11", "tslib": "^2.3.0", "zone.js": "~0.11.4" }, @@ -29,6 +30,7 @@ "@angular/compiler-cli": "~12.2.0", "@types/jasmine": "~3.8.0", "@types/node": "^12.11.1", + "@types/sha.js": "^2.4.0", "jasmine-core": "~3.8.0", "karma": "~6.3.0", "karma-chrome-launcher": "~3.1.0", @@ -2614,6 +2616,15 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "node_modules/@types/sha.js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/sha.js/-/sha.js-2.4.0.tgz", + "integrity": "sha512-amxKgPy6WJTKuw8mpUwjX2BSxuBtBmZfRwIUDIuPJKNwGN8CWDli8JTg5ONTWOtcTkHIstvT7oAhhYXqEjStHQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/source-list-map": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", @@ -7279,8 +7290,7 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/ini": { "version": "2.0.0", @@ -12765,8 +12775,7 @@ "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "node_modules/safe-regex": { "version": "1.1.0", @@ -13106,6 +13115,18 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "dev": true }, + "node_modules/sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "bin": { + "sha.js": "bin.js" + } + }, "node_modules/shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", @@ -17552,6 +17573,15 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "@types/sha.js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/sha.js/-/sha.js-2.4.0.tgz", + "integrity": "sha512-amxKgPy6WJTKuw8mpUwjX2BSxuBtBmZfRwIUDIuPJKNwGN8CWDli8JTg5ONTWOtcTkHIstvT7oAhhYXqEjStHQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/source-list-map": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", @@ -21129,8 +21159,7 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "ini": { "version": "2.0.0", @@ -25219,8 +25248,7 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "safe-regex": { "version": "1.1.0", @@ -25494,6 +25522,15 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "dev": true }, + "sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, "shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", diff --git a/package.json b/package.json index 092ece5..2c9a675 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "@angular/router": "~12.2.0", "hammerjs": "^2.0.8", "rxjs": "~6.6.0", + "sha.js": "^2.4.11", "tslib": "^2.3.0", "zone.js": "~0.11.4" }, @@ -31,6 +32,7 @@ "@angular/compiler-cli": "~12.2.0", "@types/jasmine": "~3.8.0", "@types/node": "^12.11.1", + "@types/sha.js": "^2.4.0", "jasmine-core": "~3.8.0", "karma": "~6.3.0", "karma-chrome-launcher": "~3.1.0", diff --git a/src/app/account.service.spec.ts b/src/app/account.service.spec.ts new file mode 100644 index 0000000..2ffad6f --- /dev/null +++ b/src/app/account.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { AccountService } from './account.service'; + +describe('AccountService', () => { + let service: AccountService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(AccountService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/account.service.ts b/src/app/account.service.ts new file mode 100644 index 0000000..2b64b6a --- /dev/null +++ b/src/app/account.service.ts @@ -0,0 +1,96 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { environment } from 'src/environments/environment'; +import { sha256 } from 'sha.js'; +import { Router } from '@angular/router'; + +class Answer { + success: Boolean = false +} + +class PresaltAnswer extends Answer { + presalt: string = '' +} + +class AuthenticateAnswer extends Answer { + token: string = '' +} + +class CheckTokenAnswer extends Answer { + valid: Boolean = false +} + +class TokenData { + iss: string = '' + dat: string = '' + typ: string = '' + usr: string = '' +} + +@Injectable({ + providedIn: 'root' +}) +export class AccountService { + public username = '?'; + private userId: bigint = BigInt(0); + private userToken: bigint = BigInt(0); + + constructor(private http: HttpClient, private router: Router) { + this.updateUserInfo() + } + + public login(username: string, password: string, onFail: Function, onConnectionError: Function) { + this.http.post(`${environment.apiURL}/users/presalt`, {username}) + .subscribe((answer: Object) => { + var presaltAnswer = Object.assign(new PresaltAnswer(), answer) + if (!presaltAnswer.success) { + onFail() + return + } + // this presalting and hashing is done to protect the user's password in case of a man-in-the-middle-attack, + // where an attacker might be able to intercept a password if not hashed before. The password still has to be + // hashed on the server-side to be sure but this method prevents dumb users from having their passweord + // they use everywhere easily leaked, because you still need to crack this one layer first + // also, typical automated hacking software will hopefully be confused by my weird code + var passwordHash = new sha256().update(password + presaltAnswer.presalt).digest('hex') + this.http.post(`${environment.apiURL}/users/authenticate`, {username, passwordHash}) + .subscribe((answer) => { + var authenticateAnswer = Object.assign(new AuthenticateAnswer(), answer) + var loginSuccess = authenticateAnswer.success == true + if (loginSuccess) { + localStorage.setItem('userToken', authenticateAnswer.token) + this.updateUserInfo() + this.username = username + this.router.navigate(['admin']) + } else { + onFail() + } + }, (error) => {onConnectionError()}) + }, (error) => {onConnectionError()}) + } + + updateUserInfo() { + var token = localStorage.getItem('userToken') + if (token == null) { + this.username = '' + return + } + this.http.post(`${environment.apiURL}/users/checkToken`, {token}).subscribe((answer: Object) => { + var checkTokenAnswer = Object.assign(new CheckTokenAnswer(), answer) + if (checkTokenAnswer.valid == true) { + var data = JSON.parse(atob(token?.split('.')[1] as string)) + var tokenData = Object.assign(new TokenData(), data) + this.username = tokenData.usr + } else { + this.username = '' + } + }, () => { + this.username = '' + }) + } + + logout() { + this.username = '' + localStorage.removeItem('userToken') + } +} diff --git a/angular.json b/angular.json index 90d80e5..f23dd59 100644 --- a/angular.json +++ b/angular.json @@ -1,113 +1,113 @@ { - "$schema": "./node_modules/@angular/cli/lib/config/schema.json", - "version": 1, - "newProjectRoot": "projects", - "projects": { - "Website": { - "projectType": "application", - "schematics": { - "@schematics/angular:component": { - "style": "scss" - }, - "@schematics/angular:application": { - "strict": true - } - }, - "root": "", - "sourceRoot": "src", - "prefix": "app", - "architect": { - "build": { - "builder": "@angular-devkit/build-angular:browser", - "options": { - "outputPath": "dist/Website", - "index": "src/index.html", - "main": "src/main.ts", - "polyfills": "src/polyfills.ts", - "tsConfig": "tsconfig.app.json", - "inlineStyleLanguage": "scss", - "assets": [ - "src/favicon.ico", - "src/assets" - ], - "styles": [ - "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", - "src/styles.scss" - ], - "scripts": [] - }, - "configurations": { - "production": { - "budgets": [ - { - "type": "initial", - "maximumWarning": "500kb", - "maximumError": "1mb" + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "newProjectRoot": "projects", + "projects": { + "Website": { + "projectType": "application", + "schematics": { + "@schematics/angular:component": { + "style": "scss" }, - { - "type": "anyComponentStyle", - "maximumWarning": "2kb", - "maximumError": "4kb" + "@schematics/angular:application": { + "strict": true } - ], - "fileReplacements": [ - { - "replace": "src/environments/environment.ts", - "with": "src/environments/environment.prod.ts" + }, + "root": "", + "sourceRoot": "src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:browser", + "options": { + "outputPath": "dist/Website", + "index": "src/index.html", + "main": "src/main.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "tsconfig.app.json", + "inlineStyleLanguage": "scss", + "assets": [ + "src/favicon.ico", + "src/assets" + ], + "styles": [ + "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", + "src/styles.scss" + ], + "scripts": [] + }, + "configurations": { + "production": { + "budgets": [{ + "type": "initial", + "maximumWarning": "500kb", + "maximumError": "1mb" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "2kb", + "maximumError": "4kb" + } + ], + "fileReplacements": [{ + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.prod.ts" + }], + "outputHashing": "all" + }, + "development": { + "buildOptimizer": false, + "optimization": false, + "vendorChunk": true, + "extractLicenses": false, + "sourceMap": true, + "namedChunks": true + } + }, + "defaultConfiguration": "production" + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "configurations": { + "production": { + "browserTarget": "Website:build:production" + }, + "development": { + "browserTarget": "Website:build:development" + } + }, + "options": { + "proxyConfig": "src/proxy.conf.json" + }, + "defaultConfiguration": "development" + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n", + "options": { + "browserTarget": "Website:build" + } + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "main": "src/test.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "tsconfig.spec.json", + "karmaConfig": "karma.conf.js", + "inlineStyleLanguage": "scss", + "assets": [ + "src/favicon.ico", + "src/assets" + ], + "styles": [ + "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", + "src/styles.scss" + ], + "scripts": [] + } } - ], - "outputHashing": "all" - }, - "development": { - "buildOptimizer": false, - "optimization": false, - "vendorChunk": true, - "extractLicenses": false, - "sourceMap": true, - "namedChunks": true } - }, - "defaultConfiguration": "production" - }, - "serve": { - "builder": "@angular-devkit/build-angular:dev-server", - "configurations": { - "production": { - "browserTarget": "Website:build:production" - }, - "development": { - "browserTarget": "Website:build:development" - } - }, - "defaultConfiguration": "development" - }, - "extract-i18n": { - "builder": "@angular-devkit/build-angular:extract-i18n", - "options": { - "browserTarget": "Website:build" - } - }, - "test": { - "builder": "@angular-devkit/build-angular:karma", - "options": { - "main": "src/test.ts", - "polyfills": "src/polyfills.ts", - "tsConfig": "tsconfig.spec.json", - "karmaConfig": "karma.conf.js", - "inlineStyleLanguage": "scss", - "assets": [ - "src/favicon.ico", - "src/assets" - ], - "styles": [ - "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", - "src/styles.scss" - ], - "scripts": [] - } } - } - } - }, - "defaultProject": "Website" -} + }, + "defaultProject": "Website" +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 3713633..5807647 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "@angular/router": "~12.2.0", "hammerjs": "^2.0.8", "rxjs": "~6.6.0", + "sha.js": "^2.4.11", "tslib": "^2.3.0", "zone.js": "~0.11.4" }, @@ -29,6 +30,7 @@ "@angular/compiler-cli": "~12.2.0", "@types/jasmine": "~3.8.0", "@types/node": "^12.11.1", + "@types/sha.js": "^2.4.0", "jasmine-core": "~3.8.0", "karma": "~6.3.0", "karma-chrome-launcher": "~3.1.0", @@ -2614,6 +2616,15 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "node_modules/@types/sha.js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/sha.js/-/sha.js-2.4.0.tgz", + "integrity": "sha512-amxKgPy6WJTKuw8mpUwjX2BSxuBtBmZfRwIUDIuPJKNwGN8CWDli8JTg5ONTWOtcTkHIstvT7oAhhYXqEjStHQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/source-list-map": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", @@ -7279,8 +7290,7 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/ini": { "version": "2.0.0", @@ -12765,8 +12775,7 @@ "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "node_modules/safe-regex": { "version": "1.1.0", @@ -13106,6 +13115,18 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "dev": true }, + "node_modules/sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "bin": { + "sha.js": "bin.js" + } + }, "node_modules/shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", @@ -17552,6 +17573,15 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "@types/sha.js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/sha.js/-/sha.js-2.4.0.tgz", + "integrity": "sha512-amxKgPy6WJTKuw8mpUwjX2BSxuBtBmZfRwIUDIuPJKNwGN8CWDli8JTg5ONTWOtcTkHIstvT7oAhhYXqEjStHQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/source-list-map": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", @@ -21129,8 +21159,7 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "ini": { "version": "2.0.0", @@ -25219,8 +25248,7 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "safe-regex": { "version": "1.1.0", @@ -25494,6 +25522,15 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "dev": true }, + "sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, "shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", diff --git a/package.json b/package.json index 092ece5..2c9a675 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "@angular/router": "~12.2.0", "hammerjs": "^2.0.8", "rxjs": "~6.6.0", + "sha.js": "^2.4.11", "tslib": "^2.3.0", "zone.js": "~0.11.4" }, @@ -31,6 +32,7 @@ "@angular/compiler-cli": "~12.2.0", "@types/jasmine": "~3.8.0", "@types/node": "^12.11.1", + "@types/sha.js": "^2.4.0", "jasmine-core": "~3.8.0", "karma": "~6.3.0", "karma-chrome-launcher": "~3.1.0", diff --git a/src/app/account.service.spec.ts b/src/app/account.service.spec.ts new file mode 100644 index 0000000..2ffad6f --- /dev/null +++ b/src/app/account.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { AccountService } from './account.service'; + +describe('AccountService', () => { + let service: AccountService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(AccountService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/account.service.ts b/src/app/account.service.ts new file mode 100644 index 0000000..2b64b6a --- /dev/null +++ b/src/app/account.service.ts @@ -0,0 +1,96 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { environment } from 'src/environments/environment'; +import { sha256 } from 'sha.js'; +import { Router } from '@angular/router'; + +class Answer { + success: Boolean = false +} + +class PresaltAnswer extends Answer { + presalt: string = '' +} + +class AuthenticateAnswer extends Answer { + token: string = '' +} + +class CheckTokenAnswer extends Answer { + valid: Boolean = false +} + +class TokenData { + iss: string = '' + dat: string = '' + typ: string = '' + usr: string = '' +} + +@Injectable({ + providedIn: 'root' +}) +export class AccountService { + public username = '?'; + private userId: bigint = BigInt(0); + private userToken: bigint = BigInt(0); + + constructor(private http: HttpClient, private router: Router) { + this.updateUserInfo() + } + + public login(username: string, password: string, onFail: Function, onConnectionError: Function) { + this.http.post(`${environment.apiURL}/users/presalt`, {username}) + .subscribe((answer: Object) => { + var presaltAnswer = Object.assign(new PresaltAnswer(), answer) + if (!presaltAnswer.success) { + onFail() + return + } + // this presalting and hashing is done to protect the user's password in case of a man-in-the-middle-attack, + // where an attacker might be able to intercept a password if not hashed before. The password still has to be + // hashed on the server-side to be sure but this method prevents dumb users from having their passweord + // they use everywhere easily leaked, because you still need to crack this one layer first + // also, typical automated hacking software will hopefully be confused by my weird code + var passwordHash = new sha256().update(password + presaltAnswer.presalt).digest('hex') + this.http.post(`${environment.apiURL}/users/authenticate`, {username, passwordHash}) + .subscribe((answer) => { + var authenticateAnswer = Object.assign(new AuthenticateAnswer(), answer) + var loginSuccess = authenticateAnswer.success == true + if (loginSuccess) { + localStorage.setItem('userToken', authenticateAnswer.token) + this.updateUserInfo() + this.username = username + this.router.navigate(['admin']) + } else { + onFail() + } + }, (error) => {onConnectionError()}) + }, (error) => {onConnectionError()}) + } + + updateUserInfo() { + var token = localStorage.getItem('userToken') + if (token == null) { + this.username = '' + return + } + this.http.post(`${environment.apiURL}/users/checkToken`, {token}).subscribe((answer: Object) => { + var checkTokenAnswer = Object.assign(new CheckTokenAnswer(), answer) + if (checkTokenAnswer.valid == true) { + var data = JSON.parse(atob(token?.split('.')[1] as string)) + var tokenData = Object.assign(new TokenData(), data) + this.username = tokenData.usr + } else { + this.username = '' + } + }, () => { + this.username = '' + }) + } + + logout() { + this.username = '' + localStorage.removeItem('userToken') + } +} diff --git a/src/app/admin/admin.component.html b/src/app/admin/admin.component.html new file mode 100644 index 0000000..f949c13 --- /dev/null +++ b/src/app/admin/admin.component.html @@ -0,0 +1,6 @@ +

Hello {{account.username}}!

+

Welcome to the admin panel

+ +

There is nothing to do here yet...

+ + \ No newline at end of file diff --git a/angular.json b/angular.json index 90d80e5..f23dd59 100644 --- a/angular.json +++ b/angular.json @@ -1,113 +1,113 @@ { - "$schema": "./node_modules/@angular/cli/lib/config/schema.json", - "version": 1, - "newProjectRoot": "projects", - "projects": { - "Website": { - "projectType": "application", - "schematics": { - "@schematics/angular:component": { - "style": "scss" - }, - "@schematics/angular:application": { - "strict": true - } - }, - "root": "", - "sourceRoot": "src", - "prefix": "app", - "architect": { - "build": { - "builder": "@angular-devkit/build-angular:browser", - "options": { - "outputPath": "dist/Website", - "index": "src/index.html", - "main": "src/main.ts", - "polyfills": "src/polyfills.ts", - "tsConfig": "tsconfig.app.json", - "inlineStyleLanguage": "scss", - "assets": [ - "src/favicon.ico", - "src/assets" - ], - "styles": [ - "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", - "src/styles.scss" - ], - "scripts": [] - }, - "configurations": { - "production": { - "budgets": [ - { - "type": "initial", - "maximumWarning": "500kb", - "maximumError": "1mb" + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "newProjectRoot": "projects", + "projects": { + "Website": { + "projectType": "application", + "schematics": { + "@schematics/angular:component": { + "style": "scss" }, - { - "type": "anyComponentStyle", - "maximumWarning": "2kb", - "maximumError": "4kb" + "@schematics/angular:application": { + "strict": true } - ], - "fileReplacements": [ - { - "replace": "src/environments/environment.ts", - "with": "src/environments/environment.prod.ts" + }, + "root": "", + "sourceRoot": "src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:browser", + "options": { + "outputPath": "dist/Website", + "index": "src/index.html", + "main": "src/main.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "tsconfig.app.json", + "inlineStyleLanguage": "scss", + "assets": [ + "src/favicon.ico", + "src/assets" + ], + "styles": [ + "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", + "src/styles.scss" + ], + "scripts": [] + }, + "configurations": { + "production": { + "budgets": [{ + "type": "initial", + "maximumWarning": "500kb", + "maximumError": "1mb" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "2kb", + "maximumError": "4kb" + } + ], + "fileReplacements": [{ + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.prod.ts" + }], + "outputHashing": "all" + }, + "development": { + "buildOptimizer": false, + "optimization": false, + "vendorChunk": true, + "extractLicenses": false, + "sourceMap": true, + "namedChunks": true + } + }, + "defaultConfiguration": "production" + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "configurations": { + "production": { + "browserTarget": "Website:build:production" + }, + "development": { + "browserTarget": "Website:build:development" + } + }, + "options": { + "proxyConfig": "src/proxy.conf.json" + }, + "defaultConfiguration": "development" + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n", + "options": { + "browserTarget": "Website:build" + } + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "main": "src/test.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "tsconfig.spec.json", + "karmaConfig": "karma.conf.js", + "inlineStyleLanguage": "scss", + "assets": [ + "src/favicon.ico", + "src/assets" + ], + "styles": [ + "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", + "src/styles.scss" + ], + "scripts": [] + } } - ], - "outputHashing": "all" - }, - "development": { - "buildOptimizer": false, - "optimization": false, - "vendorChunk": true, - "extractLicenses": false, - "sourceMap": true, - "namedChunks": true } - }, - "defaultConfiguration": "production" - }, - "serve": { - "builder": "@angular-devkit/build-angular:dev-server", - "configurations": { - "production": { - "browserTarget": "Website:build:production" - }, - "development": { - "browserTarget": "Website:build:development" - } - }, - "defaultConfiguration": "development" - }, - "extract-i18n": { - "builder": "@angular-devkit/build-angular:extract-i18n", - "options": { - "browserTarget": "Website:build" - } - }, - "test": { - "builder": "@angular-devkit/build-angular:karma", - "options": { - "main": "src/test.ts", - "polyfills": "src/polyfills.ts", - "tsConfig": "tsconfig.spec.json", - "karmaConfig": "karma.conf.js", - "inlineStyleLanguage": "scss", - "assets": [ - "src/favicon.ico", - "src/assets" - ], - "styles": [ - "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", - "src/styles.scss" - ], - "scripts": [] - } } - } - } - }, - "defaultProject": "Website" -} + }, + "defaultProject": "Website" +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 3713633..5807647 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "@angular/router": "~12.2.0", "hammerjs": "^2.0.8", "rxjs": "~6.6.0", + "sha.js": "^2.4.11", "tslib": "^2.3.0", "zone.js": "~0.11.4" }, @@ -29,6 +30,7 @@ "@angular/compiler-cli": "~12.2.0", "@types/jasmine": "~3.8.0", "@types/node": "^12.11.1", + "@types/sha.js": "^2.4.0", "jasmine-core": "~3.8.0", "karma": "~6.3.0", "karma-chrome-launcher": "~3.1.0", @@ -2614,6 +2616,15 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "node_modules/@types/sha.js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/sha.js/-/sha.js-2.4.0.tgz", + "integrity": "sha512-amxKgPy6WJTKuw8mpUwjX2BSxuBtBmZfRwIUDIuPJKNwGN8CWDli8JTg5ONTWOtcTkHIstvT7oAhhYXqEjStHQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/source-list-map": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", @@ -7279,8 +7290,7 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/ini": { "version": "2.0.0", @@ -12765,8 +12775,7 @@ "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "node_modules/safe-regex": { "version": "1.1.0", @@ -13106,6 +13115,18 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "dev": true }, + "node_modules/sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "bin": { + "sha.js": "bin.js" + } + }, "node_modules/shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", @@ -17552,6 +17573,15 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "@types/sha.js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/sha.js/-/sha.js-2.4.0.tgz", + "integrity": "sha512-amxKgPy6WJTKuw8mpUwjX2BSxuBtBmZfRwIUDIuPJKNwGN8CWDli8JTg5ONTWOtcTkHIstvT7oAhhYXqEjStHQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/source-list-map": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", @@ -21129,8 +21159,7 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "ini": { "version": "2.0.0", @@ -25219,8 +25248,7 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "safe-regex": { "version": "1.1.0", @@ -25494,6 +25522,15 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "dev": true }, + "sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, "shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", diff --git a/package.json b/package.json index 092ece5..2c9a675 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "@angular/router": "~12.2.0", "hammerjs": "^2.0.8", "rxjs": "~6.6.0", + "sha.js": "^2.4.11", "tslib": "^2.3.0", "zone.js": "~0.11.4" }, @@ -31,6 +32,7 @@ "@angular/compiler-cli": "~12.2.0", "@types/jasmine": "~3.8.0", "@types/node": "^12.11.1", + "@types/sha.js": "^2.4.0", "jasmine-core": "~3.8.0", "karma": "~6.3.0", "karma-chrome-launcher": "~3.1.0", diff --git a/src/app/account.service.spec.ts b/src/app/account.service.spec.ts new file mode 100644 index 0000000..2ffad6f --- /dev/null +++ b/src/app/account.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { AccountService } from './account.service'; + +describe('AccountService', () => { + let service: AccountService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(AccountService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/account.service.ts b/src/app/account.service.ts new file mode 100644 index 0000000..2b64b6a --- /dev/null +++ b/src/app/account.service.ts @@ -0,0 +1,96 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { environment } from 'src/environments/environment'; +import { sha256 } from 'sha.js'; +import { Router } from '@angular/router'; + +class Answer { + success: Boolean = false +} + +class PresaltAnswer extends Answer { + presalt: string = '' +} + +class AuthenticateAnswer extends Answer { + token: string = '' +} + +class CheckTokenAnswer extends Answer { + valid: Boolean = false +} + +class TokenData { + iss: string = '' + dat: string = '' + typ: string = '' + usr: string = '' +} + +@Injectable({ + providedIn: 'root' +}) +export class AccountService { + public username = '?'; + private userId: bigint = BigInt(0); + private userToken: bigint = BigInt(0); + + constructor(private http: HttpClient, private router: Router) { + this.updateUserInfo() + } + + public login(username: string, password: string, onFail: Function, onConnectionError: Function) { + this.http.post(`${environment.apiURL}/users/presalt`, {username}) + .subscribe((answer: Object) => { + var presaltAnswer = Object.assign(new PresaltAnswer(), answer) + if (!presaltAnswer.success) { + onFail() + return + } + // this presalting and hashing is done to protect the user's password in case of a man-in-the-middle-attack, + // where an attacker might be able to intercept a password if not hashed before. The password still has to be + // hashed on the server-side to be sure but this method prevents dumb users from having their passweord + // they use everywhere easily leaked, because you still need to crack this one layer first + // also, typical automated hacking software will hopefully be confused by my weird code + var passwordHash = new sha256().update(password + presaltAnswer.presalt).digest('hex') + this.http.post(`${environment.apiURL}/users/authenticate`, {username, passwordHash}) + .subscribe((answer) => { + var authenticateAnswer = Object.assign(new AuthenticateAnswer(), answer) + var loginSuccess = authenticateAnswer.success == true + if (loginSuccess) { + localStorage.setItem('userToken', authenticateAnswer.token) + this.updateUserInfo() + this.username = username + this.router.navigate(['admin']) + } else { + onFail() + } + }, (error) => {onConnectionError()}) + }, (error) => {onConnectionError()}) + } + + updateUserInfo() { + var token = localStorage.getItem('userToken') + if (token == null) { + this.username = '' + return + } + this.http.post(`${environment.apiURL}/users/checkToken`, {token}).subscribe((answer: Object) => { + var checkTokenAnswer = Object.assign(new CheckTokenAnswer(), answer) + if (checkTokenAnswer.valid == true) { + var data = JSON.parse(atob(token?.split('.')[1] as string)) + var tokenData = Object.assign(new TokenData(), data) + this.username = tokenData.usr + } else { + this.username = '' + } + }, () => { + this.username = '' + }) + } + + logout() { + this.username = '' + localStorage.removeItem('userToken') + } +} diff --git a/src/app/admin/admin.component.html b/src/app/admin/admin.component.html new file mode 100644 index 0000000..f949c13 --- /dev/null +++ b/src/app/admin/admin.component.html @@ -0,0 +1,6 @@ +

Hello {{account.username}}!

+

Welcome to the admin panel

+ +

There is nothing to do here yet...

+ + \ No newline at end of file diff --git a/src/app/admin/admin.component.scss b/src/app/admin/admin.component.scss new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/app/admin/admin.component.scss diff --git a/angular.json b/angular.json index 90d80e5..f23dd59 100644 --- a/angular.json +++ b/angular.json @@ -1,113 +1,113 @@ { - "$schema": "./node_modules/@angular/cli/lib/config/schema.json", - "version": 1, - "newProjectRoot": "projects", - "projects": { - "Website": { - "projectType": "application", - "schematics": { - "@schematics/angular:component": { - "style": "scss" - }, - "@schematics/angular:application": { - "strict": true - } - }, - "root": "", - "sourceRoot": "src", - "prefix": "app", - "architect": { - "build": { - "builder": "@angular-devkit/build-angular:browser", - "options": { - "outputPath": "dist/Website", - "index": "src/index.html", - "main": "src/main.ts", - "polyfills": "src/polyfills.ts", - "tsConfig": "tsconfig.app.json", - "inlineStyleLanguage": "scss", - "assets": [ - "src/favicon.ico", - "src/assets" - ], - "styles": [ - "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", - "src/styles.scss" - ], - "scripts": [] - }, - "configurations": { - "production": { - "budgets": [ - { - "type": "initial", - "maximumWarning": "500kb", - "maximumError": "1mb" + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "newProjectRoot": "projects", + "projects": { + "Website": { + "projectType": "application", + "schematics": { + "@schematics/angular:component": { + "style": "scss" }, - { - "type": "anyComponentStyle", - "maximumWarning": "2kb", - "maximumError": "4kb" + "@schematics/angular:application": { + "strict": true } - ], - "fileReplacements": [ - { - "replace": "src/environments/environment.ts", - "with": "src/environments/environment.prod.ts" + }, + "root": "", + "sourceRoot": "src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:browser", + "options": { + "outputPath": "dist/Website", + "index": "src/index.html", + "main": "src/main.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "tsconfig.app.json", + "inlineStyleLanguage": "scss", + "assets": [ + "src/favicon.ico", + "src/assets" + ], + "styles": [ + "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", + "src/styles.scss" + ], + "scripts": [] + }, + "configurations": { + "production": { + "budgets": [{ + "type": "initial", + "maximumWarning": "500kb", + "maximumError": "1mb" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "2kb", + "maximumError": "4kb" + } + ], + "fileReplacements": [{ + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.prod.ts" + }], + "outputHashing": "all" + }, + "development": { + "buildOptimizer": false, + "optimization": false, + "vendorChunk": true, + "extractLicenses": false, + "sourceMap": true, + "namedChunks": true + } + }, + "defaultConfiguration": "production" + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "configurations": { + "production": { + "browserTarget": "Website:build:production" + }, + "development": { + "browserTarget": "Website:build:development" + } + }, + "options": { + "proxyConfig": "src/proxy.conf.json" + }, + "defaultConfiguration": "development" + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n", + "options": { + "browserTarget": "Website:build" + } + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "main": "src/test.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "tsconfig.spec.json", + "karmaConfig": "karma.conf.js", + "inlineStyleLanguage": "scss", + "assets": [ + "src/favicon.ico", + "src/assets" + ], + "styles": [ + "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", + "src/styles.scss" + ], + "scripts": [] + } } - ], - "outputHashing": "all" - }, - "development": { - "buildOptimizer": false, - "optimization": false, - "vendorChunk": true, - "extractLicenses": false, - "sourceMap": true, - "namedChunks": true } - }, - "defaultConfiguration": "production" - }, - "serve": { - "builder": "@angular-devkit/build-angular:dev-server", - "configurations": { - "production": { - "browserTarget": "Website:build:production" - }, - "development": { - "browserTarget": "Website:build:development" - } - }, - "defaultConfiguration": "development" - }, - "extract-i18n": { - "builder": "@angular-devkit/build-angular:extract-i18n", - "options": { - "browserTarget": "Website:build" - } - }, - "test": { - "builder": "@angular-devkit/build-angular:karma", - "options": { - "main": "src/test.ts", - "polyfills": "src/polyfills.ts", - "tsConfig": "tsconfig.spec.json", - "karmaConfig": "karma.conf.js", - "inlineStyleLanguage": "scss", - "assets": [ - "src/favicon.ico", - "src/assets" - ], - "styles": [ - "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", - "src/styles.scss" - ], - "scripts": [] - } } - } - } - }, - "defaultProject": "Website" -} + }, + "defaultProject": "Website" +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 3713633..5807647 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "@angular/router": "~12.2.0", "hammerjs": "^2.0.8", "rxjs": "~6.6.0", + "sha.js": "^2.4.11", "tslib": "^2.3.0", "zone.js": "~0.11.4" }, @@ -29,6 +30,7 @@ "@angular/compiler-cli": "~12.2.0", "@types/jasmine": "~3.8.0", "@types/node": "^12.11.1", + "@types/sha.js": "^2.4.0", "jasmine-core": "~3.8.0", "karma": "~6.3.0", "karma-chrome-launcher": "~3.1.0", @@ -2614,6 +2616,15 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "node_modules/@types/sha.js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/sha.js/-/sha.js-2.4.0.tgz", + "integrity": "sha512-amxKgPy6WJTKuw8mpUwjX2BSxuBtBmZfRwIUDIuPJKNwGN8CWDli8JTg5ONTWOtcTkHIstvT7oAhhYXqEjStHQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/source-list-map": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", @@ -7279,8 +7290,7 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/ini": { "version": "2.0.0", @@ -12765,8 +12775,7 @@ "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "node_modules/safe-regex": { "version": "1.1.0", @@ -13106,6 +13115,18 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "dev": true }, + "node_modules/sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "bin": { + "sha.js": "bin.js" + } + }, "node_modules/shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", @@ -17552,6 +17573,15 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "@types/sha.js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/sha.js/-/sha.js-2.4.0.tgz", + "integrity": "sha512-amxKgPy6WJTKuw8mpUwjX2BSxuBtBmZfRwIUDIuPJKNwGN8CWDli8JTg5ONTWOtcTkHIstvT7oAhhYXqEjStHQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/source-list-map": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", @@ -21129,8 +21159,7 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "ini": { "version": "2.0.0", @@ -25219,8 +25248,7 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "safe-regex": { "version": "1.1.0", @@ -25494,6 +25522,15 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "dev": true }, + "sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, "shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", diff --git a/package.json b/package.json index 092ece5..2c9a675 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "@angular/router": "~12.2.0", "hammerjs": "^2.0.8", "rxjs": "~6.6.0", + "sha.js": "^2.4.11", "tslib": "^2.3.0", "zone.js": "~0.11.4" }, @@ -31,6 +32,7 @@ "@angular/compiler-cli": "~12.2.0", "@types/jasmine": "~3.8.0", "@types/node": "^12.11.1", + "@types/sha.js": "^2.4.0", "jasmine-core": "~3.8.0", "karma": "~6.3.0", "karma-chrome-launcher": "~3.1.0", diff --git a/src/app/account.service.spec.ts b/src/app/account.service.spec.ts new file mode 100644 index 0000000..2ffad6f --- /dev/null +++ b/src/app/account.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { AccountService } from './account.service'; + +describe('AccountService', () => { + let service: AccountService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(AccountService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/account.service.ts b/src/app/account.service.ts new file mode 100644 index 0000000..2b64b6a --- /dev/null +++ b/src/app/account.service.ts @@ -0,0 +1,96 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { environment } from 'src/environments/environment'; +import { sha256 } from 'sha.js'; +import { Router } from '@angular/router'; + +class Answer { + success: Boolean = false +} + +class PresaltAnswer extends Answer { + presalt: string = '' +} + +class AuthenticateAnswer extends Answer { + token: string = '' +} + +class CheckTokenAnswer extends Answer { + valid: Boolean = false +} + +class TokenData { + iss: string = '' + dat: string = '' + typ: string = '' + usr: string = '' +} + +@Injectable({ + providedIn: 'root' +}) +export class AccountService { + public username = '?'; + private userId: bigint = BigInt(0); + private userToken: bigint = BigInt(0); + + constructor(private http: HttpClient, private router: Router) { + this.updateUserInfo() + } + + public login(username: string, password: string, onFail: Function, onConnectionError: Function) { + this.http.post(`${environment.apiURL}/users/presalt`, {username}) + .subscribe((answer: Object) => { + var presaltAnswer = Object.assign(new PresaltAnswer(), answer) + if (!presaltAnswer.success) { + onFail() + return + } + // this presalting and hashing is done to protect the user's password in case of a man-in-the-middle-attack, + // where an attacker might be able to intercept a password if not hashed before. The password still has to be + // hashed on the server-side to be sure but this method prevents dumb users from having their passweord + // they use everywhere easily leaked, because you still need to crack this one layer first + // also, typical automated hacking software will hopefully be confused by my weird code + var passwordHash = new sha256().update(password + presaltAnswer.presalt).digest('hex') + this.http.post(`${environment.apiURL}/users/authenticate`, {username, passwordHash}) + .subscribe((answer) => { + var authenticateAnswer = Object.assign(new AuthenticateAnswer(), answer) + var loginSuccess = authenticateAnswer.success == true + if (loginSuccess) { + localStorage.setItem('userToken', authenticateAnswer.token) + this.updateUserInfo() + this.username = username + this.router.navigate(['admin']) + } else { + onFail() + } + }, (error) => {onConnectionError()}) + }, (error) => {onConnectionError()}) + } + + updateUserInfo() { + var token = localStorage.getItem('userToken') + if (token == null) { + this.username = '' + return + } + this.http.post(`${environment.apiURL}/users/checkToken`, {token}).subscribe((answer: Object) => { + var checkTokenAnswer = Object.assign(new CheckTokenAnswer(), answer) + if (checkTokenAnswer.valid == true) { + var data = JSON.parse(atob(token?.split('.')[1] as string)) + var tokenData = Object.assign(new TokenData(), data) + this.username = tokenData.usr + } else { + this.username = '' + } + }, () => { + this.username = '' + }) + } + + logout() { + this.username = '' + localStorage.removeItem('userToken') + } +} diff --git a/src/app/admin/admin.component.html b/src/app/admin/admin.component.html new file mode 100644 index 0000000..f949c13 --- /dev/null +++ b/src/app/admin/admin.component.html @@ -0,0 +1,6 @@ +

Hello {{account.username}}!

+

Welcome to the admin panel

+ +

There is nothing to do here yet...

+ + \ No newline at end of file diff --git a/src/app/admin/admin.component.scss b/src/app/admin/admin.component.scss new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/app/admin/admin.component.scss diff --git a/src/app/admin/admin.component.spec.ts b/src/app/admin/admin.component.spec.ts new file mode 100644 index 0000000..eb28e42 --- /dev/null +++ b/src/app/admin/admin.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AdminComponent } from './admin.component'; + +describe('AdminComponent', () => { + let component: AdminComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ AdminComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(AdminComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/angular.json b/angular.json index 90d80e5..f23dd59 100644 --- a/angular.json +++ b/angular.json @@ -1,113 +1,113 @@ { - "$schema": "./node_modules/@angular/cli/lib/config/schema.json", - "version": 1, - "newProjectRoot": "projects", - "projects": { - "Website": { - "projectType": "application", - "schematics": { - "@schematics/angular:component": { - "style": "scss" - }, - "@schematics/angular:application": { - "strict": true - } - }, - "root": "", - "sourceRoot": "src", - "prefix": "app", - "architect": { - "build": { - "builder": "@angular-devkit/build-angular:browser", - "options": { - "outputPath": "dist/Website", - "index": "src/index.html", - "main": "src/main.ts", - "polyfills": "src/polyfills.ts", - "tsConfig": "tsconfig.app.json", - "inlineStyleLanguage": "scss", - "assets": [ - "src/favicon.ico", - "src/assets" - ], - "styles": [ - "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", - "src/styles.scss" - ], - "scripts": [] - }, - "configurations": { - "production": { - "budgets": [ - { - "type": "initial", - "maximumWarning": "500kb", - "maximumError": "1mb" + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "newProjectRoot": "projects", + "projects": { + "Website": { + "projectType": "application", + "schematics": { + "@schematics/angular:component": { + "style": "scss" }, - { - "type": "anyComponentStyle", - "maximumWarning": "2kb", - "maximumError": "4kb" + "@schematics/angular:application": { + "strict": true } - ], - "fileReplacements": [ - { - "replace": "src/environments/environment.ts", - "with": "src/environments/environment.prod.ts" + }, + "root": "", + "sourceRoot": "src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:browser", + "options": { + "outputPath": "dist/Website", + "index": "src/index.html", + "main": "src/main.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "tsconfig.app.json", + "inlineStyleLanguage": "scss", + "assets": [ + "src/favicon.ico", + "src/assets" + ], + "styles": [ + "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", + "src/styles.scss" + ], + "scripts": [] + }, + "configurations": { + "production": { + "budgets": [{ + "type": "initial", + "maximumWarning": "500kb", + "maximumError": "1mb" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "2kb", + "maximumError": "4kb" + } + ], + "fileReplacements": [{ + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.prod.ts" + }], + "outputHashing": "all" + }, + "development": { + "buildOptimizer": false, + "optimization": false, + "vendorChunk": true, + "extractLicenses": false, + "sourceMap": true, + "namedChunks": true + } + }, + "defaultConfiguration": "production" + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "configurations": { + "production": { + "browserTarget": "Website:build:production" + }, + "development": { + "browserTarget": "Website:build:development" + } + }, + "options": { + "proxyConfig": "src/proxy.conf.json" + }, + "defaultConfiguration": "development" + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n", + "options": { + "browserTarget": "Website:build" + } + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "main": "src/test.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "tsconfig.spec.json", + "karmaConfig": "karma.conf.js", + "inlineStyleLanguage": "scss", + "assets": [ + "src/favicon.ico", + "src/assets" + ], + "styles": [ + "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", + "src/styles.scss" + ], + "scripts": [] + } } - ], - "outputHashing": "all" - }, - "development": { - "buildOptimizer": false, - "optimization": false, - "vendorChunk": true, - "extractLicenses": false, - "sourceMap": true, - "namedChunks": true } - }, - "defaultConfiguration": "production" - }, - "serve": { - "builder": "@angular-devkit/build-angular:dev-server", - "configurations": { - "production": { - "browserTarget": "Website:build:production" - }, - "development": { - "browserTarget": "Website:build:development" - } - }, - "defaultConfiguration": "development" - }, - "extract-i18n": { - "builder": "@angular-devkit/build-angular:extract-i18n", - "options": { - "browserTarget": "Website:build" - } - }, - "test": { - "builder": "@angular-devkit/build-angular:karma", - "options": { - "main": "src/test.ts", - "polyfills": "src/polyfills.ts", - "tsConfig": "tsconfig.spec.json", - "karmaConfig": "karma.conf.js", - "inlineStyleLanguage": "scss", - "assets": [ - "src/favicon.ico", - "src/assets" - ], - "styles": [ - "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", - "src/styles.scss" - ], - "scripts": [] - } } - } - } - }, - "defaultProject": "Website" -} + }, + "defaultProject": "Website" +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 3713633..5807647 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "@angular/router": "~12.2.0", "hammerjs": "^2.0.8", "rxjs": "~6.6.0", + "sha.js": "^2.4.11", "tslib": "^2.3.0", "zone.js": "~0.11.4" }, @@ -29,6 +30,7 @@ "@angular/compiler-cli": "~12.2.0", "@types/jasmine": "~3.8.0", "@types/node": "^12.11.1", + "@types/sha.js": "^2.4.0", "jasmine-core": "~3.8.0", "karma": "~6.3.0", "karma-chrome-launcher": "~3.1.0", @@ -2614,6 +2616,15 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "node_modules/@types/sha.js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/sha.js/-/sha.js-2.4.0.tgz", + "integrity": "sha512-amxKgPy6WJTKuw8mpUwjX2BSxuBtBmZfRwIUDIuPJKNwGN8CWDli8JTg5ONTWOtcTkHIstvT7oAhhYXqEjStHQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/source-list-map": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", @@ -7279,8 +7290,7 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/ini": { "version": "2.0.0", @@ -12765,8 +12775,7 @@ "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "node_modules/safe-regex": { "version": "1.1.0", @@ -13106,6 +13115,18 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "dev": true }, + "node_modules/sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "bin": { + "sha.js": "bin.js" + } + }, "node_modules/shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", @@ -17552,6 +17573,15 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "@types/sha.js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/sha.js/-/sha.js-2.4.0.tgz", + "integrity": "sha512-amxKgPy6WJTKuw8mpUwjX2BSxuBtBmZfRwIUDIuPJKNwGN8CWDli8JTg5ONTWOtcTkHIstvT7oAhhYXqEjStHQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/source-list-map": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", @@ -21129,8 +21159,7 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "ini": { "version": "2.0.0", @@ -25219,8 +25248,7 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "safe-regex": { "version": "1.1.0", @@ -25494,6 +25522,15 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "dev": true }, + "sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, "shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", diff --git a/package.json b/package.json index 092ece5..2c9a675 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "@angular/router": "~12.2.0", "hammerjs": "^2.0.8", "rxjs": "~6.6.0", + "sha.js": "^2.4.11", "tslib": "^2.3.0", "zone.js": "~0.11.4" }, @@ -31,6 +32,7 @@ "@angular/compiler-cli": "~12.2.0", "@types/jasmine": "~3.8.0", "@types/node": "^12.11.1", + "@types/sha.js": "^2.4.0", "jasmine-core": "~3.8.0", "karma": "~6.3.0", "karma-chrome-launcher": "~3.1.0", diff --git a/src/app/account.service.spec.ts b/src/app/account.service.spec.ts new file mode 100644 index 0000000..2ffad6f --- /dev/null +++ b/src/app/account.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { AccountService } from './account.service'; + +describe('AccountService', () => { + let service: AccountService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(AccountService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/account.service.ts b/src/app/account.service.ts new file mode 100644 index 0000000..2b64b6a --- /dev/null +++ b/src/app/account.service.ts @@ -0,0 +1,96 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { environment } from 'src/environments/environment'; +import { sha256 } from 'sha.js'; +import { Router } from '@angular/router'; + +class Answer { + success: Boolean = false +} + +class PresaltAnswer extends Answer { + presalt: string = '' +} + +class AuthenticateAnswer extends Answer { + token: string = '' +} + +class CheckTokenAnswer extends Answer { + valid: Boolean = false +} + +class TokenData { + iss: string = '' + dat: string = '' + typ: string = '' + usr: string = '' +} + +@Injectable({ + providedIn: 'root' +}) +export class AccountService { + public username = '?'; + private userId: bigint = BigInt(0); + private userToken: bigint = BigInt(0); + + constructor(private http: HttpClient, private router: Router) { + this.updateUserInfo() + } + + public login(username: string, password: string, onFail: Function, onConnectionError: Function) { + this.http.post(`${environment.apiURL}/users/presalt`, {username}) + .subscribe((answer: Object) => { + var presaltAnswer = Object.assign(new PresaltAnswer(), answer) + if (!presaltAnswer.success) { + onFail() + return + } + // this presalting and hashing is done to protect the user's password in case of a man-in-the-middle-attack, + // where an attacker might be able to intercept a password if not hashed before. The password still has to be + // hashed on the server-side to be sure but this method prevents dumb users from having their passweord + // they use everywhere easily leaked, because you still need to crack this one layer first + // also, typical automated hacking software will hopefully be confused by my weird code + var passwordHash = new sha256().update(password + presaltAnswer.presalt).digest('hex') + this.http.post(`${environment.apiURL}/users/authenticate`, {username, passwordHash}) + .subscribe((answer) => { + var authenticateAnswer = Object.assign(new AuthenticateAnswer(), answer) + var loginSuccess = authenticateAnswer.success == true + if (loginSuccess) { + localStorage.setItem('userToken', authenticateAnswer.token) + this.updateUserInfo() + this.username = username + this.router.navigate(['admin']) + } else { + onFail() + } + }, (error) => {onConnectionError()}) + }, (error) => {onConnectionError()}) + } + + updateUserInfo() { + var token = localStorage.getItem('userToken') + if (token == null) { + this.username = '' + return + } + this.http.post(`${environment.apiURL}/users/checkToken`, {token}).subscribe((answer: Object) => { + var checkTokenAnswer = Object.assign(new CheckTokenAnswer(), answer) + if (checkTokenAnswer.valid == true) { + var data = JSON.parse(atob(token?.split('.')[1] as string)) + var tokenData = Object.assign(new TokenData(), data) + this.username = tokenData.usr + } else { + this.username = '' + } + }, () => { + this.username = '' + }) + } + + logout() { + this.username = '' + localStorage.removeItem('userToken') + } +} diff --git a/src/app/admin/admin.component.html b/src/app/admin/admin.component.html new file mode 100644 index 0000000..f949c13 --- /dev/null +++ b/src/app/admin/admin.component.html @@ -0,0 +1,6 @@ +

Hello {{account.username}}!

+

Welcome to the admin panel

+ +

There is nothing to do here yet...

+ + \ No newline at end of file diff --git a/src/app/admin/admin.component.scss b/src/app/admin/admin.component.scss new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/app/admin/admin.component.scss diff --git a/src/app/admin/admin.component.spec.ts b/src/app/admin/admin.component.spec.ts new file mode 100644 index 0000000..eb28e42 --- /dev/null +++ b/src/app/admin/admin.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AdminComponent } from './admin.component'; + +describe('AdminComponent', () => { + let component: AdminComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ AdminComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(AdminComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/admin/admin.component.ts b/src/app/admin/admin.component.ts new file mode 100644 index 0000000..85273be --- /dev/null +++ b/src/app/admin/admin.component.ts @@ -0,0 +1,20 @@ +import { Component, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; +import { AccountService } from '../account.service'; + +@Component({ + selector: 'app-admin', + templateUrl: './admin.component.html', + styleUrls: ['./admin.component.scss'] +}) +export class AdminComponent implements OnInit { + + constructor(public account: AccountService, private router: Router) { + if (account.username.length == 0) { + router.navigate(['login']) + } + } + + ngOnInit(): void { + } +} diff --git a/angular.json b/angular.json index 90d80e5..f23dd59 100644 --- a/angular.json +++ b/angular.json @@ -1,113 +1,113 @@ { - "$schema": "./node_modules/@angular/cli/lib/config/schema.json", - "version": 1, - "newProjectRoot": "projects", - "projects": { - "Website": { - "projectType": "application", - "schematics": { - "@schematics/angular:component": { - "style": "scss" - }, - "@schematics/angular:application": { - "strict": true - } - }, - "root": "", - "sourceRoot": "src", - "prefix": "app", - "architect": { - "build": { - "builder": "@angular-devkit/build-angular:browser", - "options": { - "outputPath": "dist/Website", - "index": "src/index.html", - "main": "src/main.ts", - "polyfills": "src/polyfills.ts", - "tsConfig": "tsconfig.app.json", - "inlineStyleLanguage": "scss", - "assets": [ - "src/favicon.ico", - "src/assets" - ], - "styles": [ - "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", - "src/styles.scss" - ], - "scripts": [] - }, - "configurations": { - "production": { - "budgets": [ - { - "type": "initial", - "maximumWarning": "500kb", - "maximumError": "1mb" + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "newProjectRoot": "projects", + "projects": { + "Website": { + "projectType": "application", + "schematics": { + "@schematics/angular:component": { + "style": "scss" }, - { - "type": "anyComponentStyle", - "maximumWarning": "2kb", - "maximumError": "4kb" + "@schematics/angular:application": { + "strict": true } - ], - "fileReplacements": [ - { - "replace": "src/environments/environment.ts", - "with": "src/environments/environment.prod.ts" + }, + "root": "", + "sourceRoot": "src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:browser", + "options": { + "outputPath": "dist/Website", + "index": "src/index.html", + "main": "src/main.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "tsconfig.app.json", + "inlineStyleLanguage": "scss", + "assets": [ + "src/favicon.ico", + "src/assets" + ], + "styles": [ + "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", + "src/styles.scss" + ], + "scripts": [] + }, + "configurations": { + "production": { + "budgets": [{ + "type": "initial", + "maximumWarning": "500kb", + "maximumError": "1mb" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "2kb", + "maximumError": "4kb" + } + ], + "fileReplacements": [{ + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.prod.ts" + }], + "outputHashing": "all" + }, + "development": { + "buildOptimizer": false, + "optimization": false, + "vendorChunk": true, + "extractLicenses": false, + "sourceMap": true, + "namedChunks": true + } + }, + "defaultConfiguration": "production" + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "configurations": { + "production": { + "browserTarget": "Website:build:production" + }, + "development": { + "browserTarget": "Website:build:development" + } + }, + "options": { + "proxyConfig": "src/proxy.conf.json" + }, + "defaultConfiguration": "development" + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n", + "options": { + "browserTarget": "Website:build" + } + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "main": "src/test.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "tsconfig.spec.json", + "karmaConfig": "karma.conf.js", + "inlineStyleLanguage": "scss", + "assets": [ + "src/favicon.ico", + "src/assets" + ], + "styles": [ + "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", + "src/styles.scss" + ], + "scripts": [] + } } - ], - "outputHashing": "all" - }, - "development": { - "buildOptimizer": false, - "optimization": false, - "vendorChunk": true, - "extractLicenses": false, - "sourceMap": true, - "namedChunks": true } - }, - "defaultConfiguration": "production" - }, - "serve": { - "builder": "@angular-devkit/build-angular:dev-server", - "configurations": { - "production": { - "browserTarget": "Website:build:production" - }, - "development": { - "browserTarget": "Website:build:development" - } - }, - "defaultConfiguration": "development" - }, - "extract-i18n": { - "builder": "@angular-devkit/build-angular:extract-i18n", - "options": { - "browserTarget": "Website:build" - } - }, - "test": { - "builder": "@angular-devkit/build-angular:karma", - "options": { - "main": "src/test.ts", - "polyfills": "src/polyfills.ts", - "tsConfig": "tsconfig.spec.json", - "karmaConfig": "karma.conf.js", - "inlineStyleLanguage": "scss", - "assets": [ - "src/favicon.ico", - "src/assets" - ], - "styles": [ - "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", - "src/styles.scss" - ], - "scripts": [] - } } - } - } - }, - "defaultProject": "Website" -} + }, + "defaultProject": "Website" +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 3713633..5807647 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "@angular/router": "~12.2.0", "hammerjs": "^2.0.8", "rxjs": "~6.6.0", + "sha.js": "^2.4.11", "tslib": "^2.3.0", "zone.js": "~0.11.4" }, @@ -29,6 +30,7 @@ "@angular/compiler-cli": "~12.2.0", "@types/jasmine": "~3.8.0", "@types/node": "^12.11.1", + "@types/sha.js": "^2.4.0", "jasmine-core": "~3.8.0", "karma": "~6.3.0", "karma-chrome-launcher": "~3.1.0", @@ -2614,6 +2616,15 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "node_modules/@types/sha.js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/sha.js/-/sha.js-2.4.0.tgz", + "integrity": "sha512-amxKgPy6WJTKuw8mpUwjX2BSxuBtBmZfRwIUDIuPJKNwGN8CWDli8JTg5ONTWOtcTkHIstvT7oAhhYXqEjStHQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/source-list-map": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", @@ -7279,8 +7290,7 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/ini": { "version": "2.0.0", @@ -12765,8 +12775,7 @@ "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "node_modules/safe-regex": { "version": "1.1.0", @@ -13106,6 +13115,18 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "dev": true }, + "node_modules/sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "bin": { + "sha.js": "bin.js" + } + }, "node_modules/shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", @@ -17552,6 +17573,15 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "@types/sha.js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/sha.js/-/sha.js-2.4.0.tgz", + "integrity": "sha512-amxKgPy6WJTKuw8mpUwjX2BSxuBtBmZfRwIUDIuPJKNwGN8CWDli8JTg5ONTWOtcTkHIstvT7oAhhYXqEjStHQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/source-list-map": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", @@ -21129,8 +21159,7 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "ini": { "version": "2.0.0", @@ -25219,8 +25248,7 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "safe-regex": { "version": "1.1.0", @@ -25494,6 +25522,15 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "dev": true }, + "sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, "shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", diff --git a/package.json b/package.json index 092ece5..2c9a675 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "@angular/router": "~12.2.0", "hammerjs": "^2.0.8", "rxjs": "~6.6.0", + "sha.js": "^2.4.11", "tslib": "^2.3.0", "zone.js": "~0.11.4" }, @@ -31,6 +32,7 @@ "@angular/compiler-cli": "~12.2.0", "@types/jasmine": "~3.8.0", "@types/node": "^12.11.1", + "@types/sha.js": "^2.4.0", "jasmine-core": "~3.8.0", "karma": "~6.3.0", "karma-chrome-launcher": "~3.1.0", diff --git a/src/app/account.service.spec.ts b/src/app/account.service.spec.ts new file mode 100644 index 0000000..2ffad6f --- /dev/null +++ b/src/app/account.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { AccountService } from './account.service'; + +describe('AccountService', () => { + let service: AccountService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(AccountService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/account.service.ts b/src/app/account.service.ts new file mode 100644 index 0000000..2b64b6a --- /dev/null +++ b/src/app/account.service.ts @@ -0,0 +1,96 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { environment } from 'src/environments/environment'; +import { sha256 } from 'sha.js'; +import { Router } from '@angular/router'; + +class Answer { + success: Boolean = false +} + +class PresaltAnswer extends Answer { + presalt: string = '' +} + +class AuthenticateAnswer extends Answer { + token: string = '' +} + +class CheckTokenAnswer extends Answer { + valid: Boolean = false +} + +class TokenData { + iss: string = '' + dat: string = '' + typ: string = '' + usr: string = '' +} + +@Injectable({ + providedIn: 'root' +}) +export class AccountService { + public username = '?'; + private userId: bigint = BigInt(0); + private userToken: bigint = BigInt(0); + + constructor(private http: HttpClient, private router: Router) { + this.updateUserInfo() + } + + public login(username: string, password: string, onFail: Function, onConnectionError: Function) { + this.http.post(`${environment.apiURL}/users/presalt`, {username}) + .subscribe((answer: Object) => { + var presaltAnswer = Object.assign(new PresaltAnswer(), answer) + if (!presaltAnswer.success) { + onFail() + return + } + // this presalting and hashing is done to protect the user's password in case of a man-in-the-middle-attack, + // where an attacker might be able to intercept a password if not hashed before. The password still has to be + // hashed on the server-side to be sure but this method prevents dumb users from having their passweord + // they use everywhere easily leaked, because you still need to crack this one layer first + // also, typical automated hacking software will hopefully be confused by my weird code + var passwordHash = new sha256().update(password + presaltAnswer.presalt).digest('hex') + this.http.post(`${environment.apiURL}/users/authenticate`, {username, passwordHash}) + .subscribe((answer) => { + var authenticateAnswer = Object.assign(new AuthenticateAnswer(), answer) + var loginSuccess = authenticateAnswer.success == true + if (loginSuccess) { + localStorage.setItem('userToken', authenticateAnswer.token) + this.updateUserInfo() + this.username = username + this.router.navigate(['admin']) + } else { + onFail() + } + }, (error) => {onConnectionError()}) + }, (error) => {onConnectionError()}) + } + + updateUserInfo() { + var token = localStorage.getItem('userToken') + if (token == null) { + this.username = '' + return + } + this.http.post(`${environment.apiURL}/users/checkToken`, {token}).subscribe((answer: Object) => { + var checkTokenAnswer = Object.assign(new CheckTokenAnswer(), answer) + if (checkTokenAnswer.valid == true) { + var data = JSON.parse(atob(token?.split('.')[1] as string)) + var tokenData = Object.assign(new TokenData(), data) + this.username = tokenData.usr + } else { + this.username = '' + } + }, () => { + this.username = '' + }) + } + + logout() { + this.username = '' + localStorage.removeItem('userToken') + } +} diff --git a/src/app/admin/admin.component.html b/src/app/admin/admin.component.html new file mode 100644 index 0000000..f949c13 --- /dev/null +++ b/src/app/admin/admin.component.html @@ -0,0 +1,6 @@ +

Hello {{account.username}}!

+

Welcome to the admin panel

+ +

There is nothing to do here yet...

+ + \ No newline at end of file diff --git a/src/app/admin/admin.component.scss b/src/app/admin/admin.component.scss new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/app/admin/admin.component.scss diff --git a/src/app/admin/admin.component.spec.ts b/src/app/admin/admin.component.spec.ts new file mode 100644 index 0000000..eb28e42 --- /dev/null +++ b/src/app/admin/admin.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AdminComponent } from './admin.component'; + +describe('AdminComponent', () => { + let component: AdminComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ AdminComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(AdminComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/admin/admin.component.ts b/src/app/admin/admin.component.ts new file mode 100644 index 0000000..85273be --- /dev/null +++ b/src/app/admin/admin.component.ts @@ -0,0 +1,20 @@ +import { Component, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; +import { AccountService } from '../account.service'; + +@Component({ + selector: 'app-admin', + templateUrl: './admin.component.html', + styleUrls: ['./admin.component.scss'] +}) +export class AdminComponent implements OnInit { + + constructor(public account: AccountService, private router: Router) { + if (account.username.length == 0) { + router.navigate(['login']) + } + } + + ngOnInit(): void { + } +} diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index b250123..9a07a6e 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -1,9 +1,13 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; +import { AdminComponent } from './admin/admin.component'; import { HomeComponent } from './home/home.component'; +import { LoginComponent } from './login/login.component'; const routes: Routes = [ - { path: "", component: HomeComponent } + { path: "", component: HomeComponent }, + { path: "login", component: LoginComponent }, + { path: "admin", component: AdminComponent }, ]; @NgModule({ diff --git a/angular.json b/angular.json index 90d80e5..f23dd59 100644 --- a/angular.json +++ b/angular.json @@ -1,113 +1,113 @@ { - "$schema": "./node_modules/@angular/cli/lib/config/schema.json", - "version": 1, - "newProjectRoot": "projects", - "projects": { - "Website": { - "projectType": "application", - "schematics": { - "@schematics/angular:component": { - "style": "scss" - }, - "@schematics/angular:application": { - "strict": true - } - }, - "root": "", - "sourceRoot": "src", - "prefix": "app", - "architect": { - "build": { - "builder": "@angular-devkit/build-angular:browser", - "options": { - "outputPath": "dist/Website", - "index": "src/index.html", - "main": "src/main.ts", - "polyfills": "src/polyfills.ts", - "tsConfig": "tsconfig.app.json", - "inlineStyleLanguage": "scss", - "assets": [ - "src/favicon.ico", - "src/assets" - ], - "styles": [ - "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", - "src/styles.scss" - ], - "scripts": [] - }, - "configurations": { - "production": { - "budgets": [ - { - "type": "initial", - "maximumWarning": "500kb", - "maximumError": "1mb" + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "newProjectRoot": "projects", + "projects": { + "Website": { + "projectType": "application", + "schematics": { + "@schematics/angular:component": { + "style": "scss" }, - { - "type": "anyComponentStyle", - "maximumWarning": "2kb", - "maximumError": "4kb" + "@schematics/angular:application": { + "strict": true } - ], - "fileReplacements": [ - { - "replace": "src/environments/environment.ts", - "with": "src/environments/environment.prod.ts" + }, + "root": "", + "sourceRoot": "src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:browser", + "options": { + "outputPath": "dist/Website", + "index": "src/index.html", + "main": "src/main.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "tsconfig.app.json", + "inlineStyleLanguage": "scss", + "assets": [ + "src/favicon.ico", + "src/assets" + ], + "styles": [ + "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", + "src/styles.scss" + ], + "scripts": [] + }, + "configurations": { + "production": { + "budgets": [{ + "type": "initial", + "maximumWarning": "500kb", + "maximumError": "1mb" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "2kb", + "maximumError": "4kb" + } + ], + "fileReplacements": [{ + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.prod.ts" + }], + "outputHashing": "all" + }, + "development": { + "buildOptimizer": false, + "optimization": false, + "vendorChunk": true, + "extractLicenses": false, + "sourceMap": true, + "namedChunks": true + } + }, + "defaultConfiguration": "production" + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "configurations": { + "production": { + "browserTarget": "Website:build:production" + }, + "development": { + "browserTarget": "Website:build:development" + } + }, + "options": { + "proxyConfig": "src/proxy.conf.json" + }, + "defaultConfiguration": "development" + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n", + "options": { + "browserTarget": "Website:build" + } + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "main": "src/test.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "tsconfig.spec.json", + "karmaConfig": "karma.conf.js", + "inlineStyleLanguage": "scss", + "assets": [ + "src/favicon.ico", + "src/assets" + ], + "styles": [ + "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", + "src/styles.scss" + ], + "scripts": [] + } } - ], - "outputHashing": "all" - }, - "development": { - "buildOptimizer": false, - "optimization": false, - "vendorChunk": true, - "extractLicenses": false, - "sourceMap": true, - "namedChunks": true } - }, - "defaultConfiguration": "production" - }, - "serve": { - "builder": "@angular-devkit/build-angular:dev-server", - "configurations": { - "production": { - "browserTarget": "Website:build:production" - }, - "development": { - "browserTarget": "Website:build:development" - } - }, - "defaultConfiguration": "development" - }, - "extract-i18n": { - "builder": "@angular-devkit/build-angular:extract-i18n", - "options": { - "browserTarget": "Website:build" - } - }, - "test": { - "builder": "@angular-devkit/build-angular:karma", - "options": { - "main": "src/test.ts", - "polyfills": "src/polyfills.ts", - "tsConfig": "tsconfig.spec.json", - "karmaConfig": "karma.conf.js", - "inlineStyleLanguage": "scss", - "assets": [ - "src/favicon.ico", - "src/assets" - ], - "styles": [ - "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", - "src/styles.scss" - ], - "scripts": [] - } } - } - } - }, - "defaultProject": "Website" -} + }, + "defaultProject": "Website" +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 3713633..5807647 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "@angular/router": "~12.2.0", "hammerjs": "^2.0.8", "rxjs": "~6.6.0", + "sha.js": "^2.4.11", "tslib": "^2.3.0", "zone.js": "~0.11.4" }, @@ -29,6 +30,7 @@ "@angular/compiler-cli": "~12.2.0", "@types/jasmine": "~3.8.0", "@types/node": "^12.11.1", + "@types/sha.js": "^2.4.0", "jasmine-core": "~3.8.0", "karma": "~6.3.0", "karma-chrome-launcher": "~3.1.0", @@ -2614,6 +2616,15 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "node_modules/@types/sha.js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/sha.js/-/sha.js-2.4.0.tgz", + "integrity": "sha512-amxKgPy6WJTKuw8mpUwjX2BSxuBtBmZfRwIUDIuPJKNwGN8CWDli8JTg5ONTWOtcTkHIstvT7oAhhYXqEjStHQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/source-list-map": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", @@ -7279,8 +7290,7 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/ini": { "version": "2.0.0", @@ -12765,8 +12775,7 @@ "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "node_modules/safe-regex": { "version": "1.1.0", @@ -13106,6 +13115,18 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "dev": true }, + "node_modules/sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "bin": { + "sha.js": "bin.js" + } + }, "node_modules/shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", @@ -17552,6 +17573,15 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "@types/sha.js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/sha.js/-/sha.js-2.4.0.tgz", + "integrity": "sha512-amxKgPy6WJTKuw8mpUwjX2BSxuBtBmZfRwIUDIuPJKNwGN8CWDli8JTg5ONTWOtcTkHIstvT7oAhhYXqEjStHQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/source-list-map": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", @@ -21129,8 +21159,7 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "ini": { "version": "2.0.0", @@ -25219,8 +25248,7 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "safe-regex": { "version": "1.1.0", @@ -25494,6 +25522,15 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "dev": true }, + "sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, "shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", diff --git a/package.json b/package.json index 092ece5..2c9a675 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "@angular/router": "~12.2.0", "hammerjs": "^2.0.8", "rxjs": "~6.6.0", + "sha.js": "^2.4.11", "tslib": "^2.3.0", "zone.js": "~0.11.4" }, @@ -31,6 +32,7 @@ "@angular/compiler-cli": "~12.2.0", "@types/jasmine": "~3.8.0", "@types/node": "^12.11.1", + "@types/sha.js": "^2.4.0", "jasmine-core": "~3.8.0", "karma": "~6.3.0", "karma-chrome-launcher": "~3.1.0", diff --git a/src/app/account.service.spec.ts b/src/app/account.service.spec.ts new file mode 100644 index 0000000..2ffad6f --- /dev/null +++ b/src/app/account.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { AccountService } from './account.service'; + +describe('AccountService', () => { + let service: AccountService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(AccountService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/account.service.ts b/src/app/account.service.ts new file mode 100644 index 0000000..2b64b6a --- /dev/null +++ b/src/app/account.service.ts @@ -0,0 +1,96 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { environment } from 'src/environments/environment'; +import { sha256 } from 'sha.js'; +import { Router } from '@angular/router'; + +class Answer { + success: Boolean = false +} + +class PresaltAnswer extends Answer { + presalt: string = '' +} + +class AuthenticateAnswer extends Answer { + token: string = '' +} + +class CheckTokenAnswer extends Answer { + valid: Boolean = false +} + +class TokenData { + iss: string = '' + dat: string = '' + typ: string = '' + usr: string = '' +} + +@Injectable({ + providedIn: 'root' +}) +export class AccountService { + public username = '?'; + private userId: bigint = BigInt(0); + private userToken: bigint = BigInt(0); + + constructor(private http: HttpClient, private router: Router) { + this.updateUserInfo() + } + + public login(username: string, password: string, onFail: Function, onConnectionError: Function) { + this.http.post(`${environment.apiURL}/users/presalt`, {username}) + .subscribe((answer: Object) => { + var presaltAnswer = Object.assign(new PresaltAnswer(), answer) + if (!presaltAnswer.success) { + onFail() + return + } + // this presalting and hashing is done to protect the user's password in case of a man-in-the-middle-attack, + // where an attacker might be able to intercept a password if not hashed before. The password still has to be + // hashed on the server-side to be sure but this method prevents dumb users from having their passweord + // they use everywhere easily leaked, because you still need to crack this one layer first + // also, typical automated hacking software will hopefully be confused by my weird code + var passwordHash = new sha256().update(password + presaltAnswer.presalt).digest('hex') + this.http.post(`${environment.apiURL}/users/authenticate`, {username, passwordHash}) + .subscribe((answer) => { + var authenticateAnswer = Object.assign(new AuthenticateAnswer(), answer) + var loginSuccess = authenticateAnswer.success == true + if (loginSuccess) { + localStorage.setItem('userToken', authenticateAnswer.token) + this.updateUserInfo() + this.username = username + this.router.navigate(['admin']) + } else { + onFail() + } + }, (error) => {onConnectionError()}) + }, (error) => {onConnectionError()}) + } + + updateUserInfo() { + var token = localStorage.getItem('userToken') + if (token == null) { + this.username = '' + return + } + this.http.post(`${environment.apiURL}/users/checkToken`, {token}).subscribe((answer: Object) => { + var checkTokenAnswer = Object.assign(new CheckTokenAnswer(), answer) + if (checkTokenAnswer.valid == true) { + var data = JSON.parse(atob(token?.split('.')[1] as string)) + var tokenData = Object.assign(new TokenData(), data) + this.username = tokenData.usr + } else { + this.username = '' + } + }, () => { + this.username = '' + }) + } + + logout() { + this.username = '' + localStorage.removeItem('userToken') + } +} diff --git a/src/app/admin/admin.component.html b/src/app/admin/admin.component.html new file mode 100644 index 0000000..f949c13 --- /dev/null +++ b/src/app/admin/admin.component.html @@ -0,0 +1,6 @@ +

Hello {{account.username}}!

+

Welcome to the admin panel

+ +

There is nothing to do here yet...

+ + \ No newline at end of file diff --git a/src/app/admin/admin.component.scss b/src/app/admin/admin.component.scss new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/app/admin/admin.component.scss diff --git a/src/app/admin/admin.component.spec.ts b/src/app/admin/admin.component.spec.ts new file mode 100644 index 0000000..eb28e42 --- /dev/null +++ b/src/app/admin/admin.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AdminComponent } from './admin.component'; + +describe('AdminComponent', () => { + let component: AdminComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ AdminComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(AdminComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/admin/admin.component.ts b/src/app/admin/admin.component.ts new file mode 100644 index 0000000..85273be --- /dev/null +++ b/src/app/admin/admin.component.ts @@ -0,0 +1,20 @@ +import { Component, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; +import { AccountService } from '../account.service'; + +@Component({ + selector: 'app-admin', + templateUrl: './admin.component.html', + styleUrls: ['./admin.component.scss'] +}) +export class AdminComponent implements OnInit { + + constructor(public account: AccountService, private router: Router) { + if (account.username.length == 0) { + router.navigate(['login']) + } + } + + ngOnInit(): void { + } +} diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index b250123..9a07a6e 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -1,9 +1,13 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; +import { AdminComponent } from './admin/admin.component'; import { HomeComponent } from './home/home.component'; +import { LoginComponent } from './login/login.component'; const routes: Routes = [ - { path: "", component: HomeComponent } + { path: "", component: HomeComponent }, + { path: "login", component: LoginComponent }, + { path: "admin", component: AdminComponent }, ]; @NgModule({ diff --git a/src/app/app.module.ts b/src/app/app.module.ts index a6f5aac..1fb908c 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -4,6 +4,15 @@ import { MatSidenavModule } from '@angular/material/sidenav'; import { MatListModule } from '@angular/material/list'; import { MatIconModule } from '@angular/material/icon'; +import { MatCardModule } from '@angular/material/card'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatButtonModule } from '@angular/material/button'; +import { MatInputModule } from '@angular/material/input'; +import { MatMenuModule } from '@angular/material/menu'; +import { MatTableModule } from '@angular/material/table'; +import { MatSlideToggleModule } from '@angular/material/slide-toggle'; +import { MatSelectModule } from '@angular/material/select'; +import { MatOptionModule } from '@angular/material/core'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; @@ -13,13 +22,21 @@ import { FooterComponent } from './footer/footer.component'; import { GridComponent } from './grid/grid.component'; +import { AccountService } from './account.service'; +import { LoginComponent } from './login/login.component' +import { HttpClientModule } from '@angular/common/http'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { AdminComponent } from './admin/admin.component'; + @NgModule({ declarations: [ AppComponent, ToolbarComponent, HomeComponent, FooterComponent, - GridComponent + GridComponent, + LoginComponent, + AdminComponent ], imports: [ BrowserModule, @@ -29,8 +46,20 @@ MatSidenavModule, MatListModule, MatIconModule, + HttpClientModule, + MatCardModule, + MatFormFieldModule, + MatInputModule, + FormsModule, + ReactiveFormsModule, + MatButtonModule, + MatMenuModule, + MatTableModule, + MatSlideToggleModule, + MatSelectModule, + MatOptionModule, ], - providers: [], + providers: [AccountService], bootstrap: [AppComponent] }) export class AppModule { } diff --git a/angular.json b/angular.json index 90d80e5..f23dd59 100644 --- a/angular.json +++ b/angular.json @@ -1,113 +1,113 @@ { - "$schema": "./node_modules/@angular/cli/lib/config/schema.json", - "version": 1, - "newProjectRoot": "projects", - "projects": { - "Website": { - "projectType": "application", - "schematics": { - "@schematics/angular:component": { - "style": "scss" - }, - "@schematics/angular:application": { - "strict": true - } - }, - "root": "", - "sourceRoot": "src", - "prefix": "app", - "architect": { - "build": { - "builder": "@angular-devkit/build-angular:browser", - "options": { - "outputPath": "dist/Website", - "index": "src/index.html", - "main": "src/main.ts", - "polyfills": "src/polyfills.ts", - "tsConfig": "tsconfig.app.json", - "inlineStyleLanguage": "scss", - "assets": [ - "src/favicon.ico", - "src/assets" - ], - "styles": [ - "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", - "src/styles.scss" - ], - "scripts": [] - }, - "configurations": { - "production": { - "budgets": [ - { - "type": "initial", - "maximumWarning": "500kb", - "maximumError": "1mb" + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "newProjectRoot": "projects", + "projects": { + "Website": { + "projectType": "application", + "schematics": { + "@schematics/angular:component": { + "style": "scss" }, - { - "type": "anyComponentStyle", - "maximumWarning": "2kb", - "maximumError": "4kb" + "@schematics/angular:application": { + "strict": true } - ], - "fileReplacements": [ - { - "replace": "src/environments/environment.ts", - "with": "src/environments/environment.prod.ts" + }, + "root": "", + "sourceRoot": "src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:browser", + "options": { + "outputPath": "dist/Website", + "index": "src/index.html", + "main": "src/main.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "tsconfig.app.json", + "inlineStyleLanguage": "scss", + "assets": [ + "src/favicon.ico", + "src/assets" + ], + "styles": [ + "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", + "src/styles.scss" + ], + "scripts": [] + }, + "configurations": { + "production": { + "budgets": [{ + "type": "initial", + "maximumWarning": "500kb", + "maximumError": "1mb" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "2kb", + "maximumError": "4kb" + } + ], + "fileReplacements": [{ + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.prod.ts" + }], + "outputHashing": "all" + }, + "development": { + "buildOptimizer": false, + "optimization": false, + "vendorChunk": true, + "extractLicenses": false, + "sourceMap": true, + "namedChunks": true + } + }, + "defaultConfiguration": "production" + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "configurations": { + "production": { + "browserTarget": "Website:build:production" + }, + "development": { + "browserTarget": "Website:build:development" + } + }, + "options": { + "proxyConfig": "src/proxy.conf.json" + }, + "defaultConfiguration": "development" + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n", + "options": { + "browserTarget": "Website:build" + } + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "main": "src/test.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "tsconfig.spec.json", + "karmaConfig": "karma.conf.js", + "inlineStyleLanguage": "scss", + "assets": [ + "src/favicon.ico", + "src/assets" + ], + "styles": [ + "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", + "src/styles.scss" + ], + "scripts": [] + } } - ], - "outputHashing": "all" - }, - "development": { - "buildOptimizer": false, - "optimization": false, - "vendorChunk": true, - "extractLicenses": false, - "sourceMap": true, - "namedChunks": true } - }, - "defaultConfiguration": "production" - }, - "serve": { - "builder": "@angular-devkit/build-angular:dev-server", - "configurations": { - "production": { - "browserTarget": "Website:build:production" - }, - "development": { - "browserTarget": "Website:build:development" - } - }, - "defaultConfiguration": "development" - }, - "extract-i18n": { - "builder": "@angular-devkit/build-angular:extract-i18n", - "options": { - "browserTarget": "Website:build" - } - }, - "test": { - "builder": "@angular-devkit/build-angular:karma", - "options": { - "main": "src/test.ts", - "polyfills": "src/polyfills.ts", - "tsConfig": "tsconfig.spec.json", - "karmaConfig": "karma.conf.js", - "inlineStyleLanguage": "scss", - "assets": [ - "src/favicon.ico", - "src/assets" - ], - "styles": [ - "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", - "src/styles.scss" - ], - "scripts": [] - } } - } - } - }, - "defaultProject": "Website" -} + }, + "defaultProject": "Website" +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 3713633..5807647 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "@angular/router": "~12.2.0", "hammerjs": "^2.0.8", "rxjs": "~6.6.0", + "sha.js": "^2.4.11", "tslib": "^2.3.0", "zone.js": "~0.11.4" }, @@ -29,6 +30,7 @@ "@angular/compiler-cli": "~12.2.0", "@types/jasmine": "~3.8.0", "@types/node": "^12.11.1", + "@types/sha.js": "^2.4.0", "jasmine-core": "~3.8.0", "karma": "~6.3.0", "karma-chrome-launcher": "~3.1.0", @@ -2614,6 +2616,15 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "node_modules/@types/sha.js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/sha.js/-/sha.js-2.4.0.tgz", + "integrity": "sha512-amxKgPy6WJTKuw8mpUwjX2BSxuBtBmZfRwIUDIuPJKNwGN8CWDli8JTg5ONTWOtcTkHIstvT7oAhhYXqEjStHQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/source-list-map": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", @@ -7279,8 +7290,7 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/ini": { "version": "2.0.0", @@ -12765,8 +12775,7 @@ "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "node_modules/safe-regex": { "version": "1.1.0", @@ -13106,6 +13115,18 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "dev": true }, + "node_modules/sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "bin": { + "sha.js": "bin.js" + } + }, "node_modules/shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", @@ -17552,6 +17573,15 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "@types/sha.js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/sha.js/-/sha.js-2.4.0.tgz", + "integrity": "sha512-amxKgPy6WJTKuw8mpUwjX2BSxuBtBmZfRwIUDIuPJKNwGN8CWDli8JTg5ONTWOtcTkHIstvT7oAhhYXqEjStHQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/source-list-map": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", @@ -21129,8 +21159,7 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "ini": { "version": "2.0.0", @@ -25219,8 +25248,7 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "safe-regex": { "version": "1.1.0", @@ -25494,6 +25522,15 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "dev": true }, + "sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, "shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", diff --git a/package.json b/package.json index 092ece5..2c9a675 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "@angular/router": "~12.2.0", "hammerjs": "^2.0.8", "rxjs": "~6.6.0", + "sha.js": "^2.4.11", "tslib": "^2.3.0", "zone.js": "~0.11.4" }, @@ -31,6 +32,7 @@ "@angular/compiler-cli": "~12.2.0", "@types/jasmine": "~3.8.0", "@types/node": "^12.11.1", + "@types/sha.js": "^2.4.0", "jasmine-core": "~3.8.0", "karma": "~6.3.0", "karma-chrome-launcher": "~3.1.0", diff --git a/src/app/account.service.spec.ts b/src/app/account.service.spec.ts new file mode 100644 index 0000000..2ffad6f --- /dev/null +++ b/src/app/account.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { AccountService } from './account.service'; + +describe('AccountService', () => { + let service: AccountService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(AccountService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/account.service.ts b/src/app/account.service.ts new file mode 100644 index 0000000..2b64b6a --- /dev/null +++ b/src/app/account.service.ts @@ -0,0 +1,96 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { environment } from 'src/environments/environment'; +import { sha256 } from 'sha.js'; +import { Router } from '@angular/router'; + +class Answer { + success: Boolean = false +} + +class PresaltAnswer extends Answer { + presalt: string = '' +} + +class AuthenticateAnswer extends Answer { + token: string = '' +} + +class CheckTokenAnswer extends Answer { + valid: Boolean = false +} + +class TokenData { + iss: string = '' + dat: string = '' + typ: string = '' + usr: string = '' +} + +@Injectable({ + providedIn: 'root' +}) +export class AccountService { + public username = '?'; + private userId: bigint = BigInt(0); + private userToken: bigint = BigInt(0); + + constructor(private http: HttpClient, private router: Router) { + this.updateUserInfo() + } + + public login(username: string, password: string, onFail: Function, onConnectionError: Function) { + this.http.post(`${environment.apiURL}/users/presalt`, {username}) + .subscribe((answer: Object) => { + var presaltAnswer = Object.assign(new PresaltAnswer(), answer) + if (!presaltAnswer.success) { + onFail() + return + } + // this presalting and hashing is done to protect the user's password in case of a man-in-the-middle-attack, + // where an attacker might be able to intercept a password if not hashed before. The password still has to be + // hashed on the server-side to be sure but this method prevents dumb users from having their passweord + // they use everywhere easily leaked, because you still need to crack this one layer first + // also, typical automated hacking software will hopefully be confused by my weird code + var passwordHash = new sha256().update(password + presaltAnswer.presalt).digest('hex') + this.http.post(`${environment.apiURL}/users/authenticate`, {username, passwordHash}) + .subscribe((answer) => { + var authenticateAnswer = Object.assign(new AuthenticateAnswer(), answer) + var loginSuccess = authenticateAnswer.success == true + if (loginSuccess) { + localStorage.setItem('userToken', authenticateAnswer.token) + this.updateUserInfo() + this.username = username + this.router.navigate(['admin']) + } else { + onFail() + } + }, (error) => {onConnectionError()}) + }, (error) => {onConnectionError()}) + } + + updateUserInfo() { + var token = localStorage.getItem('userToken') + if (token == null) { + this.username = '' + return + } + this.http.post(`${environment.apiURL}/users/checkToken`, {token}).subscribe((answer: Object) => { + var checkTokenAnswer = Object.assign(new CheckTokenAnswer(), answer) + if (checkTokenAnswer.valid == true) { + var data = JSON.parse(atob(token?.split('.')[1] as string)) + var tokenData = Object.assign(new TokenData(), data) + this.username = tokenData.usr + } else { + this.username = '' + } + }, () => { + this.username = '' + }) + } + + logout() { + this.username = '' + localStorage.removeItem('userToken') + } +} diff --git a/src/app/admin/admin.component.html b/src/app/admin/admin.component.html new file mode 100644 index 0000000..f949c13 --- /dev/null +++ b/src/app/admin/admin.component.html @@ -0,0 +1,6 @@ +

Hello {{account.username}}!

+

Welcome to the admin panel

+ +

There is nothing to do here yet...

+ + \ No newline at end of file diff --git a/src/app/admin/admin.component.scss b/src/app/admin/admin.component.scss new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/app/admin/admin.component.scss diff --git a/src/app/admin/admin.component.spec.ts b/src/app/admin/admin.component.spec.ts new file mode 100644 index 0000000..eb28e42 --- /dev/null +++ b/src/app/admin/admin.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AdminComponent } from './admin.component'; + +describe('AdminComponent', () => { + let component: AdminComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ AdminComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(AdminComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/admin/admin.component.ts b/src/app/admin/admin.component.ts new file mode 100644 index 0000000..85273be --- /dev/null +++ b/src/app/admin/admin.component.ts @@ -0,0 +1,20 @@ +import { Component, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; +import { AccountService } from '../account.service'; + +@Component({ + selector: 'app-admin', + templateUrl: './admin.component.html', + styleUrls: ['./admin.component.scss'] +}) +export class AdminComponent implements OnInit { + + constructor(public account: AccountService, private router: Router) { + if (account.username.length == 0) { + router.navigate(['login']) + } + } + + ngOnInit(): void { + } +} diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index b250123..9a07a6e 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -1,9 +1,13 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; +import { AdminComponent } from './admin/admin.component'; import { HomeComponent } from './home/home.component'; +import { LoginComponent } from './login/login.component'; const routes: Routes = [ - { path: "", component: HomeComponent } + { path: "", component: HomeComponent }, + { path: "login", component: LoginComponent }, + { path: "admin", component: AdminComponent }, ]; @NgModule({ diff --git a/src/app/app.module.ts b/src/app/app.module.ts index a6f5aac..1fb908c 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -4,6 +4,15 @@ import { MatSidenavModule } from '@angular/material/sidenav'; import { MatListModule } from '@angular/material/list'; import { MatIconModule } from '@angular/material/icon'; +import { MatCardModule } from '@angular/material/card'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatButtonModule } from '@angular/material/button'; +import { MatInputModule } from '@angular/material/input'; +import { MatMenuModule } from '@angular/material/menu'; +import { MatTableModule } from '@angular/material/table'; +import { MatSlideToggleModule } from '@angular/material/slide-toggle'; +import { MatSelectModule } from '@angular/material/select'; +import { MatOptionModule } from '@angular/material/core'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; @@ -13,13 +22,21 @@ import { FooterComponent } from './footer/footer.component'; import { GridComponent } from './grid/grid.component'; +import { AccountService } from './account.service'; +import { LoginComponent } from './login/login.component' +import { HttpClientModule } from '@angular/common/http'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { AdminComponent } from './admin/admin.component'; + @NgModule({ declarations: [ AppComponent, ToolbarComponent, HomeComponent, FooterComponent, - GridComponent + GridComponent, + LoginComponent, + AdminComponent ], imports: [ BrowserModule, @@ -29,8 +46,20 @@ MatSidenavModule, MatListModule, MatIconModule, + HttpClientModule, + MatCardModule, + MatFormFieldModule, + MatInputModule, + FormsModule, + ReactiveFormsModule, + MatButtonModule, + MatMenuModule, + MatTableModule, + MatSlideToggleModule, + MatSelectModule, + MatOptionModule, ], - providers: [], + providers: [AccountService], bootstrap: [AppComponent] }) export class AppModule { } diff --git a/src/app/login/login.component.html b/src/app/login/login.component.html new file mode 100644 index 0000000..54bebb0 --- /dev/null +++ b/src/app/login/login.component.html @@ -0,0 +1,21 @@ + +

Login

+
+

Enter your credentials here to log into the website.

+
+ + Username + this field must not be empty + + + + Password + this field must not be empty + + + +

Something went wrong while authenticating you. Make sure both the username and password fields are filled and are correct.

+

Could not connect to the API at {{environment.apiURL}}

+
+
+
\ No newline at end of file diff --git a/angular.json b/angular.json index 90d80e5..f23dd59 100644 --- a/angular.json +++ b/angular.json @@ -1,113 +1,113 @@ { - "$schema": "./node_modules/@angular/cli/lib/config/schema.json", - "version": 1, - "newProjectRoot": "projects", - "projects": { - "Website": { - "projectType": "application", - "schematics": { - "@schematics/angular:component": { - "style": "scss" - }, - "@schematics/angular:application": { - "strict": true - } - }, - "root": "", - "sourceRoot": "src", - "prefix": "app", - "architect": { - "build": { - "builder": "@angular-devkit/build-angular:browser", - "options": { - "outputPath": "dist/Website", - "index": "src/index.html", - "main": "src/main.ts", - "polyfills": "src/polyfills.ts", - "tsConfig": "tsconfig.app.json", - "inlineStyleLanguage": "scss", - "assets": [ - "src/favicon.ico", - "src/assets" - ], - "styles": [ - "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", - "src/styles.scss" - ], - "scripts": [] - }, - "configurations": { - "production": { - "budgets": [ - { - "type": "initial", - "maximumWarning": "500kb", - "maximumError": "1mb" + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "newProjectRoot": "projects", + "projects": { + "Website": { + "projectType": "application", + "schematics": { + "@schematics/angular:component": { + "style": "scss" }, - { - "type": "anyComponentStyle", - "maximumWarning": "2kb", - "maximumError": "4kb" + "@schematics/angular:application": { + "strict": true } - ], - "fileReplacements": [ - { - "replace": "src/environments/environment.ts", - "with": "src/environments/environment.prod.ts" + }, + "root": "", + "sourceRoot": "src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:browser", + "options": { + "outputPath": "dist/Website", + "index": "src/index.html", + "main": "src/main.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "tsconfig.app.json", + "inlineStyleLanguage": "scss", + "assets": [ + "src/favicon.ico", + "src/assets" + ], + "styles": [ + "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", + "src/styles.scss" + ], + "scripts": [] + }, + "configurations": { + "production": { + "budgets": [{ + "type": "initial", + "maximumWarning": "500kb", + "maximumError": "1mb" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "2kb", + "maximumError": "4kb" + } + ], + "fileReplacements": [{ + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.prod.ts" + }], + "outputHashing": "all" + }, + "development": { + "buildOptimizer": false, + "optimization": false, + "vendorChunk": true, + "extractLicenses": false, + "sourceMap": true, + "namedChunks": true + } + }, + "defaultConfiguration": "production" + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "configurations": { + "production": { + "browserTarget": "Website:build:production" + }, + "development": { + "browserTarget": "Website:build:development" + } + }, + "options": { + "proxyConfig": "src/proxy.conf.json" + }, + "defaultConfiguration": "development" + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n", + "options": { + "browserTarget": "Website:build" + } + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "main": "src/test.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "tsconfig.spec.json", + "karmaConfig": "karma.conf.js", + "inlineStyleLanguage": "scss", + "assets": [ + "src/favicon.ico", + "src/assets" + ], + "styles": [ + "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", + "src/styles.scss" + ], + "scripts": [] + } } - ], - "outputHashing": "all" - }, - "development": { - "buildOptimizer": false, - "optimization": false, - "vendorChunk": true, - "extractLicenses": false, - "sourceMap": true, - "namedChunks": true } - }, - "defaultConfiguration": "production" - }, - "serve": { - "builder": "@angular-devkit/build-angular:dev-server", - "configurations": { - "production": { - "browserTarget": "Website:build:production" - }, - "development": { - "browserTarget": "Website:build:development" - } - }, - "defaultConfiguration": "development" - }, - "extract-i18n": { - "builder": "@angular-devkit/build-angular:extract-i18n", - "options": { - "browserTarget": "Website:build" - } - }, - "test": { - "builder": "@angular-devkit/build-angular:karma", - "options": { - "main": "src/test.ts", - "polyfills": "src/polyfills.ts", - "tsConfig": "tsconfig.spec.json", - "karmaConfig": "karma.conf.js", - "inlineStyleLanguage": "scss", - "assets": [ - "src/favicon.ico", - "src/assets" - ], - "styles": [ - "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", - "src/styles.scss" - ], - "scripts": [] - } } - } - } - }, - "defaultProject": "Website" -} + }, + "defaultProject": "Website" +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 3713633..5807647 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "@angular/router": "~12.2.0", "hammerjs": "^2.0.8", "rxjs": "~6.6.0", + "sha.js": "^2.4.11", "tslib": "^2.3.0", "zone.js": "~0.11.4" }, @@ -29,6 +30,7 @@ "@angular/compiler-cli": "~12.2.0", "@types/jasmine": "~3.8.0", "@types/node": "^12.11.1", + "@types/sha.js": "^2.4.0", "jasmine-core": "~3.8.0", "karma": "~6.3.0", "karma-chrome-launcher": "~3.1.0", @@ -2614,6 +2616,15 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "node_modules/@types/sha.js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/sha.js/-/sha.js-2.4.0.tgz", + "integrity": "sha512-amxKgPy6WJTKuw8mpUwjX2BSxuBtBmZfRwIUDIuPJKNwGN8CWDli8JTg5ONTWOtcTkHIstvT7oAhhYXqEjStHQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/source-list-map": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", @@ -7279,8 +7290,7 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/ini": { "version": "2.0.0", @@ -12765,8 +12775,7 @@ "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "node_modules/safe-regex": { "version": "1.1.0", @@ -13106,6 +13115,18 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "dev": true }, + "node_modules/sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "bin": { + "sha.js": "bin.js" + } + }, "node_modules/shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", @@ -17552,6 +17573,15 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "@types/sha.js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/sha.js/-/sha.js-2.4.0.tgz", + "integrity": "sha512-amxKgPy6WJTKuw8mpUwjX2BSxuBtBmZfRwIUDIuPJKNwGN8CWDli8JTg5ONTWOtcTkHIstvT7oAhhYXqEjStHQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/source-list-map": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", @@ -21129,8 +21159,7 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "ini": { "version": "2.0.0", @@ -25219,8 +25248,7 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "safe-regex": { "version": "1.1.0", @@ -25494,6 +25522,15 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "dev": true }, + "sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, "shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", diff --git a/package.json b/package.json index 092ece5..2c9a675 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "@angular/router": "~12.2.0", "hammerjs": "^2.0.8", "rxjs": "~6.6.0", + "sha.js": "^2.4.11", "tslib": "^2.3.0", "zone.js": "~0.11.4" }, @@ -31,6 +32,7 @@ "@angular/compiler-cli": "~12.2.0", "@types/jasmine": "~3.8.0", "@types/node": "^12.11.1", + "@types/sha.js": "^2.4.0", "jasmine-core": "~3.8.0", "karma": "~6.3.0", "karma-chrome-launcher": "~3.1.0", diff --git a/src/app/account.service.spec.ts b/src/app/account.service.spec.ts new file mode 100644 index 0000000..2ffad6f --- /dev/null +++ b/src/app/account.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { AccountService } from './account.service'; + +describe('AccountService', () => { + let service: AccountService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(AccountService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/account.service.ts b/src/app/account.service.ts new file mode 100644 index 0000000..2b64b6a --- /dev/null +++ b/src/app/account.service.ts @@ -0,0 +1,96 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { environment } from 'src/environments/environment'; +import { sha256 } from 'sha.js'; +import { Router } from '@angular/router'; + +class Answer { + success: Boolean = false +} + +class PresaltAnswer extends Answer { + presalt: string = '' +} + +class AuthenticateAnswer extends Answer { + token: string = '' +} + +class CheckTokenAnswer extends Answer { + valid: Boolean = false +} + +class TokenData { + iss: string = '' + dat: string = '' + typ: string = '' + usr: string = '' +} + +@Injectable({ + providedIn: 'root' +}) +export class AccountService { + public username = '?'; + private userId: bigint = BigInt(0); + private userToken: bigint = BigInt(0); + + constructor(private http: HttpClient, private router: Router) { + this.updateUserInfo() + } + + public login(username: string, password: string, onFail: Function, onConnectionError: Function) { + this.http.post(`${environment.apiURL}/users/presalt`, {username}) + .subscribe((answer: Object) => { + var presaltAnswer = Object.assign(new PresaltAnswer(), answer) + if (!presaltAnswer.success) { + onFail() + return + } + // this presalting and hashing is done to protect the user's password in case of a man-in-the-middle-attack, + // where an attacker might be able to intercept a password if not hashed before. The password still has to be + // hashed on the server-side to be sure but this method prevents dumb users from having their passweord + // they use everywhere easily leaked, because you still need to crack this one layer first + // also, typical automated hacking software will hopefully be confused by my weird code + var passwordHash = new sha256().update(password + presaltAnswer.presalt).digest('hex') + this.http.post(`${environment.apiURL}/users/authenticate`, {username, passwordHash}) + .subscribe((answer) => { + var authenticateAnswer = Object.assign(new AuthenticateAnswer(), answer) + var loginSuccess = authenticateAnswer.success == true + if (loginSuccess) { + localStorage.setItem('userToken', authenticateAnswer.token) + this.updateUserInfo() + this.username = username + this.router.navigate(['admin']) + } else { + onFail() + } + }, (error) => {onConnectionError()}) + }, (error) => {onConnectionError()}) + } + + updateUserInfo() { + var token = localStorage.getItem('userToken') + if (token == null) { + this.username = '' + return + } + this.http.post(`${environment.apiURL}/users/checkToken`, {token}).subscribe((answer: Object) => { + var checkTokenAnswer = Object.assign(new CheckTokenAnswer(), answer) + if (checkTokenAnswer.valid == true) { + var data = JSON.parse(atob(token?.split('.')[1] as string)) + var tokenData = Object.assign(new TokenData(), data) + this.username = tokenData.usr + } else { + this.username = '' + } + }, () => { + this.username = '' + }) + } + + logout() { + this.username = '' + localStorage.removeItem('userToken') + } +} diff --git a/src/app/admin/admin.component.html b/src/app/admin/admin.component.html new file mode 100644 index 0000000..f949c13 --- /dev/null +++ b/src/app/admin/admin.component.html @@ -0,0 +1,6 @@ +

Hello {{account.username}}!

+

Welcome to the admin panel

+ +

There is nothing to do here yet...

+ + \ No newline at end of file diff --git a/src/app/admin/admin.component.scss b/src/app/admin/admin.component.scss new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/app/admin/admin.component.scss diff --git a/src/app/admin/admin.component.spec.ts b/src/app/admin/admin.component.spec.ts new file mode 100644 index 0000000..eb28e42 --- /dev/null +++ b/src/app/admin/admin.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AdminComponent } from './admin.component'; + +describe('AdminComponent', () => { + let component: AdminComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ AdminComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(AdminComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/admin/admin.component.ts b/src/app/admin/admin.component.ts new file mode 100644 index 0000000..85273be --- /dev/null +++ b/src/app/admin/admin.component.ts @@ -0,0 +1,20 @@ +import { Component, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; +import { AccountService } from '../account.service'; + +@Component({ + selector: 'app-admin', + templateUrl: './admin.component.html', + styleUrls: ['./admin.component.scss'] +}) +export class AdminComponent implements OnInit { + + constructor(public account: AccountService, private router: Router) { + if (account.username.length == 0) { + router.navigate(['login']) + } + } + + ngOnInit(): void { + } +} diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index b250123..9a07a6e 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -1,9 +1,13 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; +import { AdminComponent } from './admin/admin.component'; import { HomeComponent } from './home/home.component'; +import { LoginComponent } from './login/login.component'; const routes: Routes = [ - { path: "", component: HomeComponent } + { path: "", component: HomeComponent }, + { path: "login", component: LoginComponent }, + { path: "admin", component: AdminComponent }, ]; @NgModule({ diff --git a/src/app/app.module.ts b/src/app/app.module.ts index a6f5aac..1fb908c 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -4,6 +4,15 @@ import { MatSidenavModule } from '@angular/material/sidenav'; import { MatListModule } from '@angular/material/list'; import { MatIconModule } from '@angular/material/icon'; +import { MatCardModule } from '@angular/material/card'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatButtonModule } from '@angular/material/button'; +import { MatInputModule } from '@angular/material/input'; +import { MatMenuModule } from '@angular/material/menu'; +import { MatTableModule } from '@angular/material/table'; +import { MatSlideToggleModule } from '@angular/material/slide-toggle'; +import { MatSelectModule } from '@angular/material/select'; +import { MatOptionModule } from '@angular/material/core'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; @@ -13,13 +22,21 @@ import { FooterComponent } from './footer/footer.component'; import { GridComponent } from './grid/grid.component'; +import { AccountService } from './account.service'; +import { LoginComponent } from './login/login.component' +import { HttpClientModule } from '@angular/common/http'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { AdminComponent } from './admin/admin.component'; + @NgModule({ declarations: [ AppComponent, ToolbarComponent, HomeComponent, FooterComponent, - GridComponent + GridComponent, + LoginComponent, + AdminComponent ], imports: [ BrowserModule, @@ -29,8 +46,20 @@ MatSidenavModule, MatListModule, MatIconModule, + HttpClientModule, + MatCardModule, + MatFormFieldModule, + MatInputModule, + FormsModule, + ReactiveFormsModule, + MatButtonModule, + MatMenuModule, + MatTableModule, + MatSlideToggleModule, + MatSelectModule, + MatOptionModule, ], - providers: [], + providers: [AccountService], bootstrap: [AppComponent] }) export class AppModule { } diff --git a/src/app/login/login.component.html b/src/app/login/login.component.html new file mode 100644 index 0000000..54bebb0 --- /dev/null +++ b/src/app/login/login.component.html @@ -0,0 +1,21 @@ + +

Login

+
+

Enter your credentials here to log into the website.

+
+ + Username + this field must not be empty + + + + Password + this field must not be empty + + + +

Something went wrong while authenticating you. Make sure both the username and password fields are filled and are correct.

+

Could not connect to the API at {{environment.apiURL}}

+
+
+
\ No newline at end of file diff --git a/src/app/login/login.component.scss b/src/app/login/login.component.scss new file mode 100644 index 0000000..75bbd4b --- /dev/null +++ b/src/app/login/login.component.scss @@ -0,0 +1,13 @@ +mat-card { + max-width: 40ch; +} + +:host { + display: flex; + justify-content: space-around; + padding: 20px; +} + +mat-form-field { + width: 100%; +} \ No newline at end of file diff --git a/angular.json b/angular.json index 90d80e5..f23dd59 100644 --- a/angular.json +++ b/angular.json @@ -1,113 +1,113 @@ { - "$schema": "./node_modules/@angular/cli/lib/config/schema.json", - "version": 1, - "newProjectRoot": "projects", - "projects": { - "Website": { - "projectType": "application", - "schematics": { - "@schematics/angular:component": { - "style": "scss" - }, - "@schematics/angular:application": { - "strict": true - } - }, - "root": "", - "sourceRoot": "src", - "prefix": "app", - "architect": { - "build": { - "builder": "@angular-devkit/build-angular:browser", - "options": { - "outputPath": "dist/Website", - "index": "src/index.html", - "main": "src/main.ts", - "polyfills": "src/polyfills.ts", - "tsConfig": "tsconfig.app.json", - "inlineStyleLanguage": "scss", - "assets": [ - "src/favicon.ico", - "src/assets" - ], - "styles": [ - "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", - "src/styles.scss" - ], - "scripts": [] - }, - "configurations": { - "production": { - "budgets": [ - { - "type": "initial", - "maximumWarning": "500kb", - "maximumError": "1mb" + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "newProjectRoot": "projects", + "projects": { + "Website": { + "projectType": "application", + "schematics": { + "@schematics/angular:component": { + "style": "scss" }, - { - "type": "anyComponentStyle", - "maximumWarning": "2kb", - "maximumError": "4kb" + "@schematics/angular:application": { + "strict": true } - ], - "fileReplacements": [ - { - "replace": "src/environments/environment.ts", - "with": "src/environments/environment.prod.ts" + }, + "root": "", + "sourceRoot": "src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:browser", + "options": { + "outputPath": "dist/Website", + "index": "src/index.html", + "main": "src/main.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "tsconfig.app.json", + "inlineStyleLanguage": "scss", + "assets": [ + "src/favicon.ico", + "src/assets" + ], + "styles": [ + "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", + "src/styles.scss" + ], + "scripts": [] + }, + "configurations": { + "production": { + "budgets": [{ + "type": "initial", + "maximumWarning": "500kb", + "maximumError": "1mb" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "2kb", + "maximumError": "4kb" + } + ], + "fileReplacements": [{ + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.prod.ts" + }], + "outputHashing": "all" + }, + "development": { + "buildOptimizer": false, + "optimization": false, + "vendorChunk": true, + "extractLicenses": false, + "sourceMap": true, + "namedChunks": true + } + }, + "defaultConfiguration": "production" + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "configurations": { + "production": { + "browserTarget": "Website:build:production" + }, + "development": { + "browserTarget": "Website:build:development" + } + }, + "options": { + "proxyConfig": "src/proxy.conf.json" + }, + "defaultConfiguration": "development" + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n", + "options": { + "browserTarget": "Website:build" + } + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "main": "src/test.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "tsconfig.spec.json", + "karmaConfig": "karma.conf.js", + "inlineStyleLanguage": "scss", + "assets": [ + "src/favicon.ico", + "src/assets" + ], + "styles": [ + "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", + "src/styles.scss" + ], + "scripts": [] + } } - ], - "outputHashing": "all" - }, - "development": { - "buildOptimizer": false, - "optimization": false, - "vendorChunk": true, - "extractLicenses": false, - "sourceMap": true, - "namedChunks": true } - }, - "defaultConfiguration": "production" - }, - "serve": { - "builder": "@angular-devkit/build-angular:dev-server", - "configurations": { - "production": { - "browserTarget": "Website:build:production" - }, - "development": { - "browserTarget": "Website:build:development" - } - }, - "defaultConfiguration": "development" - }, - "extract-i18n": { - "builder": "@angular-devkit/build-angular:extract-i18n", - "options": { - "browserTarget": "Website:build" - } - }, - "test": { - "builder": "@angular-devkit/build-angular:karma", - "options": { - "main": "src/test.ts", - "polyfills": "src/polyfills.ts", - "tsConfig": "tsconfig.spec.json", - "karmaConfig": "karma.conf.js", - "inlineStyleLanguage": "scss", - "assets": [ - "src/favicon.ico", - "src/assets" - ], - "styles": [ - "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", - "src/styles.scss" - ], - "scripts": [] - } } - } - } - }, - "defaultProject": "Website" -} + }, + "defaultProject": "Website" +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 3713633..5807647 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "@angular/router": "~12.2.0", "hammerjs": "^2.0.8", "rxjs": "~6.6.0", + "sha.js": "^2.4.11", "tslib": "^2.3.0", "zone.js": "~0.11.4" }, @@ -29,6 +30,7 @@ "@angular/compiler-cli": "~12.2.0", "@types/jasmine": "~3.8.0", "@types/node": "^12.11.1", + "@types/sha.js": "^2.4.0", "jasmine-core": "~3.8.0", "karma": "~6.3.0", "karma-chrome-launcher": "~3.1.0", @@ -2614,6 +2616,15 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "node_modules/@types/sha.js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/sha.js/-/sha.js-2.4.0.tgz", + "integrity": "sha512-amxKgPy6WJTKuw8mpUwjX2BSxuBtBmZfRwIUDIuPJKNwGN8CWDli8JTg5ONTWOtcTkHIstvT7oAhhYXqEjStHQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/source-list-map": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", @@ -7279,8 +7290,7 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/ini": { "version": "2.0.0", @@ -12765,8 +12775,7 @@ "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "node_modules/safe-regex": { "version": "1.1.0", @@ -13106,6 +13115,18 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "dev": true }, + "node_modules/sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "bin": { + "sha.js": "bin.js" + } + }, "node_modules/shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", @@ -17552,6 +17573,15 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "@types/sha.js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/sha.js/-/sha.js-2.4.0.tgz", + "integrity": "sha512-amxKgPy6WJTKuw8mpUwjX2BSxuBtBmZfRwIUDIuPJKNwGN8CWDli8JTg5ONTWOtcTkHIstvT7oAhhYXqEjStHQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/source-list-map": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", @@ -21129,8 +21159,7 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "ini": { "version": "2.0.0", @@ -25219,8 +25248,7 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "safe-regex": { "version": "1.1.0", @@ -25494,6 +25522,15 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "dev": true }, + "sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, "shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", diff --git a/package.json b/package.json index 092ece5..2c9a675 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "@angular/router": "~12.2.0", "hammerjs": "^2.0.8", "rxjs": "~6.6.0", + "sha.js": "^2.4.11", "tslib": "^2.3.0", "zone.js": "~0.11.4" }, @@ -31,6 +32,7 @@ "@angular/compiler-cli": "~12.2.0", "@types/jasmine": "~3.8.0", "@types/node": "^12.11.1", + "@types/sha.js": "^2.4.0", "jasmine-core": "~3.8.0", "karma": "~6.3.0", "karma-chrome-launcher": "~3.1.0", diff --git a/src/app/account.service.spec.ts b/src/app/account.service.spec.ts new file mode 100644 index 0000000..2ffad6f --- /dev/null +++ b/src/app/account.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { AccountService } from './account.service'; + +describe('AccountService', () => { + let service: AccountService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(AccountService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/account.service.ts b/src/app/account.service.ts new file mode 100644 index 0000000..2b64b6a --- /dev/null +++ b/src/app/account.service.ts @@ -0,0 +1,96 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { environment } from 'src/environments/environment'; +import { sha256 } from 'sha.js'; +import { Router } from '@angular/router'; + +class Answer { + success: Boolean = false +} + +class PresaltAnswer extends Answer { + presalt: string = '' +} + +class AuthenticateAnswer extends Answer { + token: string = '' +} + +class CheckTokenAnswer extends Answer { + valid: Boolean = false +} + +class TokenData { + iss: string = '' + dat: string = '' + typ: string = '' + usr: string = '' +} + +@Injectable({ + providedIn: 'root' +}) +export class AccountService { + public username = '?'; + private userId: bigint = BigInt(0); + private userToken: bigint = BigInt(0); + + constructor(private http: HttpClient, private router: Router) { + this.updateUserInfo() + } + + public login(username: string, password: string, onFail: Function, onConnectionError: Function) { + this.http.post(`${environment.apiURL}/users/presalt`, {username}) + .subscribe((answer: Object) => { + var presaltAnswer = Object.assign(new PresaltAnswer(), answer) + if (!presaltAnswer.success) { + onFail() + return + } + // this presalting and hashing is done to protect the user's password in case of a man-in-the-middle-attack, + // where an attacker might be able to intercept a password if not hashed before. The password still has to be + // hashed on the server-side to be sure but this method prevents dumb users from having their passweord + // they use everywhere easily leaked, because you still need to crack this one layer first + // also, typical automated hacking software will hopefully be confused by my weird code + var passwordHash = new sha256().update(password + presaltAnswer.presalt).digest('hex') + this.http.post(`${environment.apiURL}/users/authenticate`, {username, passwordHash}) + .subscribe((answer) => { + var authenticateAnswer = Object.assign(new AuthenticateAnswer(), answer) + var loginSuccess = authenticateAnswer.success == true + if (loginSuccess) { + localStorage.setItem('userToken', authenticateAnswer.token) + this.updateUserInfo() + this.username = username + this.router.navigate(['admin']) + } else { + onFail() + } + }, (error) => {onConnectionError()}) + }, (error) => {onConnectionError()}) + } + + updateUserInfo() { + var token = localStorage.getItem('userToken') + if (token == null) { + this.username = '' + return + } + this.http.post(`${environment.apiURL}/users/checkToken`, {token}).subscribe((answer: Object) => { + var checkTokenAnswer = Object.assign(new CheckTokenAnswer(), answer) + if (checkTokenAnswer.valid == true) { + var data = JSON.parse(atob(token?.split('.')[1] as string)) + var tokenData = Object.assign(new TokenData(), data) + this.username = tokenData.usr + } else { + this.username = '' + } + }, () => { + this.username = '' + }) + } + + logout() { + this.username = '' + localStorage.removeItem('userToken') + } +} diff --git a/src/app/admin/admin.component.html b/src/app/admin/admin.component.html new file mode 100644 index 0000000..f949c13 --- /dev/null +++ b/src/app/admin/admin.component.html @@ -0,0 +1,6 @@ +

Hello {{account.username}}!

+

Welcome to the admin panel

+ +

There is nothing to do here yet...

+ + \ No newline at end of file diff --git a/src/app/admin/admin.component.scss b/src/app/admin/admin.component.scss new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/app/admin/admin.component.scss diff --git a/src/app/admin/admin.component.spec.ts b/src/app/admin/admin.component.spec.ts new file mode 100644 index 0000000..eb28e42 --- /dev/null +++ b/src/app/admin/admin.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AdminComponent } from './admin.component'; + +describe('AdminComponent', () => { + let component: AdminComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ AdminComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(AdminComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/admin/admin.component.ts b/src/app/admin/admin.component.ts new file mode 100644 index 0000000..85273be --- /dev/null +++ b/src/app/admin/admin.component.ts @@ -0,0 +1,20 @@ +import { Component, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; +import { AccountService } from '../account.service'; + +@Component({ + selector: 'app-admin', + templateUrl: './admin.component.html', + styleUrls: ['./admin.component.scss'] +}) +export class AdminComponent implements OnInit { + + constructor(public account: AccountService, private router: Router) { + if (account.username.length == 0) { + router.navigate(['login']) + } + } + + ngOnInit(): void { + } +} diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index b250123..9a07a6e 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -1,9 +1,13 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; +import { AdminComponent } from './admin/admin.component'; import { HomeComponent } from './home/home.component'; +import { LoginComponent } from './login/login.component'; const routes: Routes = [ - { path: "", component: HomeComponent } + { path: "", component: HomeComponent }, + { path: "login", component: LoginComponent }, + { path: "admin", component: AdminComponent }, ]; @NgModule({ diff --git a/src/app/app.module.ts b/src/app/app.module.ts index a6f5aac..1fb908c 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -4,6 +4,15 @@ import { MatSidenavModule } from '@angular/material/sidenav'; import { MatListModule } from '@angular/material/list'; import { MatIconModule } from '@angular/material/icon'; +import { MatCardModule } from '@angular/material/card'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatButtonModule } from '@angular/material/button'; +import { MatInputModule } from '@angular/material/input'; +import { MatMenuModule } from '@angular/material/menu'; +import { MatTableModule } from '@angular/material/table'; +import { MatSlideToggleModule } from '@angular/material/slide-toggle'; +import { MatSelectModule } from '@angular/material/select'; +import { MatOptionModule } from '@angular/material/core'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; @@ -13,13 +22,21 @@ import { FooterComponent } from './footer/footer.component'; import { GridComponent } from './grid/grid.component'; +import { AccountService } from './account.service'; +import { LoginComponent } from './login/login.component' +import { HttpClientModule } from '@angular/common/http'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { AdminComponent } from './admin/admin.component'; + @NgModule({ declarations: [ AppComponent, ToolbarComponent, HomeComponent, FooterComponent, - GridComponent + GridComponent, + LoginComponent, + AdminComponent ], imports: [ BrowserModule, @@ -29,8 +46,20 @@ MatSidenavModule, MatListModule, MatIconModule, + HttpClientModule, + MatCardModule, + MatFormFieldModule, + MatInputModule, + FormsModule, + ReactiveFormsModule, + MatButtonModule, + MatMenuModule, + MatTableModule, + MatSlideToggleModule, + MatSelectModule, + MatOptionModule, ], - providers: [], + providers: [AccountService], bootstrap: [AppComponent] }) export class AppModule { } diff --git a/src/app/login/login.component.html b/src/app/login/login.component.html new file mode 100644 index 0000000..54bebb0 --- /dev/null +++ b/src/app/login/login.component.html @@ -0,0 +1,21 @@ + +

Login

+
+

Enter your credentials here to log into the website.

+
+ + Username + this field must not be empty + + + + Password + this field must not be empty + + + +

Something went wrong while authenticating you. Make sure both the username and password fields are filled and are correct.

+

Could not connect to the API at {{environment.apiURL}}

+
+
+
\ No newline at end of file diff --git a/src/app/login/login.component.scss b/src/app/login/login.component.scss new file mode 100644 index 0000000..75bbd4b --- /dev/null +++ b/src/app/login/login.component.scss @@ -0,0 +1,13 @@ +mat-card { + max-width: 40ch; +} + +:host { + display: flex; + justify-content: space-around; + padding: 20px; +} + +mat-form-field { + width: 100%; +} \ No newline at end of file diff --git a/src/app/login/login.component.spec.ts b/src/app/login/login.component.spec.ts new file mode 100644 index 0000000..d2c0e6c --- /dev/null +++ b/src/app/login/login.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { LoginComponent } from './login.component'; + +describe('LoginComponent', () => { + let component: LoginComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ LoginComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(LoginComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/angular.json b/angular.json index 90d80e5..f23dd59 100644 --- a/angular.json +++ b/angular.json @@ -1,113 +1,113 @@ { - "$schema": "./node_modules/@angular/cli/lib/config/schema.json", - "version": 1, - "newProjectRoot": "projects", - "projects": { - "Website": { - "projectType": "application", - "schematics": { - "@schematics/angular:component": { - "style": "scss" - }, - "@schematics/angular:application": { - "strict": true - } - }, - "root": "", - "sourceRoot": "src", - "prefix": "app", - "architect": { - "build": { - "builder": "@angular-devkit/build-angular:browser", - "options": { - "outputPath": "dist/Website", - "index": "src/index.html", - "main": "src/main.ts", - "polyfills": "src/polyfills.ts", - "tsConfig": "tsconfig.app.json", - "inlineStyleLanguage": "scss", - "assets": [ - "src/favicon.ico", - "src/assets" - ], - "styles": [ - "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", - "src/styles.scss" - ], - "scripts": [] - }, - "configurations": { - "production": { - "budgets": [ - { - "type": "initial", - "maximumWarning": "500kb", - "maximumError": "1mb" + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "newProjectRoot": "projects", + "projects": { + "Website": { + "projectType": "application", + "schematics": { + "@schematics/angular:component": { + "style": "scss" }, - { - "type": "anyComponentStyle", - "maximumWarning": "2kb", - "maximumError": "4kb" + "@schematics/angular:application": { + "strict": true } - ], - "fileReplacements": [ - { - "replace": "src/environments/environment.ts", - "with": "src/environments/environment.prod.ts" + }, + "root": "", + "sourceRoot": "src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:browser", + "options": { + "outputPath": "dist/Website", + "index": "src/index.html", + "main": "src/main.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "tsconfig.app.json", + "inlineStyleLanguage": "scss", + "assets": [ + "src/favicon.ico", + "src/assets" + ], + "styles": [ + "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", + "src/styles.scss" + ], + "scripts": [] + }, + "configurations": { + "production": { + "budgets": [{ + "type": "initial", + "maximumWarning": "500kb", + "maximumError": "1mb" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "2kb", + "maximumError": "4kb" + } + ], + "fileReplacements": [{ + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.prod.ts" + }], + "outputHashing": "all" + }, + "development": { + "buildOptimizer": false, + "optimization": false, + "vendorChunk": true, + "extractLicenses": false, + "sourceMap": true, + "namedChunks": true + } + }, + "defaultConfiguration": "production" + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "configurations": { + "production": { + "browserTarget": "Website:build:production" + }, + "development": { + "browserTarget": "Website:build:development" + } + }, + "options": { + "proxyConfig": "src/proxy.conf.json" + }, + "defaultConfiguration": "development" + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n", + "options": { + "browserTarget": "Website:build" + } + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "main": "src/test.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "tsconfig.spec.json", + "karmaConfig": "karma.conf.js", + "inlineStyleLanguage": "scss", + "assets": [ + "src/favicon.ico", + "src/assets" + ], + "styles": [ + "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", + "src/styles.scss" + ], + "scripts": [] + } } - ], - "outputHashing": "all" - }, - "development": { - "buildOptimizer": false, - "optimization": false, - "vendorChunk": true, - "extractLicenses": false, - "sourceMap": true, - "namedChunks": true } - }, - "defaultConfiguration": "production" - }, - "serve": { - "builder": "@angular-devkit/build-angular:dev-server", - "configurations": { - "production": { - "browserTarget": "Website:build:production" - }, - "development": { - "browserTarget": "Website:build:development" - } - }, - "defaultConfiguration": "development" - }, - "extract-i18n": { - "builder": "@angular-devkit/build-angular:extract-i18n", - "options": { - "browserTarget": "Website:build" - } - }, - "test": { - "builder": "@angular-devkit/build-angular:karma", - "options": { - "main": "src/test.ts", - "polyfills": "src/polyfills.ts", - "tsConfig": "tsconfig.spec.json", - "karmaConfig": "karma.conf.js", - "inlineStyleLanguage": "scss", - "assets": [ - "src/favicon.ico", - "src/assets" - ], - "styles": [ - "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", - "src/styles.scss" - ], - "scripts": [] - } } - } - } - }, - "defaultProject": "Website" -} + }, + "defaultProject": "Website" +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 3713633..5807647 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "@angular/router": "~12.2.0", "hammerjs": "^2.0.8", "rxjs": "~6.6.0", + "sha.js": "^2.4.11", "tslib": "^2.3.0", "zone.js": "~0.11.4" }, @@ -29,6 +30,7 @@ "@angular/compiler-cli": "~12.2.0", "@types/jasmine": "~3.8.0", "@types/node": "^12.11.1", + "@types/sha.js": "^2.4.0", "jasmine-core": "~3.8.0", "karma": "~6.3.0", "karma-chrome-launcher": "~3.1.0", @@ -2614,6 +2616,15 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "node_modules/@types/sha.js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/sha.js/-/sha.js-2.4.0.tgz", + "integrity": "sha512-amxKgPy6WJTKuw8mpUwjX2BSxuBtBmZfRwIUDIuPJKNwGN8CWDli8JTg5ONTWOtcTkHIstvT7oAhhYXqEjStHQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/source-list-map": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", @@ -7279,8 +7290,7 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/ini": { "version": "2.0.0", @@ -12765,8 +12775,7 @@ "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "node_modules/safe-regex": { "version": "1.1.0", @@ -13106,6 +13115,18 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "dev": true }, + "node_modules/sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "bin": { + "sha.js": "bin.js" + } + }, "node_modules/shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", @@ -17552,6 +17573,15 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "@types/sha.js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/sha.js/-/sha.js-2.4.0.tgz", + "integrity": "sha512-amxKgPy6WJTKuw8mpUwjX2BSxuBtBmZfRwIUDIuPJKNwGN8CWDli8JTg5ONTWOtcTkHIstvT7oAhhYXqEjStHQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/source-list-map": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", @@ -21129,8 +21159,7 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "ini": { "version": "2.0.0", @@ -25219,8 +25248,7 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "safe-regex": { "version": "1.1.0", @@ -25494,6 +25522,15 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "dev": true }, + "sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, "shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", diff --git a/package.json b/package.json index 092ece5..2c9a675 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "@angular/router": "~12.2.0", "hammerjs": "^2.0.8", "rxjs": "~6.6.0", + "sha.js": "^2.4.11", "tslib": "^2.3.0", "zone.js": "~0.11.4" }, @@ -31,6 +32,7 @@ "@angular/compiler-cli": "~12.2.0", "@types/jasmine": "~3.8.0", "@types/node": "^12.11.1", + "@types/sha.js": "^2.4.0", "jasmine-core": "~3.8.0", "karma": "~6.3.0", "karma-chrome-launcher": "~3.1.0", diff --git a/src/app/account.service.spec.ts b/src/app/account.service.spec.ts new file mode 100644 index 0000000..2ffad6f --- /dev/null +++ b/src/app/account.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { AccountService } from './account.service'; + +describe('AccountService', () => { + let service: AccountService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(AccountService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/account.service.ts b/src/app/account.service.ts new file mode 100644 index 0000000..2b64b6a --- /dev/null +++ b/src/app/account.service.ts @@ -0,0 +1,96 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { environment } from 'src/environments/environment'; +import { sha256 } from 'sha.js'; +import { Router } from '@angular/router'; + +class Answer { + success: Boolean = false +} + +class PresaltAnswer extends Answer { + presalt: string = '' +} + +class AuthenticateAnswer extends Answer { + token: string = '' +} + +class CheckTokenAnswer extends Answer { + valid: Boolean = false +} + +class TokenData { + iss: string = '' + dat: string = '' + typ: string = '' + usr: string = '' +} + +@Injectable({ + providedIn: 'root' +}) +export class AccountService { + public username = '?'; + private userId: bigint = BigInt(0); + private userToken: bigint = BigInt(0); + + constructor(private http: HttpClient, private router: Router) { + this.updateUserInfo() + } + + public login(username: string, password: string, onFail: Function, onConnectionError: Function) { + this.http.post(`${environment.apiURL}/users/presalt`, {username}) + .subscribe((answer: Object) => { + var presaltAnswer = Object.assign(new PresaltAnswer(), answer) + if (!presaltAnswer.success) { + onFail() + return + } + // this presalting and hashing is done to protect the user's password in case of a man-in-the-middle-attack, + // where an attacker might be able to intercept a password if not hashed before. The password still has to be + // hashed on the server-side to be sure but this method prevents dumb users from having their passweord + // they use everywhere easily leaked, because you still need to crack this one layer first + // also, typical automated hacking software will hopefully be confused by my weird code + var passwordHash = new sha256().update(password + presaltAnswer.presalt).digest('hex') + this.http.post(`${environment.apiURL}/users/authenticate`, {username, passwordHash}) + .subscribe((answer) => { + var authenticateAnswer = Object.assign(new AuthenticateAnswer(), answer) + var loginSuccess = authenticateAnswer.success == true + if (loginSuccess) { + localStorage.setItem('userToken', authenticateAnswer.token) + this.updateUserInfo() + this.username = username + this.router.navigate(['admin']) + } else { + onFail() + } + }, (error) => {onConnectionError()}) + }, (error) => {onConnectionError()}) + } + + updateUserInfo() { + var token = localStorage.getItem('userToken') + if (token == null) { + this.username = '' + return + } + this.http.post(`${environment.apiURL}/users/checkToken`, {token}).subscribe((answer: Object) => { + var checkTokenAnswer = Object.assign(new CheckTokenAnswer(), answer) + if (checkTokenAnswer.valid == true) { + var data = JSON.parse(atob(token?.split('.')[1] as string)) + var tokenData = Object.assign(new TokenData(), data) + this.username = tokenData.usr + } else { + this.username = '' + } + }, () => { + this.username = '' + }) + } + + logout() { + this.username = '' + localStorage.removeItem('userToken') + } +} diff --git a/src/app/admin/admin.component.html b/src/app/admin/admin.component.html new file mode 100644 index 0000000..f949c13 --- /dev/null +++ b/src/app/admin/admin.component.html @@ -0,0 +1,6 @@ +

Hello {{account.username}}!

+

Welcome to the admin panel

+ +

There is nothing to do here yet...

+ + \ No newline at end of file diff --git a/src/app/admin/admin.component.scss b/src/app/admin/admin.component.scss new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/app/admin/admin.component.scss diff --git a/src/app/admin/admin.component.spec.ts b/src/app/admin/admin.component.spec.ts new file mode 100644 index 0000000..eb28e42 --- /dev/null +++ b/src/app/admin/admin.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AdminComponent } from './admin.component'; + +describe('AdminComponent', () => { + let component: AdminComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ AdminComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(AdminComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/admin/admin.component.ts b/src/app/admin/admin.component.ts new file mode 100644 index 0000000..85273be --- /dev/null +++ b/src/app/admin/admin.component.ts @@ -0,0 +1,20 @@ +import { Component, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; +import { AccountService } from '../account.service'; + +@Component({ + selector: 'app-admin', + templateUrl: './admin.component.html', + styleUrls: ['./admin.component.scss'] +}) +export class AdminComponent implements OnInit { + + constructor(public account: AccountService, private router: Router) { + if (account.username.length == 0) { + router.navigate(['login']) + } + } + + ngOnInit(): void { + } +} diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index b250123..9a07a6e 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -1,9 +1,13 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; +import { AdminComponent } from './admin/admin.component'; import { HomeComponent } from './home/home.component'; +import { LoginComponent } from './login/login.component'; const routes: Routes = [ - { path: "", component: HomeComponent } + { path: "", component: HomeComponent }, + { path: "login", component: LoginComponent }, + { path: "admin", component: AdminComponent }, ]; @NgModule({ diff --git a/src/app/app.module.ts b/src/app/app.module.ts index a6f5aac..1fb908c 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -4,6 +4,15 @@ import { MatSidenavModule } from '@angular/material/sidenav'; import { MatListModule } from '@angular/material/list'; import { MatIconModule } from '@angular/material/icon'; +import { MatCardModule } from '@angular/material/card'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatButtonModule } from '@angular/material/button'; +import { MatInputModule } from '@angular/material/input'; +import { MatMenuModule } from '@angular/material/menu'; +import { MatTableModule } from '@angular/material/table'; +import { MatSlideToggleModule } from '@angular/material/slide-toggle'; +import { MatSelectModule } from '@angular/material/select'; +import { MatOptionModule } from '@angular/material/core'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; @@ -13,13 +22,21 @@ import { FooterComponent } from './footer/footer.component'; import { GridComponent } from './grid/grid.component'; +import { AccountService } from './account.service'; +import { LoginComponent } from './login/login.component' +import { HttpClientModule } from '@angular/common/http'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { AdminComponent } from './admin/admin.component'; + @NgModule({ declarations: [ AppComponent, ToolbarComponent, HomeComponent, FooterComponent, - GridComponent + GridComponent, + LoginComponent, + AdminComponent ], imports: [ BrowserModule, @@ -29,8 +46,20 @@ MatSidenavModule, MatListModule, MatIconModule, + HttpClientModule, + MatCardModule, + MatFormFieldModule, + MatInputModule, + FormsModule, + ReactiveFormsModule, + MatButtonModule, + MatMenuModule, + MatTableModule, + MatSlideToggleModule, + MatSelectModule, + MatOptionModule, ], - providers: [], + providers: [AccountService], bootstrap: [AppComponent] }) export class AppModule { } diff --git a/src/app/login/login.component.html b/src/app/login/login.component.html new file mode 100644 index 0000000..54bebb0 --- /dev/null +++ b/src/app/login/login.component.html @@ -0,0 +1,21 @@ + +

Login

+
+

Enter your credentials here to log into the website.

+
+ + Username + this field must not be empty + + + + Password + this field must not be empty + + + +

Something went wrong while authenticating you. Make sure both the username and password fields are filled and are correct.

+

Could not connect to the API at {{environment.apiURL}}

+
+
+
\ No newline at end of file diff --git a/src/app/login/login.component.scss b/src/app/login/login.component.scss new file mode 100644 index 0000000..75bbd4b --- /dev/null +++ b/src/app/login/login.component.scss @@ -0,0 +1,13 @@ +mat-card { + max-width: 40ch; +} + +:host { + display: flex; + justify-content: space-around; + padding: 20px; +} + +mat-form-field { + width: 100%; +} \ No newline at end of file diff --git a/src/app/login/login.component.spec.ts b/src/app/login/login.component.spec.ts new file mode 100644 index 0000000..d2c0e6c --- /dev/null +++ b/src/app/login/login.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { LoginComponent } from './login.component'; + +describe('LoginComponent', () => { + let component: LoginComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ LoginComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(LoginComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/login/login.component.ts b/src/app/login/login.component.ts new file mode 100644 index 0000000..450a8f0 --- /dev/null +++ b/src/app/login/login.component.ts @@ -0,0 +1,43 @@ +import { Component, OnInit } from '@angular/core'; +import { FormBuilder, FormGroup, NgForm, Validators } from '@angular/forms'; +import { environment } from 'src/environments/environment'; +import { AccountService } from '../account.service'; + +class LoginInformation { + constructor(public username: string, public password: string) { + + } +} + +@Component({ + selector: 'app-login', + templateUrl: './login.component.html', + styleUrls: ['./login.component.scss'] +}) +export class LoginComponent implements OnInit { + model = new LoginInformation('', '') + public form: FormGroup + public valid = true + public connectionError = false + public environment = environment + constructor(private formBuilder: FormBuilder, private account: AccountService) { + this.form = this.formBuilder.group({ + username: ['', Validators.required], + password: ['', Validators.required] + }) + } + + ngOnInit(): void { + } + + onSubmit() { + if (this.form.invalid) { + this.valid = false + return + } + this.valid = true + this.connectionError = false + this.account.login(this.form.controls.username.value, this.form.controls.password.value, + () => {this.valid = false}, () => {this.connectionError = true}); + } +} diff --git a/angular.json b/angular.json index 90d80e5..f23dd59 100644 --- a/angular.json +++ b/angular.json @@ -1,113 +1,113 @@ { - "$schema": "./node_modules/@angular/cli/lib/config/schema.json", - "version": 1, - "newProjectRoot": "projects", - "projects": { - "Website": { - "projectType": "application", - "schematics": { - "@schematics/angular:component": { - "style": "scss" - }, - "@schematics/angular:application": { - "strict": true - } - }, - "root": "", - "sourceRoot": "src", - "prefix": "app", - "architect": { - "build": { - "builder": "@angular-devkit/build-angular:browser", - "options": { - "outputPath": "dist/Website", - "index": "src/index.html", - "main": "src/main.ts", - "polyfills": "src/polyfills.ts", - "tsConfig": "tsconfig.app.json", - "inlineStyleLanguage": "scss", - "assets": [ - "src/favicon.ico", - "src/assets" - ], - "styles": [ - "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", - "src/styles.scss" - ], - "scripts": [] - }, - "configurations": { - "production": { - "budgets": [ - { - "type": "initial", - "maximumWarning": "500kb", - "maximumError": "1mb" + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "newProjectRoot": "projects", + "projects": { + "Website": { + "projectType": "application", + "schematics": { + "@schematics/angular:component": { + "style": "scss" }, - { - "type": "anyComponentStyle", - "maximumWarning": "2kb", - "maximumError": "4kb" + "@schematics/angular:application": { + "strict": true } - ], - "fileReplacements": [ - { - "replace": "src/environments/environment.ts", - "with": "src/environments/environment.prod.ts" + }, + "root": "", + "sourceRoot": "src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:browser", + "options": { + "outputPath": "dist/Website", + "index": "src/index.html", + "main": "src/main.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "tsconfig.app.json", + "inlineStyleLanguage": "scss", + "assets": [ + "src/favicon.ico", + "src/assets" + ], + "styles": [ + "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", + "src/styles.scss" + ], + "scripts": [] + }, + "configurations": { + "production": { + "budgets": [{ + "type": "initial", + "maximumWarning": "500kb", + "maximumError": "1mb" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "2kb", + "maximumError": "4kb" + } + ], + "fileReplacements": [{ + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.prod.ts" + }], + "outputHashing": "all" + }, + "development": { + "buildOptimizer": false, + "optimization": false, + "vendorChunk": true, + "extractLicenses": false, + "sourceMap": true, + "namedChunks": true + } + }, + "defaultConfiguration": "production" + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "configurations": { + "production": { + "browserTarget": "Website:build:production" + }, + "development": { + "browserTarget": "Website:build:development" + } + }, + "options": { + "proxyConfig": "src/proxy.conf.json" + }, + "defaultConfiguration": "development" + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n", + "options": { + "browserTarget": "Website:build" + } + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "main": "src/test.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "tsconfig.spec.json", + "karmaConfig": "karma.conf.js", + "inlineStyleLanguage": "scss", + "assets": [ + "src/favicon.ico", + "src/assets" + ], + "styles": [ + "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", + "src/styles.scss" + ], + "scripts": [] + } } - ], - "outputHashing": "all" - }, - "development": { - "buildOptimizer": false, - "optimization": false, - "vendorChunk": true, - "extractLicenses": false, - "sourceMap": true, - "namedChunks": true } - }, - "defaultConfiguration": "production" - }, - "serve": { - "builder": "@angular-devkit/build-angular:dev-server", - "configurations": { - "production": { - "browserTarget": "Website:build:production" - }, - "development": { - "browserTarget": "Website:build:development" - } - }, - "defaultConfiguration": "development" - }, - "extract-i18n": { - "builder": "@angular-devkit/build-angular:extract-i18n", - "options": { - "browserTarget": "Website:build" - } - }, - "test": { - "builder": "@angular-devkit/build-angular:karma", - "options": { - "main": "src/test.ts", - "polyfills": "src/polyfills.ts", - "tsConfig": "tsconfig.spec.json", - "karmaConfig": "karma.conf.js", - "inlineStyleLanguage": "scss", - "assets": [ - "src/favicon.ico", - "src/assets" - ], - "styles": [ - "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", - "src/styles.scss" - ], - "scripts": [] - } } - } - } - }, - "defaultProject": "Website" -} + }, + "defaultProject": "Website" +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 3713633..5807647 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "@angular/router": "~12.2.0", "hammerjs": "^2.0.8", "rxjs": "~6.6.0", + "sha.js": "^2.4.11", "tslib": "^2.3.0", "zone.js": "~0.11.4" }, @@ -29,6 +30,7 @@ "@angular/compiler-cli": "~12.2.0", "@types/jasmine": "~3.8.0", "@types/node": "^12.11.1", + "@types/sha.js": "^2.4.0", "jasmine-core": "~3.8.0", "karma": "~6.3.0", "karma-chrome-launcher": "~3.1.0", @@ -2614,6 +2616,15 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "node_modules/@types/sha.js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/sha.js/-/sha.js-2.4.0.tgz", + "integrity": "sha512-amxKgPy6WJTKuw8mpUwjX2BSxuBtBmZfRwIUDIuPJKNwGN8CWDli8JTg5ONTWOtcTkHIstvT7oAhhYXqEjStHQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/source-list-map": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", @@ -7279,8 +7290,7 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/ini": { "version": "2.0.0", @@ -12765,8 +12775,7 @@ "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "node_modules/safe-regex": { "version": "1.1.0", @@ -13106,6 +13115,18 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "dev": true }, + "node_modules/sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "bin": { + "sha.js": "bin.js" + } + }, "node_modules/shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", @@ -17552,6 +17573,15 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "@types/sha.js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/sha.js/-/sha.js-2.4.0.tgz", + "integrity": "sha512-amxKgPy6WJTKuw8mpUwjX2BSxuBtBmZfRwIUDIuPJKNwGN8CWDli8JTg5ONTWOtcTkHIstvT7oAhhYXqEjStHQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/source-list-map": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", @@ -21129,8 +21159,7 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "ini": { "version": "2.0.0", @@ -25219,8 +25248,7 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "safe-regex": { "version": "1.1.0", @@ -25494,6 +25522,15 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "dev": true }, + "sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, "shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", diff --git a/package.json b/package.json index 092ece5..2c9a675 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "@angular/router": "~12.2.0", "hammerjs": "^2.0.8", "rxjs": "~6.6.0", + "sha.js": "^2.4.11", "tslib": "^2.3.0", "zone.js": "~0.11.4" }, @@ -31,6 +32,7 @@ "@angular/compiler-cli": "~12.2.0", "@types/jasmine": "~3.8.0", "@types/node": "^12.11.1", + "@types/sha.js": "^2.4.0", "jasmine-core": "~3.8.0", "karma": "~6.3.0", "karma-chrome-launcher": "~3.1.0", diff --git a/src/app/account.service.spec.ts b/src/app/account.service.spec.ts new file mode 100644 index 0000000..2ffad6f --- /dev/null +++ b/src/app/account.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { AccountService } from './account.service'; + +describe('AccountService', () => { + let service: AccountService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(AccountService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/account.service.ts b/src/app/account.service.ts new file mode 100644 index 0000000..2b64b6a --- /dev/null +++ b/src/app/account.service.ts @@ -0,0 +1,96 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { environment } from 'src/environments/environment'; +import { sha256 } from 'sha.js'; +import { Router } from '@angular/router'; + +class Answer { + success: Boolean = false +} + +class PresaltAnswer extends Answer { + presalt: string = '' +} + +class AuthenticateAnswer extends Answer { + token: string = '' +} + +class CheckTokenAnswer extends Answer { + valid: Boolean = false +} + +class TokenData { + iss: string = '' + dat: string = '' + typ: string = '' + usr: string = '' +} + +@Injectable({ + providedIn: 'root' +}) +export class AccountService { + public username = '?'; + private userId: bigint = BigInt(0); + private userToken: bigint = BigInt(0); + + constructor(private http: HttpClient, private router: Router) { + this.updateUserInfo() + } + + public login(username: string, password: string, onFail: Function, onConnectionError: Function) { + this.http.post(`${environment.apiURL}/users/presalt`, {username}) + .subscribe((answer: Object) => { + var presaltAnswer = Object.assign(new PresaltAnswer(), answer) + if (!presaltAnswer.success) { + onFail() + return + } + // this presalting and hashing is done to protect the user's password in case of a man-in-the-middle-attack, + // where an attacker might be able to intercept a password if not hashed before. The password still has to be + // hashed on the server-side to be sure but this method prevents dumb users from having their passweord + // they use everywhere easily leaked, because you still need to crack this one layer first + // also, typical automated hacking software will hopefully be confused by my weird code + var passwordHash = new sha256().update(password + presaltAnswer.presalt).digest('hex') + this.http.post(`${environment.apiURL}/users/authenticate`, {username, passwordHash}) + .subscribe((answer) => { + var authenticateAnswer = Object.assign(new AuthenticateAnswer(), answer) + var loginSuccess = authenticateAnswer.success == true + if (loginSuccess) { + localStorage.setItem('userToken', authenticateAnswer.token) + this.updateUserInfo() + this.username = username + this.router.navigate(['admin']) + } else { + onFail() + } + }, (error) => {onConnectionError()}) + }, (error) => {onConnectionError()}) + } + + updateUserInfo() { + var token = localStorage.getItem('userToken') + if (token == null) { + this.username = '' + return + } + this.http.post(`${environment.apiURL}/users/checkToken`, {token}).subscribe((answer: Object) => { + var checkTokenAnswer = Object.assign(new CheckTokenAnswer(), answer) + if (checkTokenAnswer.valid == true) { + var data = JSON.parse(atob(token?.split('.')[1] as string)) + var tokenData = Object.assign(new TokenData(), data) + this.username = tokenData.usr + } else { + this.username = '' + } + }, () => { + this.username = '' + }) + } + + logout() { + this.username = '' + localStorage.removeItem('userToken') + } +} diff --git a/src/app/admin/admin.component.html b/src/app/admin/admin.component.html new file mode 100644 index 0000000..f949c13 --- /dev/null +++ b/src/app/admin/admin.component.html @@ -0,0 +1,6 @@ +

Hello {{account.username}}!

+

Welcome to the admin panel

+ +

There is nothing to do here yet...

+ + \ No newline at end of file diff --git a/src/app/admin/admin.component.scss b/src/app/admin/admin.component.scss new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/app/admin/admin.component.scss diff --git a/src/app/admin/admin.component.spec.ts b/src/app/admin/admin.component.spec.ts new file mode 100644 index 0000000..eb28e42 --- /dev/null +++ b/src/app/admin/admin.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AdminComponent } from './admin.component'; + +describe('AdminComponent', () => { + let component: AdminComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ AdminComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(AdminComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/admin/admin.component.ts b/src/app/admin/admin.component.ts new file mode 100644 index 0000000..85273be --- /dev/null +++ b/src/app/admin/admin.component.ts @@ -0,0 +1,20 @@ +import { Component, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; +import { AccountService } from '../account.service'; + +@Component({ + selector: 'app-admin', + templateUrl: './admin.component.html', + styleUrls: ['./admin.component.scss'] +}) +export class AdminComponent implements OnInit { + + constructor(public account: AccountService, private router: Router) { + if (account.username.length == 0) { + router.navigate(['login']) + } + } + + ngOnInit(): void { + } +} diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index b250123..9a07a6e 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -1,9 +1,13 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; +import { AdminComponent } from './admin/admin.component'; import { HomeComponent } from './home/home.component'; +import { LoginComponent } from './login/login.component'; const routes: Routes = [ - { path: "", component: HomeComponent } + { path: "", component: HomeComponent }, + { path: "login", component: LoginComponent }, + { path: "admin", component: AdminComponent }, ]; @NgModule({ diff --git a/src/app/app.module.ts b/src/app/app.module.ts index a6f5aac..1fb908c 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -4,6 +4,15 @@ import { MatSidenavModule } from '@angular/material/sidenav'; import { MatListModule } from '@angular/material/list'; import { MatIconModule } from '@angular/material/icon'; +import { MatCardModule } from '@angular/material/card'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatButtonModule } from '@angular/material/button'; +import { MatInputModule } from '@angular/material/input'; +import { MatMenuModule } from '@angular/material/menu'; +import { MatTableModule } from '@angular/material/table'; +import { MatSlideToggleModule } from '@angular/material/slide-toggle'; +import { MatSelectModule } from '@angular/material/select'; +import { MatOptionModule } from '@angular/material/core'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; @@ -13,13 +22,21 @@ import { FooterComponent } from './footer/footer.component'; import { GridComponent } from './grid/grid.component'; +import { AccountService } from './account.service'; +import { LoginComponent } from './login/login.component' +import { HttpClientModule } from '@angular/common/http'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { AdminComponent } from './admin/admin.component'; + @NgModule({ declarations: [ AppComponent, ToolbarComponent, HomeComponent, FooterComponent, - GridComponent + GridComponent, + LoginComponent, + AdminComponent ], imports: [ BrowserModule, @@ -29,8 +46,20 @@ MatSidenavModule, MatListModule, MatIconModule, + HttpClientModule, + MatCardModule, + MatFormFieldModule, + MatInputModule, + FormsModule, + ReactiveFormsModule, + MatButtonModule, + MatMenuModule, + MatTableModule, + MatSlideToggleModule, + MatSelectModule, + MatOptionModule, ], - providers: [], + providers: [AccountService], bootstrap: [AppComponent] }) export class AppModule { } diff --git a/src/app/login/login.component.html b/src/app/login/login.component.html new file mode 100644 index 0000000..54bebb0 --- /dev/null +++ b/src/app/login/login.component.html @@ -0,0 +1,21 @@ + +

Login

+
+

Enter your credentials here to log into the website.

+
+ + Username + this field must not be empty + + + + Password + this field must not be empty + + + +

Something went wrong while authenticating you. Make sure both the username and password fields are filled and are correct.

+

Could not connect to the API at {{environment.apiURL}}

+
+
+
\ No newline at end of file diff --git a/src/app/login/login.component.scss b/src/app/login/login.component.scss new file mode 100644 index 0000000..75bbd4b --- /dev/null +++ b/src/app/login/login.component.scss @@ -0,0 +1,13 @@ +mat-card { + max-width: 40ch; +} + +:host { + display: flex; + justify-content: space-around; + padding: 20px; +} + +mat-form-field { + width: 100%; +} \ No newline at end of file diff --git a/src/app/login/login.component.spec.ts b/src/app/login/login.component.spec.ts new file mode 100644 index 0000000..d2c0e6c --- /dev/null +++ b/src/app/login/login.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { LoginComponent } from './login.component'; + +describe('LoginComponent', () => { + let component: LoginComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ LoginComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(LoginComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/login/login.component.ts b/src/app/login/login.component.ts new file mode 100644 index 0000000..450a8f0 --- /dev/null +++ b/src/app/login/login.component.ts @@ -0,0 +1,43 @@ +import { Component, OnInit } from '@angular/core'; +import { FormBuilder, FormGroup, NgForm, Validators } from '@angular/forms'; +import { environment } from 'src/environments/environment'; +import { AccountService } from '../account.service'; + +class LoginInformation { + constructor(public username: string, public password: string) { + + } +} + +@Component({ + selector: 'app-login', + templateUrl: './login.component.html', + styleUrls: ['./login.component.scss'] +}) +export class LoginComponent implements OnInit { + model = new LoginInformation('', '') + public form: FormGroup + public valid = true + public connectionError = false + public environment = environment + constructor(private formBuilder: FormBuilder, private account: AccountService) { + this.form = this.formBuilder.group({ + username: ['', Validators.required], + password: ['', Validators.required] + }) + } + + ngOnInit(): void { + } + + onSubmit() { + if (this.form.invalid) { + this.valid = false + return + } + this.valid = true + this.connectionError = false + this.account.login(this.form.controls.username.value, this.form.controls.password.value, + () => {this.valid = false}, () => {this.connectionError = true}); + } +} diff --git a/src/app/toolbar/toolbar.component.html b/src/app/toolbar/toolbar.component.html index c5f7157..b39e10b 100644 --- a/src/app/toolbar/toolbar.component.html +++ b/src/app/toolbar/toolbar.component.html @@ -1,6 +1,16 @@ - Lukas Eisenhauer - + Lukas Eisenhauer + Log In - \ No newline at end of file + + + + + + + + + \ No newline at end of file diff --git a/angular.json b/angular.json index 90d80e5..f23dd59 100644 --- a/angular.json +++ b/angular.json @@ -1,113 +1,113 @@ { - "$schema": "./node_modules/@angular/cli/lib/config/schema.json", - "version": 1, - "newProjectRoot": "projects", - "projects": { - "Website": { - "projectType": "application", - "schematics": { - "@schematics/angular:component": { - "style": "scss" - }, - "@schematics/angular:application": { - "strict": true - } - }, - "root": "", - "sourceRoot": "src", - "prefix": "app", - "architect": { - "build": { - "builder": "@angular-devkit/build-angular:browser", - "options": { - "outputPath": "dist/Website", - "index": "src/index.html", - "main": "src/main.ts", - "polyfills": "src/polyfills.ts", - "tsConfig": "tsconfig.app.json", - "inlineStyleLanguage": "scss", - "assets": [ - "src/favicon.ico", - "src/assets" - ], - "styles": [ - "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", - "src/styles.scss" - ], - "scripts": [] - }, - "configurations": { - "production": { - "budgets": [ - { - "type": "initial", - "maximumWarning": "500kb", - "maximumError": "1mb" + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "newProjectRoot": "projects", + "projects": { + "Website": { + "projectType": "application", + "schematics": { + "@schematics/angular:component": { + "style": "scss" }, - { - "type": "anyComponentStyle", - "maximumWarning": "2kb", - "maximumError": "4kb" + "@schematics/angular:application": { + "strict": true } - ], - "fileReplacements": [ - { - "replace": "src/environments/environment.ts", - "with": "src/environments/environment.prod.ts" + }, + "root": "", + "sourceRoot": "src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:browser", + "options": { + "outputPath": "dist/Website", + "index": "src/index.html", + "main": "src/main.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "tsconfig.app.json", + "inlineStyleLanguage": "scss", + "assets": [ + "src/favicon.ico", + "src/assets" + ], + "styles": [ + "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", + "src/styles.scss" + ], + "scripts": [] + }, + "configurations": { + "production": { + "budgets": [{ + "type": "initial", + "maximumWarning": "500kb", + "maximumError": "1mb" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "2kb", + "maximumError": "4kb" + } + ], + "fileReplacements": [{ + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.prod.ts" + }], + "outputHashing": "all" + }, + "development": { + "buildOptimizer": false, + "optimization": false, + "vendorChunk": true, + "extractLicenses": false, + "sourceMap": true, + "namedChunks": true + } + }, + "defaultConfiguration": "production" + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "configurations": { + "production": { + "browserTarget": "Website:build:production" + }, + "development": { + "browserTarget": "Website:build:development" + } + }, + "options": { + "proxyConfig": "src/proxy.conf.json" + }, + "defaultConfiguration": "development" + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n", + "options": { + "browserTarget": "Website:build" + } + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "main": "src/test.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "tsconfig.spec.json", + "karmaConfig": "karma.conf.js", + "inlineStyleLanguage": "scss", + "assets": [ + "src/favicon.ico", + "src/assets" + ], + "styles": [ + "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", + "src/styles.scss" + ], + "scripts": [] + } } - ], - "outputHashing": "all" - }, - "development": { - "buildOptimizer": false, - "optimization": false, - "vendorChunk": true, - "extractLicenses": false, - "sourceMap": true, - "namedChunks": true } - }, - "defaultConfiguration": "production" - }, - "serve": { - "builder": "@angular-devkit/build-angular:dev-server", - "configurations": { - "production": { - "browserTarget": "Website:build:production" - }, - "development": { - "browserTarget": "Website:build:development" - } - }, - "defaultConfiguration": "development" - }, - "extract-i18n": { - "builder": "@angular-devkit/build-angular:extract-i18n", - "options": { - "browserTarget": "Website:build" - } - }, - "test": { - "builder": "@angular-devkit/build-angular:karma", - "options": { - "main": "src/test.ts", - "polyfills": "src/polyfills.ts", - "tsConfig": "tsconfig.spec.json", - "karmaConfig": "karma.conf.js", - "inlineStyleLanguage": "scss", - "assets": [ - "src/favicon.ico", - "src/assets" - ], - "styles": [ - "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", - "src/styles.scss" - ], - "scripts": [] - } } - } - } - }, - "defaultProject": "Website" -} + }, + "defaultProject": "Website" +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 3713633..5807647 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "@angular/router": "~12.2.0", "hammerjs": "^2.0.8", "rxjs": "~6.6.0", + "sha.js": "^2.4.11", "tslib": "^2.3.0", "zone.js": "~0.11.4" }, @@ -29,6 +30,7 @@ "@angular/compiler-cli": "~12.2.0", "@types/jasmine": "~3.8.0", "@types/node": "^12.11.1", + "@types/sha.js": "^2.4.0", "jasmine-core": "~3.8.0", "karma": "~6.3.0", "karma-chrome-launcher": "~3.1.0", @@ -2614,6 +2616,15 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "node_modules/@types/sha.js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/sha.js/-/sha.js-2.4.0.tgz", + "integrity": "sha512-amxKgPy6WJTKuw8mpUwjX2BSxuBtBmZfRwIUDIuPJKNwGN8CWDli8JTg5ONTWOtcTkHIstvT7oAhhYXqEjStHQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/source-list-map": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", @@ -7279,8 +7290,7 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/ini": { "version": "2.0.0", @@ -12765,8 +12775,7 @@ "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "node_modules/safe-regex": { "version": "1.1.0", @@ -13106,6 +13115,18 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "dev": true }, + "node_modules/sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "bin": { + "sha.js": "bin.js" + } + }, "node_modules/shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", @@ -17552,6 +17573,15 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "@types/sha.js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/sha.js/-/sha.js-2.4.0.tgz", + "integrity": "sha512-amxKgPy6WJTKuw8mpUwjX2BSxuBtBmZfRwIUDIuPJKNwGN8CWDli8JTg5ONTWOtcTkHIstvT7oAhhYXqEjStHQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/source-list-map": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", @@ -21129,8 +21159,7 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "ini": { "version": "2.0.0", @@ -25219,8 +25248,7 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "safe-regex": { "version": "1.1.0", @@ -25494,6 +25522,15 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "dev": true }, + "sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, "shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", diff --git a/package.json b/package.json index 092ece5..2c9a675 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "@angular/router": "~12.2.0", "hammerjs": "^2.0.8", "rxjs": "~6.6.0", + "sha.js": "^2.4.11", "tslib": "^2.3.0", "zone.js": "~0.11.4" }, @@ -31,6 +32,7 @@ "@angular/compiler-cli": "~12.2.0", "@types/jasmine": "~3.8.0", "@types/node": "^12.11.1", + "@types/sha.js": "^2.4.0", "jasmine-core": "~3.8.0", "karma": "~6.3.0", "karma-chrome-launcher": "~3.1.0", diff --git a/src/app/account.service.spec.ts b/src/app/account.service.spec.ts new file mode 100644 index 0000000..2ffad6f --- /dev/null +++ b/src/app/account.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { AccountService } from './account.service'; + +describe('AccountService', () => { + let service: AccountService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(AccountService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/account.service.ts b/src/app/account.service.ts new file mode 100644 index 0000000..2b64b6a --- /dev/null +++ b/src/app/account.service.ts @@ -0,0 +1,96 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { environment } from 'src/environments/environment'; +import { sha256 } from 'sha.js'; +import { Router } from '@angular/router'; + +class Answer { + success: Boolean = false +} + +class PresaltAnswer extends Answer { + presalt: string = '' +} + +class AuthenticateAnswer extends Answer { + token: string = '' +} + +class CheckTokenAnswer extends Answer { + valid: Boolean = false +} + +class TokenData { + iss: string = '' + dat: string = '' + typ: string = '' + usr: string = '' +} + +@Injectable({ + providedIn: 'root' +}) +export class AccountService { + public username = '?'; + private userId: bigint = BigInt(0); + private userToken: bigint = BigInt(0); + + constructor(private http: HttpClient, private router: Router) { + this.updateUserInfo() + } + + public login(username: string, password: string, onFail: Function, onConnectionError: Function) { + this.http.post(`${environment.apiURL}/users/presalt`, {username}) + .subscribe((answer: Object) => { + var presaltAnswer = Object.assign(new PresaltAnswer(), answer) + if (!presaltAnswer.success) { + onFail() + return + } + // this presalting and hashing is done to protect the user's password in case of a man-in-the-middle-attack, + // where an attacker might be able to intercept a password if not hashed before. The password still has to be + // hashed on the server-side to be sure but this method prevents dumb users from having their passweord + // they use everywhere easily leaked, because you still need to crack this one layer first + // also, typical automated hacking software will hopefully be confused by my weird code + var passwordHash = new sha256().update(password + presaltAnswer.presalt).digest('hex') + this.http.post(`${environment.apiURL}/users/authenticate`, {username, passwordHash}) + .subscribe((answer) => { + var authenticateAnswer = Object.assign(new AuthenticateAnswer(), answer) + var loginSuccess = authenticateAnswer.success == true + if (loginSuccess) { + localStorage.setItem('userToken', authenticateAnswer.token) + this.updateUserInfo() + this.username = username + this.router.navigate(['admin']) + } else { + onFail() + } + }, (error) => {onConnectionError()}) + }, (error) => {onConnectionError()}) + } + + updateUserInfo() { + var token = localStorage.getItem('userToken') + if (token == null) { + this.username = '' + return + } + this.http.post(`${environment.apiURL}/users/checkToken`, {token}).subscribe((answer: Object) => { + var checkTokenAnswer = Object.assign(new CheckTokenAnswer(), answer) + if (checkTokenAnswer.valid == true) { + var data = JSON.parse(atob(token?.split('.')[1] as string)) + var tokenData = Object.assign(new TokenData(), data) + this.username = tokenData.usr + } else { + this.username = '' + } + }, () => { + this.username = '' + }) + } + + logout() { + this.username = '' + localStorage.removeItem('userToken') + } +} diff --git a/src/app/admin/admin.component.html b/src/app/admin/admin.component.html new file mode 100644 index 0000000..f949c13 --- /dev/null +++ b/src/app/admin/admin.component.html @@ -0,0 +1,6 @@ +

Hello {{account.username}}!

+

Welcome to the admin panel

+ +

There is nothing to do here yet...

+ + \ No newline at end of file diff --git a/src/app/admin/admin.component.scss b/src/app/admin/admin.component.scss new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/app/admin/admin.component.scss diff --git a/src/app/admin/admin.component.spec.ts b/src/app/admin/admin.component.spec.ts new file mode 100644 index 0000000..eb28e42 --- /dev/null +++ b/src/app/admin/admin.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AdminComponent } from './admin.component'; + +describe('AdminComponent', () => { + let component: AdminComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ AdminComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(AdminComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/admin/admin.component.ts b/src/app/admin/admin.component.ts new file mode 100644 index 0000000..85273be --- /dev/null +++ b/src/app/admin/admin.component.ts @@ -0,0 +1,20 @@ +import { Component, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; +import { AccountService } from '../account.service'; + +@Component({ + selector: 'app-admin', + templateUrl: './admin.component.html', + styleUrls: ['./admin.component.scss'] +}) +export class AdminComponent implements OnInit { + + constructor(public account: AccountService, private router: Router) { + if (account.username.length == 0) { + router.navigate(['login']) + } + } + + ngOnInit(): void { + } +} diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index b250123..9a07a6e 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -1,9 +1,13 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; +import { AdminComponent } from './admin/admin.component'; import { HomeComponent } from './home/home.component'; +import { LoginComponent } from './login/login.component'; const routes: Routes = [ - { path: "", component: HomeComponent } + { path: "", component: HomeComponent }, + { path: "login", component: LoginComponent }, + { path: "admin", component: AdminComponent }, ]; @NgModule({ diff --git a/src/app/app.module.ts b/src/app/app.module.ts index a6f5aac..1fb908c 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -4,6 +4,15 @@ import { MatSidenavModule } from '@angular/material/sidenav'; import { MatListModule } from '@angular/material/list'; import { MatIconModule } from '@angular/material/icon'; +import { MatCardModule } from '@angular/material/card'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatButtonModule } from '@angular/material/button'; +import { MatInputModule } from '@angular/material/input'; +import { MatMenuModule } from '@angular/material/menu'; +import { MatTableModule } from '@angular/material/table'; +import { MatSlideToggleModule } from '@angular/material/slide-toggle'; +import { MatSelectModule } from '@angular/material/select'; +import { MatOptionModule } from '@angular/material/core'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; @@ -13,13 +22,21 @@ import { FooterComponent } from './footer/footer.component'; import { GridComponent } from './grid/grid.component'; +import { AccountService } from './account.service'; +import { LoginComponent } from './login/login.component' +import { HttpClientModule } from '@angular/common/http'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { AdminComponent } from './admin/admin.component'; + @NgModule({ declarations: [ AppComponent, ToolbarComponent, HomeComponent, FooterComponent, - GridComponent + GridComponent, + LoginComponent, + AdminComponent ], imports: [ BrowserModule, @@ -29,8 +46,20 @@ MatSidenavModule, MatListModule, MatIconModule, + HttpClientModule, + MatCardModule, + MatFormFieldModule, + MatInputModule, + FormsModule, + ReactiveFormsModule, + MatButtonModule, + MatMenuModule, + MatTableModule, + MatSlideToggleModule, + MatSelectModule, + MatOptionModule, ], - providers: [], + providers: [AccountService], bootstrap: [AppComponent] }) export class AppModule { } diff --git a/src/app/login/login.component.html b/src/app/login/login.component.html new file mode 100644 index 0000000..54bebb0 --- /dev/null +++ b/src/app/login/login.component.html @@ -0,0 +1,21 @@ + +

Login

+
+

Enter your credentials here to log into the website.

+
+ + Username + this field must not be empty + + + + Password + this field must not be empty + + + +

Something went wrong while authenticating you. Make sure both the username and password fields are filled and are correct.

+

Could not connect to the API at {{environment.apiURL}}

+
+
+
\ No newline at end of file diff --git a/src/app/login/login.component.scss b/src/app/login/login.component.scss new file mode 100644 index 0000000..75bbd4b --- /dev/null +++ b/src/app/login/login.component.scss @@ -0,0 +1,13 @@ +mat-card { + max-width: 40ch; +} + +:host { + display: flex; + justify-content: space-around; + padding: 20px; +} + +mat-form-field { + width: 100%; +} \ No newline at end of file diff --git a/src/app/login/login.component.spec.ts b/src/app/login/login.component.spec.ts new file mode 100644 index 0000000..d2c0e6c --- /dev/null +++ b/src/app/login/login.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { LoginComponent } from './login.component'; + +describe('LoginComponent', () => { + let component: LoginComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ LoginComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(LoginComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/login/login.component.ts b/src/app/login/login.component.ts new file mode 100644 index 0000000..450a8f0 --- /dev/null +++ b/src/app/login/login.component.ts @@ -0,0 +1,43 @@ +import { Component, OnInit } from '@angular/core'; +import { FormBuilder, FormGroup, NgForm, Validators } from '@angular/forms'; +import { environment } from 'src/environments/environment'; +import { AccountService } from '../account.service'; + +class LoginInformation { + constructor(public username: string, public password: string) { + + } +} + +@Component({ + selector: 'app-login', + templateUrl: './login.component.html', + styleUrls: ['./login.component.scss'] +}) +export class LoginComponent implements OnInit { + model = new LoginInformation('', '') + public form: FormGroup + public valid = true + public connectionError = false + public environment = environment + constructor(private formBuilder: FormBuilder, private account: AccountService) { + this.form = this.formBuilder.group({ + username: ['', Validators.required], + password: ['', Validators.required] + }) + } + + ngOnInit(): void { + } + + onSubmit() { + if (this.form.invalid) { + this.valid = false + return + } + this.valid = true + this.connectionError = false + this.account.login(this.form.controls.username.value, this.form.controls.password.value, + () => {this.valid = false}, () => {this.connectionError = true}); + } +} diff --git a/src/app/toolbar/toolbar.component.html b/src/app/toolbar/toolbar.component.html index c5f7157..b39e10b 100644 --- a/src/app/toolbar/toolbar.component.html +++ b/src/app/toolbar/toolbar.component.html @@ -1,6 +1,16 @@ - Lukas Eisenhauer - + Lukas Eisenhauer + Log In - \ No newline at end of file + + + + + + + + + \ No newline at end of file diff --git a/src/app/toolbar/toolbar.component.scss b/src/app/toolbar/toolbar.component.scss index e69de29..98ca747 100644 --- a/src/app/toolbar/toolbar.component.scss +++ b/src/app/toolbar/toolbar.component.scss @@ -0,0 +1,4 @@ +mat-toolbar-row { + display: flex; + justify-content: space-between; +} \ No newline at end of file diff --git a/angular.json b/angular.json index 90d80e5..f23dd59 100644 --- a/angular.json +++ b/angular.json @@ -1,113 +1,113 @@ { - "$schema": "./node_modules/@angular/cli/lib/config/schema.json", - "version": 1, - "newProjectRoot": "projects", - "projects": { - "Website": { - "projectType": "application", - "schematics": { - "@schematics/angular:component": { - "style": "scss" - }, - "@schematics/angular:application": { - "strict": true - } - }, - "root": "", - "sourceRoot": "src", - "prefix": "app", - "architect": { - "build": { - "builder": "@angular-devkit/build-angular:browser", - "options": { - "outputPath": "dist/Website", - "index": "src/index.html", - "main": "src/main.ts", - "polyfills": "src/polyfills.ts", - "tsConfig": "tsconfig.app.json", - "inlineStyleLanguage": "scss", - "assets": [ - "src/favicon.ico", - "src/assets" - ], - "styles": [ - "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", - "src/styles.scss" - ], - "scripts": [] - }, - "configurations": { - "production": { - "budgets": [ - { - "type": "initial", - "maximumWarning": "500kb", - "maximumError": "1mb" + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "newProjectRoot": "projects", + "projects": { + "Website": { + "projectType": "application", + "schematics": { + "@schematics/angular:component": { + "style": "scss" }, - { - "type": "anyComponentStyle", - "maximumWarning": "2kb", - "maximumError": "4kb" + "@schematics/angular:application": { + "strict": true } - ], - "fileReplacements": [ - { - "replace": "src/environments/environment.ts", - "with": "src/environments/environment.prod.ts" + }, + "root": "", + "sourceRoot": "src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:browser", + "options": { + "outputPath": "dist/Website", + "index": "src/index.html", + "main": "src/main.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "tsconfig.app.json", + "inlineStyleLanguage": "scss", + "assets": [ + "src/favicon.ico", + "src/assets" + ], + "styles": [ + "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", + "src/styles.scss" + ], + "scripts": [] + }, + "configurations": { + "production": { + "budgets": [{ + "type": "initial", + "maximumWarning": "500kb", + "maximumError": "1mb" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "2kb", + "maximumError": "4kb" + } + ], + "fileReplacements": [{ + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.prod.ts" + }], + "outputHashing": "all" + }, + "development": { + "buildOptimizer": false, + "optimization": false, + "vendorChunk": true, + "extractLicenses": false, + "sourceMap": true, + "namedChunks": true + } + }, + "defaultConfiguration": "production" + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "configurations": { + "production": { + "browserTarget": "Website:build:production" + }, + "development": { + "browserTarget": "Website:build:development" + } + }, + "options": { + "proxyConfig": "src/proxy.conf.json" + }, + "defaultConfiguration": "development" + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n", + "options": { + "browserTarget": "Website:build" + } + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "main": "src/test.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "tsconfig.spec.json", + "karmaConfig": "karma.conf.js", + "inlineStyleLanguage": "scss", + "assets": [ + "src/favicon.ico", + "src/assets" + ], + "styles": [ + "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", + "src/styles.scss" + ], + "scripts": [] + } } - ], - "outputHashing": "all" - }, - "development": { - "buildOptimizer": false, - "optimization": false, - "vendorChunk": true, - "extractLicenses": false, - "sourceMap": true, - "namedChunks": true } - }, - "defaultConfiguration": "production" - }, - "serve": { - "builder": "@angular-devkit/build-angular:dev-server", - "configurations": { - "production": { - "browserTarget": "Website:build:production" - }, - "development": { - "browserTarget": "Website:build:development" - } - }, - "defaultConfiguration": "development" - }, - "extract-i18n": { - "builder": "@angular-devkit/build-angular:extract-i18n", - "options": { - "browserTarget": "Website:build" - } - }, - "test": { - "builder": "@angular-devkit/build-angular:karma", - "options": { - "main": "src/test.ts", - "polyfills": "src/polyfills.ts", - "tsConfig": "tsconfig.spec.json", - "karmaConfig": "karma.conf.js", - "inlineStyleLanguage": "scss", - "assets": [ - "src/favicon.ico", - "src/assets" - ], - "styles": [ - "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", - "src/styles.scss" - ], - "scripts": [] - } } - } - } - }, - "defaultProject": "Website" -} + }, + "defaultProject": "Website" +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 3713633..5807647 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "@angular/router": "~12.2.0", "hammerjs": "^2.0.8", "rxjs": "~6.6.0", + "sha.js": "^2.4.11", "tslib": "^2.3.0", "zone.js": "~0.11.4" }, @@ -29,6 +30,7 @@ "@angular/compiler-cli": "~12.2.0", "@types/jasmine": "~3.8.0", "@types/node": "^12.11.1", + "@types/sha.js": "^2.4.0", "jasmine-core": "~3.8.0", "karma": "~6.3.0", "karma-chrome-launcher": "~3.1.0", @@ -2614,6 +2616,15 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "node_modules/@types/sha.js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/sha.js/-/sha.js-2.4.0.tgz", + "integrity": "sha512-amxKgPy6WJTKuw8mpUwjX2BSxuBtBmZfRwIUDIuPJKNwGN8CWDli8JTg5ONTWOtcTkHIstvT7oAhhYXqEjStHQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/source-list-map": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", @@ -7279,8 +7290,7 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/ini": { "version": "2.0.0", @@ -12765,8 +12775,7 @@ "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "node_modules/safe-regex": { "version": "1.1.0", @@ -13106,6 +13115,18 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "dev": true }, + "node_modules/sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "bin": { + "sha.js": "bin.js" + } + }, "node_modules/shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", @@ -17552,6 +17573,15 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "@types/sha.js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/sha.js/-/sha.js-2.4.0.tgz", + "integrity": "sha512-amxKgPy6WJTKuw8mpUwjX2BSxuBtBmZfRwIUDIuPJKNwGN8CWDli8JTg5ONTWOtcTkHIstvT7oAhhYXqEjStHQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/source-list-map": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", @@ -21129,8 +21159,7 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "ini": { "version": "2.0.0", @@ -25219,8 +25248,7 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "safe-regex": { "version": "1.1.0", @@ -25494,6 +25522,15 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "dev": true }, + "sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, "shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", diff --git a/package.json b/package.json index 092ece5..2c9a675 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "@angular/router": "~12.2.0", "hammerjs": "^2.0.8", "rxjs": "~6.6.0", + "sha.js": "^2.4.11", "tslib": "^2.3.0", "zone.js": "~0.11.4" }, @@ -31,6 +32,7 @@ "@angular/compiler-cli": "~12.2.0", "@types/jasmine": "~3.8.0", "@types/node": "^12.11.1", + "@types/sha.js": "^2.4.0", "jasmine-core": "~3.8.0", "karma": "~6.3.0", "karma-chrome-launcher": "~3.1.0", diff --git a/src/app/account.service.spec.ts b/src/app/account.service.spec.ts new file mode 100644 index 0000000..2ffad6f --- /dev/null +++ b/src/app/account.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { AccountService } from './account.service'; + +describe('AccountService', () => { + let service: AccountService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(AccountService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/account.service.ts b/src/app/account.service.ts new file mode 100644 index 0000000..2b64b6a --- /dev/null +++ b/src/app/account.service.ts @@ -0,0 +1,96 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { environment } from 'src/environments/environment'; +import { sha256 } from 'sha.js'; +import { Router } from '@angular/router'; + +class Answer { + success: Boolean = false +} + +class PresaltAnswer extends Answer { + presalt: string = '' +} + +class AuthenticateAnswer extends Answer { + token: string = '' +} + +class CheckTokenAnswer extends Answer { + valid: Boolean = false +} + +class TokenData { + iss: string = '' + dat: string = '' + typ: string = '' + usr: string = '' +} + +@Injectable({ + providedIn: 'root' +}) +export class AccountService { + public username = '?'; + private userId: bigint = BigInt(0); + private userToken: bigint = BigInt(0); + + constructor(private http: HttpClient, private router: Router) { + this.updateUserInfo() + } + + public login(username: string, password: string, onFail: Function, onConnectionError: Function) { + this.http.post(`${environment.apiURL}/users/presalt`, {username}) + .subscribe((answer: Object) => { + var presaltAnswer = Object.assign(new PresaltAnswer(), answer) + if (!presaltAnswer.success) { + onFail() + return + } + // this presalting and hashing is done to protect the user's password in case of a man-in-the-middle-attack, + // where an attacker might be able to intercept a password if not hashed before. The password still has to be + // hashed on the server-side to be sure but this method prevents dumb users from having their passweord + // they use everywhere easily leaked, because you still need to crack this one layer first + // also, typical automated hacking software will hopefully be confused by my weird code + var passwordHash = new sha256().update(password + presaltAnswer.presalt).digest('hex') + this.http.post(`${environment.apiURL}/users/authenticate`, {username, passwordHash}) + .subscribe((answer) => { + var authenticateAnswer = Object.assign(new AuthenticateAnswer(), answer) + var loginSuccess = authenticateAnswer.success == true + if (loginSuccess) { + localStorage.setItem('userToken', authenticateAnswer.token) + this.updateUserInfo() + this.username = username + this.router.navigate(['admin']) + } else { + onFail() + } + }, (error) => {onConnectionError()}) + }, (error) => {onConnectionError()}) + } + + updateUserInfo() { + var token = localStorage.getItem('userToken') + if (token == null) { + this.username = '' + return + } + this.http.post(`${environment.apiURL}/users/checkToken`, {token}).subscribe((answer: Object) => { + var checkTokenAnswer = Object.assign(new CheckTokenAnswer(), answer) + if (checkTokenAnswer.valid == true) { + var data = JSON.parse(atob(token?.split('.')[1] as string)) + var tokenData = Object.assign(new TokenData(), data) + this.username = tokenData.usr + } else { + this.username = '' + } + }, () => { + this.username = '' + }) + } + + logout() { + this.username = '' + localStorage.removeItem('userToken') + } +} diff --git a/src/app/admin/admin.component.html b/src/app/admin/admin.component.html new file mode 100644 index 0000000..f949c13 --- /dev/null +++ b/src/app/admin/admin.component.html @@ -0,0 +1,6 @@ +

Hello {{account.username}}!

+

Welcome to the admin panel

+ +

There is nothing to do here yet...

+ + \ No newline at end of file diff --git a/src/app/admin/admin.component.scss b/src/app/admin/admin.component.scss new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/app/admin/admin.component.scss diff --git a/src/app/admin/admin.component.spec.ts b/src/app/admin/admin.component.spec.ts new file mode 100644 index 0000000..eb28e42 --- /dev/null +++ b/src/app/admin/admin.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AdminComponent } from './admin.component'; + +describe('AdminComponent', () => { + let component: AdminComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ AdminComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(AdminComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/admin/admin.component.ts b/src/app/admin/admin.component.ts new file mode 100644 index 0000000..85273be --- /dev/null +++ b/src/app/admin/admin.component.ts @@ -0,0 +1,20 @@ +import { Component, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; +import { AccountService } from '../account.service'; + +@Component({ + selector: 'app-admin', + templateUrl: './admin.component.html', + styleUrls: ['./admin.component.scss'] +}) +export class AdminComponent implements OnInit { + + constructor(public account: AccountService, private router: Router) { + if (account.username.length == 0) { + router.navigate(['login']) + } + } + + ngOnInit(): void { + } +} diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index b250123..9a07a6e 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -1,9 +1,13 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; +import { AdminComponent } from './admin/admin.component'; import { HomeComponent } from './home/home.component'; +import { LoginComponent } from './login/login.component'; const routes: Routes = [ - { path: "", component: HomeComponent } + { path: "", component: HomeComponent }, + { path: "login", component: LoginComponent }, + { path: "admin", component: AdminComponent }, ]; @NgModule({ diff --git a/src/app/app.module.ts b/src/app/app.module.ts index a6f5aac..1fb908c 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -4,6 +4,15 @@ import { MatSidenavModule } from '@angular/material/sidenav'; import { MatListModule } from '@angular/material/list'; import { MatIconModule } from '@angular/material/icon'; +import { MatCardModule } from '@angular/material/card'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatButtonModule } from '@angular/material/button'; +import { MatInputModule } from '@angular/material/input'; +import { MatMenuModule } from '@angular/material/menu'; +import { MatTableModule } from '@angular/material/table'; +import { MatSlideToggleModule } from '@angular/material/slide-toggle'; +import { MatSelectModule } from '@angular/material/select'; +import { MatOptionModule } from '@angular/material/core'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; @@ -13,13 +22,21 @@ import { FooterComponent } from './footer/footer.component'; import { GridComponent } from './grid/grid.component'; +import { AccountService } from './account.service'; +import { LoginComponent } from './login/login.component' +import { HttpClientModule } from '@angular/common/http'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { AdminComponent } from './admin/admin.component'; + @NgModule({ declarations: [ AppComponent, ToolbarComponent, HomeComponent, FooterComponent, - GridComponent + GridComponent, + LoginComponent, + AdminComponent ], imports: [ BrowserModule, @@ -29,8 +46,20 @@ MatSidenavModule, MatListModule, MatIconModule, + HttpClientModule, + MatCardModule, + MatFormFieldModule, + MatInputModule, + FormsModule, + ReactiveFormsModule, + MatButtonModule, + MatMenuModule, + MatTableModule, + MatSlideToggleModule, + MatSelectModule, + MatOptionModule, ], - providers: [], + providers: [AccountService], bootstrap: [AppComponent] }) export class AppModule { } diff --git a/src/app/login/login.component.html b/src/app/login/login.component.html new file mode 100644 index 0000000..54bebb0 --- /dev/null +++ b/src/app/login/login.component.html @@ -0,0 +1,21 @@ + +

Login

+
+

Enter your credentials here to log into the website.

+
+ + Username + this field must not be empty + + + + Password + this field must not be empty + + + +

Something went wrong while authenticating you. Make sure both the username and password fields are filled and are correct.

+

Could not connect to the API at {{environment.apiURL}}

+
+
+
\ No newline at end of file diff --git a/src/app/login/login.component.scss b/src/app/login/login.component.scss new file mode 100644 index 0000000..75bbd4b --- /dev/null +++ b/src/app/login/login.component.scss @@ -0,0 +1,13 @@ +mat-card { + max-width: 40ch; +} + +:host { + display: flex; + justify-content: space-around; + padding: 20px; +} + +mat-form-field { + width: 100%; +} \ No newline at end of file diff --git a/src/app/login/login.component.spec.ts b/src/app/login/login.component.spec.ts new file mode 100644 index 0000000..d2c0e6c --- /dev/null +++ b/src/app/login/login.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { LoginComponent } from './login.component'; + +describe('LoginComponent', () => { + let component: LoginComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ LoginComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(LoginComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/login/login.component.ts b/src/app/login/login.component.ts new file mode 100644 index 0000000..450a8f0 --- /dev/null +++ b/src/app/login/login.component.ts @@ -0,0 +1,43 @@ +import { Component, OnInit } from '@angular/core'; +import { FormBuilder, FormGroup, NgForm, Validators } from '@angular/forms'; +import { environment } from 'src/environments/environment'; +import { AccountService } from '../account.service'; + +class LoginInformation { + constructor(public username: string, public password: string) { + + } +} + +@Component({ + selector: 'app-login', + templateUrl: './login.component.html', + styleUrls: ['./login.component.scss'] +}) +export class LoginComponent implements OnInit { + model = new LoginInformation('', '') + public form: FormGroup + public valid = true + public connectionError = false + public environment = environment + constructor(private formBuilder: FormBuilder, private account: AccountService) { + this.form = this.formBuilder.group({ + username: ['', Validators.required], + password: ['', Validators.required] + }) + } + + ngOnInit(): void { + } + + onSubmit() { + if (this.form.invalid) { + this.valid = false + return + } + this.valid = true + this.connectionError = false + this.account.login(this.form.controls.username.value, this.form.controls.password.value, + () => {this.valid = false}, () => {this.connectionError = true}); + } +} diff --git a/src/app/toolbar/toolbar.component.html b/src/app/toolbar/toolbar.component.html index c5f7157..b39e10b 100644 --- a/src/app/toolbar/toolbar.component.html +++ b/src/app/toolbar/toolbar.component.html @@ -1,6 +1,16 @@ - Lukas Eisenhauer - + Lukas Eisenhauer + Log In - \ No newline at end of file + + + + + + + + + \ No newline at end of file diff --git a/src/app/toolbar/toolbar.component.scss b/src/app/toolbar/toolbar.component.scss index e69de29..98ca747 100644 --- a/src/app/toolbar/toolbar.component.scss +++ b/src/app/toolbar/toolbar.component.scss @@ -0,0 +1,4 @@ +mat-toolbar-row { + display: flex; + justify-content: space-between; +} \ No newline at end of file diff --git a/src/app/toolbar/toolbar.component.ts b/src/app/toolbar/toolbar.component.ts index 16fb31d..400ceb5 100644 --- a/src/app/toolbar/toolbar.component.ts +++ b/src/app/toolbar/toolbar.component.ts @@ -1,4 +1,6 @@ import { Component, OnInit } from '@angular/core'; +import { AccountService } from '../account.service'; + @Component({ selector: 'app-toolbar', @@ -7,7 +9,7 @@ }) export class ToolbarComponent implements OnInit { - constructor() { } + constructor(public account: AccountService) { } ngOnInit(): void { } diff --git a/angular.json b/angular.json index 90d80e5..f23dd59 100644 --- a/angular.json +++ b/angular.json @@ -1,113 +1,113 @@ { - "$schema": "./node_modules/@angular/cli/lib/config/schema.json", - "version": 1, - "newProjectRoot": "projects", - "projects": { - "Website": { - "projectType": "application", - "schematics": { - "@schematics/angular:component": { - "style": "scss" - }, - "@schematics/angular:application": { - "strict": true - } - }, - "root": "", - "sourceRoot": "src", - "prefix": "app", - "architect": { - "build": { - "builder": "@angular-devkit/build-angular:browser", - "options": { - "outputPath": "dist/Website", - "index": "src/index.html", - "main": "src/main.ts", - "polyfills": "src/polyfills.ts", - "tsConfig": "tsconfig.app.json", - "inlineStyleLanguage": "scss", - "assets": [ - "src/favicon.ico", - "src/assets" - ], - "styles": [ - "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", - "src/styles.scss" - ], - "scripts": [] - }, - "configurations": { - "production": { - "budgets": [ - { - "type": "initial", - "maximumWarning": "500kb", - "maximumError": "1mb" + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "newProjectRoot": "projects", + "projects": { + "Website": { + "projectType": "application", + "schematics": { + "@schematics/angular:component": { + "style": "scss" }, - { - "type": "anyComponentStyle", - "maximumWarning": "2kb", - "maximumError": "4kb" + "@schematics/angular:application": { + "strict": true } - ], - "fileReplacements": [ - { - "replace": "src/environments/environment.ts", - "with": "src/environments/environment.prod.ts" + }, + "root": "", + "sourceRoot": "src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:browser", + "options": { + "outputPath": "dist/Website", + "index": "src/index.html", + "main": "src/main.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "tsconfig.app.json", + "inlineStyleLanguage": "scss", + "assets": [ + "src/favicon.ico", + "src/assets" + ], + "styles": [ + "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", + "src/styles.scss" + ], + "scripts": [] + }, + "configurations": { + "production": { + "budgets": [{ + "type": "initial", + "maximumWarning": "500kb", + "maximumError": "1mb" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "2kb", + "maximumError": "4kb" + } + ], + "fileReplacements": [{ + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.prod.ts" + }], + "outputHashing": "all" + }, + "development": { + "buildOptimizer": false, + "optimization": false, + "vendorChunk": true, + "extractLicenses": false, + "sourceMap": true, + "namedChunks": true + } + }, + "defaultConfiguration": "production" + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "configurations": { + "production": { + "browserTarget": "Website:build:production" + }, + "development": { + "browserTarget": "Website:build:development" + } + }, + "options": { + "proxyConfig": "src/proxy.conf.json" + }, + "defaultConfiguration": "development" + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n", + "options": { + "browserTarget": "Website:build" + } + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "main": "src/test.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "tsconfig.spec.json", + "karmaConfig": "karma.conf.js", + "inlineStyleLanguage": "scss", + "assets": [ + "src/favicon.ico", + "src/assets" + ], + "styles": [ + "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", + "src/styles.scss" + ], + "scripts": [] + } } - ], - "outputHashing": "all" - }, - "development": { - "buildOptimizer": false, - "optimization": false, - "vendorChunk": true, - "extractLicenses": false, - "sourceMap": true, - "namedChunks": true } - }, - "defaultConfiguration": "production" - }, - "serve": { - "builder": "@angular-devkit/build-angular:dev-server", - "configurations": { - "production": { - "browserTarget": "Website:build:production" - }, - "development": { - "browserTarget": "Website:build:development" - } - }, - "defaultConfiguration": "development" - }, - "extract-i18n": { - "builder": "@angular-devkit/build-angular:extract-i18n", - "options": { - "browserTarget": "Website:build" - } - }, - "test": { - "builder": "@angular-devkit/build-angular:karma", - "options": { - "main": "src/test.ts", - "polyfills": "src/polyfills.ts", - "tsConfig": "tsconfig.spec.json", - "karmaConfig": "karma.conf.js", - "inlineStyleLanguage": "scss", - "assets": [ - "src/favicon.ico", - "src/assets" - ], - "styles": [ - "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", - "src/styles.scss" - ], - "scripts": [] - } } - } - } - }, - "defaultProject": "Website" -} + }, + "defaultProject": "Website" +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 3713633..5807647 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "@angular/router": "~12.2.0", "hammerjs": "^2.0.8", "rxjs": "~6.6.0", + "sha.js": "^2.4.11", "tslib": "^2.3.0", "zone.js": "~0.11.4" }, @@ -29,6 +30,7 @@ "@angular/compiler-cli": "~12.2.0", "@types/jasmine": "~3.8.0", "@types/node": "^12.11.1", + "@types/sha.js": "^2.4.0", "jasmine-core": "~3.8.0", "karma": "~6.3.0", "karma-chrome-launcher": "~3.1.0", @@ -2614,6 +2616,15 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "node_modules/@types/sha.js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/sha.js/-/sha.js-2.4.0.tgz", + "integrity": "sha512-amxKgPy6WJTKuw8mpUwjX2BSxuBtBmZfRwIUDIuPJKNwGN8CWDli8JTg5ONTWOtcTkHIstvT7oAhhYXqEjStHQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/source-list-map": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", @@ -7279,8 +7290,7 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/ini": { "version": "2.0.0", @@ -12765,8 +12775,7 @@ "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "node_modules/safe-regex": { "version": "1.1.0", @@ -13106,6 +13115,18 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "dev": true }, + "node_modules/sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "bin": { + "sha.js": "bin.js" + } + }, "node_modules/shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", @@ -17552,6 +17573,15 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "@types/sha.js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/sha.js/-/sha.js-2.4.0.tgz", + "integrity": "sha512-amxKgPy6WJTKuw8mpUwjX2BSxuBtBmZfRwIUDIuPJKNwGN8CWDli8JTg5ONTWOtcTkHIstvT7oAhhYXqEjStHQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/source-list-map": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", @@ -21129,8 +21159,7 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "ini": { "version": "2.0.0", @@ -25219,8 +25248,7 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "safe-regex": { "version": "1.1.0", @@ -25494,6 +25522,15 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "dev": true }, + "sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, "shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", diff --git a/package.json b/package.json index 092ece5..2c9a675 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "@angular/router": "~12.2.0", "hammerjs": "^2.0.8", "rxjs": "~6.6.0", + "sha.js": "^2.4.11", "tslib": "^2.3.0", "zone.js": "~0.11.4" }, @@ -31,6 +32,7 @@ "@angular/compiler-cli": "~12.2.0", "@types/jasmine": "~3.8.0", "@types/node": "^12.11.1", + "@types/sha.js": "^2.4.0", "jasmine-core": "~3.8.0", "karma": "~6.3.0", "karma-chrome-launcher": "~3.1.0", diff --git a/src/app/account.service.spec.ts b/src/app/account.service.spec.ts new file mode 100644 index 0000000..2ffad6f --- /dev/null +++ b/src/app/account.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { AccountService } from './account.service'; + +describe('AccountService', () => { + let service: AccountService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(AccountService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/account.service.ts b/src/app/account.service.ts new file mode 100644 index 0000000..2b64b6a --- /dev/null +++ b/src/app/account.service.ts @@ -0,0 +1,96 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { environment } from 'src/environments/environment'; +import { sha256 } from 'sha.js'; +import { Router } from '@angular/router'; + +class Answer { + success: Boolean = false +} + +class PresaltAnswer extends Answer { + presalt: string = '' +} + +class AuthenticateAnswer extends Answer { + token: string = '' +} + +class CheckTokenAnswer extends Answer { + valid: Boolean = false +} + +class TokenData { + iss: string = '' + dat: string = '' + typ: string = '' + usr: string = '' +} + +@Injectable({ + providedIn: 'root' +}) +export class AccountService { + public username = '?'; + private userId: bigint = BigInt(0); + private userToken: bigint = BigInt(0); + + constructor(private http: HttpClient, private router: Router) { + this.updateUserInfo() + } + + public login(username: string, password: string, onFail: Function, onConnectionError: Function) { + this.http.post(`${environment.apiURL}/users/presalt`, {username}) + .subscribe((answer: Object) => { + var presaltAnswer = Object.assign(new PresaltAnswer(), answer) + if (!presaltAnswer.success) { + onFail() + return + } + // this presalting and hashing is done to protect the user's password in case of a man-in-the-middle-attack, + // where an attacker might be able to intercept a password if not hashed before. The password still has to be + // hashed on the server-side to be sure but this method prevents dumb users from having their passweord + // they use everywhere easily leaked, because you still need to crack this one layer first + // also, typical automated hacking software will hopefully be confused by my weird code + var passwordHash = new sha256().update(password + presaltAnswer.presalt).digest('hex') + this.http.post(`${environment.apiURL}/users/authenticate`, {username, passwordHash}) + .subscribe((answer) => { + var authenticateAnswer = Object.assign(new AuthenticateAnswer(), answer) + var loginSuccess = authenticateAnswer.success == true + if (loginSuccess) { + localStorage.setItem('userToken', authenticateAnswer.token) + this.updateUserInfo() + this.username = username + this.router.navigate(['admin']) + } else { + onFail() + } + }, (error) => {onConnectionError()}) + }, (error) => {onConnectionError()}) + } + + updateUserInfo() { + var token = localStorage.getItem('userToken') + if (token == null) { + this.username = '' + return + } + this.http.post(`${environment.apiURL}/users/checkToken`, {token}).subscribe((answer: Object) => { + var checkTokenAnswer = Object.assign(new CheckTokenAnswer(), answer) + if (checkTokenAnswer.valid == true) { + var data = JSON.parse(atob(token?.split('.')[1] as string)) + var tokenData = Object.assign(new TokenData(), data) + this.username = tokenData.usr + } else { + this.username = '' + } + }, () => { + this.username = '' + }) + } + + logout() { + this.username = '' + localStorage.removeItem('userToken') + } +} diff --git a/src/app/admin/admin.component.html b/src/app/admin/admin.component.html new file mode 100644 index 0000000..f949c13 --- /dev/null +++ b/src/app/admin/admin.component.html @@ -0,0 +1,6 @@ +

Hello {{account.username}}!

+

Welcome to the admin panel

+ +

There is nothing to do here yet...

+ + \ No newline at end of file diff --git a/src/app/admin/admin.component.scss b/src/app/admin/admin.component.scss new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/app/admin/admin.component.scss diff --git a/src/app/admin/admin.component.spec.ts b/src/app/admin/admin.component.spec.ts new file mode 100644 index 0000000..eb28e42 --- /dev/null +++ b/src/app/admin/admin.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AdminComponent } from './admin.component'; + +describe('AdminComponent', () => { + let component: AdminComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ AdminComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(AdminComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/admin/admin.component.ts b/src/app/admin/admin.component.ts new file mode 100644 index 0000000..85273be --- /dev/null +++ b/src/app/admin/admin.component.ts @@ -0,0 +1,20 @@ +import { Component, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; +import { AccountService } from '../account.service'; + +@Component({ + selector: 'app-admin', + templateUrl: './admin.component.html', + styleUrls: ['./admin.component.scss'] +}) +export class AdminComponent implements OnInit { + + constructor(public account: AccountService, private router: Router) { + if (account.username.length == 0) { + router.navigate(['login']) + } + } + + ngOnInit(): void { + } +} diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index b250123..9a07a6e 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -1,9 +1,13 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; +import { AdminComponent } from './admin/admin.component'; import { HomeComponent } from './home/home.component'; +import { LoginComponent } from './login/login.component'; const routes: Routes = [ - { path: "", component: HomeComponent } + { path: "", component: HomeComponent }, + { path: "login", component: LoginComponent }, + { path: "admin", component: AdminComponent }, ]; @NgModule({ diff --git a/src/app/app.module.ts b/src/app/app.module.ts index a6f5aac..1fb908c 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -4,6 +4,15 @@ import { MatSidenavModule } from '@angular/material/sidenav'; import { MatListModule } from '@angular/material/list'; import { MatIconModule } from '@angular/material/icon'; +import { MatCardModule } from '@angular/material/card'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatButtonModule } from '@angular/material/button'; +import { MatInputModule } from '@angular/material/input'; +import { MatMenuModule } from '@angular/material/menu'; +import { MatTableModule } from '@angular/material/table'; +import { MatSlideToggleModule } from '@angular/material/slide-toggle'; +import { MatSelectModule } from '@angular/material/select'; +import { MatOptionModule } from '@angular/material/core'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; @@ -13,13 +22,21 @@ import { FooterComponent } from './footer/footer.component'; import { GridComponent } from './grid/grid.component'; +import { AccountService } from './account.service'; +import { LoginComponent } from './login/login.component' +import { HttpClientModule } from '@angular/common/http'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { AdminComponent } from './admin/admin.component'; + @NgModule({ declarations: [ AppComponent, ToolbarComponent, HomeComponent, FooterComponent, - GridComponent + GridComponent, + LoginComponent, + AdminComponent ], imports: [ BrowserModule, @@ -29,8 +46,20 @@ MatSidenavModule, MatListModule, MatIconModule, + HttpClientModule, + MatCardModule, + MatFormFieldModule, + MatInputModule, + FormsModule, + ReactiveFormsModule, + MatButtonModule, + MatMenuModule, + MatTableModule, + MatSlideToggleModule, + MatSelectModule, + MatOptionModule, ], - providers: [], + providers: [AccountService], bootstrap: [AppComponent] }) export class AppModule { } diff --git a/src/app/login/login.component.html b/src/app/login/login.component.html new file mode 100644 index 0000000..54bebb0 --- /dev/null +++ b/src/app/login/login.component.html @@ -0,0 +1,21 @@ + +

Login

+
+

Enter your credentials here to log into the website.

+
+ + Username + this field must not be empty + + + + Password + this field must not be empty + + + +

Something went wrong while authenticating you. Make sure both the username and password fields are filled and are correct.

+

Could not connect to the API at {{environment.apiURL}}

+
+
+
\ No newline at end of file diff --git a/src/app/login/login.component.scss b/src/app/login/login.component.scss new file mode 100644 index 0000000..75bbd4b --- /dev/null +++ b/src/app/login/login.component.scss @@ -0,0 +1,13 @@ +mat-card { + max-width: 40ch; +} + +:host { + display: flex; + justify-content: space-around; + padding: 20px; +} + +mat-form-field { + width: 100%; +} \ No newline at end of file diff --git a/src/app/login/login.component.spec.ts b/src/app/login/login.component.spec.ts new file mode 100644 index 0000000..d2c0e6c --- /dev/null +++ b/src/app/login/login.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { LoginComponent } from './login.component'; + +describe('LoginComponent', () => { + let component: LoginComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ LoginComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(LoginComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/login/login.component.ts b/src/app/login/login.component.ts new file mode 100644 index 0000000..450a8f0 --- /dev/null +++ b/src/app/login/login.component.ts @@ -0,0 +1,43 @@ +import { Component, OnInit } from '@angular/core'; +import { FormBuilder, FormGroup, NgForm, Validators } from '@angular/forms'; +import { environment } from 'src/environments/environment'; +import { AccountService } from '../account.service'; + +class LoginInformation { + constructor(public username: string, public password: string) { + + } +} + +@Component({ + selector: 'app-login', + templateUrl: './login.component.html', + styleUrls: ['./login.component.scss'] +}) +export class LoginComponent implements OnInit { + model = new LoginInformation('', '') + public form: FormGroup + public valid = true + public connectionError = false + public environment = environment + constructor(private formBuilder: FormBuilder, private account: AccountService) { + this.form = this.formBuilder.group({ + username: ['', Validators.required], + password: ['', Validators.required] + }) + } + + ngOnInit(): void { + } + + onSubmit() { + if (this.form.invalid) { + this.valid = false + return + } + this.valid = true + this.connectionError = false + this.account.login(this.form.controls.username.value, this.form.controls.password.value, + () => {this.valid = false}, () => {this.connectionError = true}); + } +} diff --git a/src/app/toolbar/toolbar.component.html b/src/app/toolbar/toolbar.component.html index c5f7157..b39e10b 100644 --- a/src/app/toolbar/toolbar.component.html +++ b/src/app/toolbar/toolbar.component.html @@ -1,6 +1,16 @@ - Lukas Eisenhauer - + Lukas Eisenhauer + Log In - \ No newline at end of file + + + + + + + + + \ No newline at end of file diff --git a/src/app/toolbar/toolbar.component.scss b/src/app/toolbar/toolbar.component.scss index e69de29..98ca747 100644 --- a/src/app/toolbar/toolbar.component.scss +++ b/src/app/toolbar/toolbar.component.scss @@ -0,0 +1,4 @@ +mat-toolbar-row { + display: flex; + justify-content: space-between; +} \ No newline at end of file diff --git a/src/app/toolbar/toolbar.component.ts b/src/app/toolbar/toolbar.component.ts index 16fb31d..400ceb5 100644 --- a/src/app/toolbar/toolbar.component.ts +++ b/src/app/toolbar/toolbar.component.ts @@ -1,4 +1,6 @@ import { Component, OnInit } from '@angular/core'; +import { AccountService } from '../account.service'; + @Component({ selector: 'app-toolbar', @@ -7,7 +9,7 @@ }) export class ToolbarComponent implements OnInit { - constructor() { } + constructor(public account: AccountService) { } ngOnInit(): void { } diff --git a/src/environments/environment.ts b/src/environments/environment.ts index f56ff47..ed4f097 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -3,7 +3,8 @@ // The list of file replacements can be found in `angular.json`. export const environment = { - production: false + production: false, + apiURL: "/api", }; /* diff --git a/angular.json b/angular.json index 90d80e5..f23dd59 100644 --- a/angular.json +++ b/angular.json @@ -1,113 +1,113 @@ { - "$schema": "./node_modules/@angular/cli/lib/config/schema.json", - "version": 1, - "newProjectRoot": "projects", - "projects": { - "Website": { - "projectType": "application", - "schematics": { - "@schematics/angular:component": { - "style": "scss" - }, - "@schematics/angular:application": { - "strict": true - } - }, - "root": "", - "sourceRoot": "src", - "prefix": "app", - "architect": { - "build": { - "builder": "@angular-devkit/build-angular:browser", - "options": { - "outputPath": "dist/Website", - "index": "src/index.html", - "main": "src/main.ts", - "polyfills": "src/polyfills.ts", - "tsConfig": "tsconfig.app.json", - "inlineStyleLanguage": "scss", - "assets": [ - "src/favicon.ico", - "src/assets" - ], - "styles": [ - "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", - "src/styles.scss" - ], - "scripts": [] - }, - "configurations": { - "production": { - "budgets": [ - { - "type": "initial", - "maximumWarning": "500kb", - "maximumError": "1mb" + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "newProjectRoot": "projects", + "projects": { + "Website": { + "projectType": "application", + "schematics": { + "@schematics/angular:component": { + "style": "scss" }, - { - "type": "anyComponentStyle", - "maximumWarning": "2kb", - "maximumError": "4kb" + "@schematics/angular:application": { + "strict": true } - ], - "fileReplacements": [ - { - "replace": "src/environments/environment.ts", - "with": "src/environments/environment.prod.ts" + }, + "root": "", + "sourceRoot": "src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:browser", + "options": { + "outputPath": "dist/Website", + "index": "src/index.html", + "main": "src/main.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "tsconfig.app.json", + "inlineStyleLanguage": "scss", + "assets": [ + "src/favicon.ico", + "src/assets" + ], + "styles": [ + "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", + "src/styles.scss" + ], + "scripts": [] + }, + "configurations": { + "production": { + "budgets": [{ + "type": "initial", + "maximumWarning": "500kb", + "maximumError": "1mb" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "2kb", + "maximumError": "4kb" + } + ], + "fileReplacements": [{ + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.prod.ts" + }], + "outputHashing": "all" + }, + "development": { + "buildOptimizer": false, + "optimization": false, + "vendorChunk": true, + "extractLicenses": false, + "sourceMap": true, + "namedChunks": true + } + }, + "defaultConfiguration": "production" + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "configurations": { + "production": { + "browserTarget": "Website:build:production" + }, + "development": { + "browserTarget": "Website:build:development" + } + }, + "options": { + "proxyConfig": "src/proxy.conf.json" + }, + "defaultConfiguration": "development" + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n", + "options": { + "browserTarget": "Website:build" + } + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "main": "src/test.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "tsconfig.spec.json", + "karmaConfig": "karma.conf.js", + "inlineStyleLanguage": "scss", + "assets": [ + "src/favicon.ico", + "src/assets" + ], + "styles": [ + "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", + "src/styles.scss" + ], + "scripts": [] + } } - ], - "outputHashing": "all" - }, - "development": { - "buildOptimizer": false, - "optimization": false, - "vendorChunk": true, - "extractLicenses": false, - "sourceMap": true, - "namedChunks": true } - }, - "defaultConfiguration": "production" - }, - "serve": { - "builder": "@angular-devkit/build-angular:dev-server", - "configurations": { - "production": { - "browserTarget": "Website:build:production" - }, - "development": { - "browserTarget": "Website:build:development" - } - }, - "defaultConfiguration": "development" - }, - "extract-i18n": { - "builder": "@angular-devkit/build-angular:extract-i18n", - "options": { - "browserTarget": "Website:build" - } - }, - "test": { - "builder": "@angular-devkit/build-angular:karma", - "options": { - "main": "src/test.ts", - "polyfills": "src/polyfills.ts", - "tsConfig": "tsconfig.spec.json", - "karmaConfig": "karma.conf.js", - "inlineStyleLanguage": "scss", - "assets": [ - "src/favicon.ico", - "src/assets" - ], - "styles": [ - "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", - "src/styles.scss" - ], - "scripts": [] - } } - } - } - }, - "defaultProject": "Website" -} + }, + "defaultProject": "Website" +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 3713633..5807647 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "@angular/router": "~12.2.0", "hammerjs": "^2.0.8", "rxjs": "~6.6.0", + "sha.js": "^2.4.11", "tslib": "^2.3.0", "zone.js": "~0.11.4" }, @@ -29,6 +30,7 @@ "@angular/compiler-cli": "~12.2.0", "@types/jasmine": "~3.8.0", "@types/node": "^12.11.1", + "@types/sha.js": "^2.4.0", "jasmine-core": "~3.8.0", "karma": "~6.3.0", "karma-chrome-launcher": "~3.1.0", @@ -2614,6 +2616,15 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "node_modules/@types/sha.js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/sha.js/-/sha.js-2.4.0.tgz", + "integrity": "sha512-amxKgPy6WJTKuw8mpUwjX2BSxuBtBmZfRwIUDIuPJKNwGN8CWDli8JTg5ONTWOtcTkHIstvT7oAhhYXqEjStHQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/source-list-map": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", @@ -7279,8 +7290,7 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/ini": { "version": "2.0.0", @@ -12765,8 +12775,7 @@ "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "node_modules/safe-regex": { "version": "1.1.0", @@ -13106,6 +13115,18 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "dev": true }, + "node_modules/sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "bin": { + "sha.js": "bin.js" + } + }, "node_modules/shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", @@ -17552,6 +17573,15 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "@types/sha.js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/sha.js/-/sha.js-2.4.0.tgz", + "integrity": "sha512-amxKgPy6WJTKuw8mpUwjX2BSxuBtBmZfRwIUDIuPJKNwGN8CWDli8JTg5ONTWOtcTkHIstvT7oAhhYXqEjStHQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/source-list-map": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", @@ -21129,8 +21159,7 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "ini": { "version": "2.0.0", @@ -25219,8 +25248,7 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "safe-regex": { "version": "1.1.0", @@ -25494,6 +25522,15 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "dev": true }, + "sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, "shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", diff --git a/package.json b/package.json index 092ece5..2c9a675 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "@angular/router": "~12.2.0", "hammerjs": "^2.0.8", "rxjs": "~6.6.0", + "sha.js": "^2.4.11", "tslib": "^2.3.0", "zone.js": "~0.11.4" }, @@ -31,6 +32,7 @@ "@angular/compiler-cli": "~12.2.0", "@types/jasmine": "~3.8.0", "@types/node": "^12.11.1", + "@types/sha.js": "^2.4.0", "jasmine-core": "~3.8.0", "karma": "~6.3.0", "karma-chrome-launcher": "~3.1.0", diff --git a/src/app/account.service.spec.ts b/src/app/account.service.spec.ts new file mode 100644 index 0000000..2ffad6f --- /dev/null +++ b/src/app/account.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { AccountService } from './account.service'; + +describe('AccountService', () => { + let service: AccountService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(AccountService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/account.service.ts b/src/app/account.service.ts new file mode 100644 index 0000000..2b64b6a --- /dev/null +++ b/src/app/account.service.ts @@ -0,0 +1,96 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { environment } from 'src/environments/environment'; +import { sha256 } from 'sha.js'; +import { Router } from '@angular/router'; + +class Answer { + success: Boolean = false +} + +class PresaltAnswer extends Answer { + presalt: string = '' +} + +class AuthenticateAnswer extends Answer { + token: string = '' +} + +class CheckTokenAnswer extends Answer { + valid: Boolean = false +} + +class TokenData { + iss: string = '' + dat: string = '' + typ: string = '' + usr: string = '' +} + +@Injectable({ + providedIn: 'root' +}) +export class AccountService { + public username = '?'; + private userId: bigint = BigInt(0); + private userToken: bigint = BigInt(0); + + constructor(private http: HttpClient, private router: Router) { + this.updateUserInfo() + } + + public login(username: string, password: string, onFail: Function, onConnectionError: Function) { + this.http.post(`${environment.apiURL}/users/presalt`, {username}) + .subscribe((answer: Object) => { + var presaltAnswer = Object.assign(new PresaltAnswer(), answer) + if (!presaltAnswer.success) { + onFail() + return + } + // this presalting and hashing is done to protect the user's password in case of a man-in-the-middle-attack, + // where an attacker might be able to intercept a password if not hashed before. The password still has to be + // hashed on the server-side to be sure but this method prevents dumb users from having their passweord + // they use everywhere easily leaked, because you still need to crack this one layer first + // also, typical automated hacking software will hopefully be confused by my weird code + var passwordHash = new sha256().update(password + presaltAnswer.presalt).digest('hex') + this.http.post(`${environment.apiURL}/users/authenticate`, {username, passwordHash}) + .subscribe((answer) => { + var authenticateAnswer = Object.assign(new AuthenticateAnswer(), answer) + var loginSuccess = authenticateAnswer.success == true + if (loginSuccess) { + localStorage.setItem('userToken', authenticateAnswer.token) + this.updateUserInfo() + this.username = username + this.router.navigate(['admin']) + } else { + onFail() + } + }, (error) => {onConnectionError()}) + }, (error) => {onConnectionError()}) + } + + updateUserInfo() { + var token = localStorage.getItem('userToken') + if (token == null) { + this.username = '' + return + } + this.http.post(`${environment.apiURL}/users/checkToken`, {token}).subscribe((answer: Object) => { + var checkTokenAnswer = Object.assign(new CheckTokenAnswer(), answer) + if (checkTokenAnswer.valid == true) { + var data = JSON.parse(atob(token?.split('.')[1] as string)) + var tokenData = Object.assign(new TokenData(), data) + this.username = tokenData.usr + } else { + this.username = '' + } + }, () => { + this.username = '' + }) + } + + logout() { + this.username = '' + localStorage.removeItem('userToken') + } +} diff --git a/src/app/admin/admin.component.html b/src/app/admin/admin.component.html new file mode 100644 index 0000000..f949c13 --- /dev/null +++ b/src/app/admin/admin.component.html @@ -0,0 +1,6 @@ +

Hello {{account.username}}!

+

Welcome to the admin panel

+ +

There is nothing to do here yet...

+ + \ No newline at end of file diff --git a/src/app/admin/admin.component.scss b/src/app/admin/admin.component.scss new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/app/admin/admin.component.scss diff --git a/src/app/admin/admin.component.spec.ts b/src/app/admin/admin.component.spec.ts new file mode 100644 index 0000000..eb28e42 --- /dev/null +++ b/src/app/admin/admin.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AdminComponent } from './admin.component'; + +describe('AdminComponent', () => { + let component: AdminComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ AdminComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(AdminComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/admin/admin.component.ts b/src/app/admin/admin.component.ts new file mode 100644 index 0000000..85273be --- /dev/null +++ b/src/app/admin/admin.component.ts @@ -0,0 +1,20 @@ +import { Component, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; +import { AccountService } from '../account.service'; + +@Component({ + selector: 'app-admin', + templateUrl: './admin.component.html', + styleUrls: ['./admin.component.scss'] +}) +export class AdminComponent implements OnInit { + + constructor(public account: AccountService, private router: Router) { + if (account.username.length == 0) { + router.navigate(['login']) + } + } + + ngOnInit(): void { + } +} diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index b250123..9a07a6e 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -1,9 +1,13 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; +import { AdminComponent } from './admin/admin.component'; import { HomeComponent } from './home/home.component'; +import { LoginComponent } from './login/login.component'; const routes: Routes = [ - { path: "", component: HomeComponent } + { path: "", component: HomeComponent }, + { path: "login", component: LoginComponent }, + { path: "admin", component: AdminComponent }, ]; @NgModule({ diff --git a/src/app/app.module.ts b/src/app/app.module.ts index a6f5aac..1fb908c 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -4,6 +4,15 @@ import { MatSidenavModule } from '@angular/material/sidenav'; import { MatListModule } from '@angular/material/list'; import { MatIconModule } from '@angular/material/icon'; +import { MatCardModule } from '@angular/material/card'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatButtonModule } from '@angular/material/button'; +import { MatInputModule } from '@angular/material/input'; +import { MatMenuModule } from '@angular/material/menu'; +import { MatTableModule } from '@angular/material/table'; +import { MatSlideToggleModule } from '@angular/material/slide-toggle'; +import { MatSelectModule } from '@angular/material/select'; +import { MatOptionModule } from '@angular/material/core'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; @@ -13,13 +22,21 @@ import { FooterComponent } from './footer/footer.component'; import { GridComponent } from './grid/grid.component'; +import { AccountService } from './account.service'; +import { LoginComponent } from './login/login.component' +import { HttpClientModule } from '@angular/common/http'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { AdminComponent } from './admin/admin.component'; + @NgModule({ declarations: [ AppComponent, ToolbarComponent, HomeComponent, FooterComponent, - GridComponent + GridComponent, + LoginComponent, + AdminComponent ], imports: [ BrowserModule, @@ -29,8 +46,20 @@ MatSidenavModule, MatListModule, MatIconModule, + HttpClientModule, + MatCardModule, + MatFormFieldModule, + MatInputModule, + FormsModule, + ReactiveFormsModule, + MatButtonModule, + MatMenuModule, + MatTableModule, + MatSlideToggleModule, + MatSelectModule, + MatOptionModule, ], - providers: [], + providers: [AccountService], bootstrap: [AppComponent] }) export class AppModule { } diff --git a/src/app/login/login.component.html b/src/app/login/login.component.html new file mode 100644 index 0000000..54bebb0 --- /dev/null +++ b/src/app/login/login.component.html @@ -0,0 +1,21 @@ + +

Login

+
+

Enter your credentials here to log into the website.

+
+ + Username + this field must not be empty + + + + Password + this field must not be empty + + + +

Something went wrong while authenticating you. Make sure both the username and password fields are filled and are correct.

+

Could not connect to the API at {{environment.apiURL}}

+
+
+
\ No newline at end of file diff --git a/src/app/login/login.component.scss b/src/app/login/login.component.scss new file mode 100644 index 0000000..75bbd4b --- /dev/null +++ b/src/app/login/login.component.scss @@ -0,0 +1,13 @@ +mat-card { + max-width: 40ch; +} + +:host { + display: flex; + justify-content: space-around; + padding: 20px; +} + +mat-form-field { + width: 100%; +} \ No newline at end of file diff --git a/src/app/login/login.component.spec.ts b/src/app/login/login.component.spec.ts new file mode 100644 index 0000000..d2c0e6c --- /dev/null +++ b/src/app/login/login.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { LoginComponent } from './login.component'; + +describe('LoginComponent', () => { + let component: LoginComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ LoginComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(LoginComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/login/login.component.ts b/src/app/login/login.component.ts new file mode 100644 index 0000000..450a8f0 --- /dev/null +++ b/src/app/login/login.component.ts @@ -0,0 +1,43 @@ +import { Component, OnInit } from '@angular/core'; +import { FormBuilder, FormGroup, NgForm, Validators } from '@angular/forms'; +import { environment } from 'src/environments/environment'; +import { AccountService } from '../account.service'; + +class LoginInformation { + constructor(public username: string, public password: string) { + + } +} + +@Component({ + selector: 'app-login', + templateUrl: './login.component.html', + styleUrls: ['./login.component.scss'] +}) +export class LoginComponent implements OnInit { + model = new LoginInformation('', '') + public form: FormGroup + public valid = true + public connectionError = false + public environment = environment + constructor(private formBuilder: FormBuilder, private account: AccountService) { + this.form = this.formBuilder.group({ + username: ['', Validators.required], + password: ['', Validators.required] + }) + } + + ngOnInit(): void { + } + + onSubmit() { + if (this.form.invalid) { + this.valid = false + return + } + this.valid = true + this.connectionError = false + this.account.login(this.form.controls.username.value, this.form.controls.password.value, + () => {this.valid = false}, () => {this.connectionError = true}); + } +} diff --git a/src/app/toolbar/toolbar.component.html b/src/app/toolbar/toolbar.component.html index c5f7157..b39e10b 100644 --- a/src/app/toolbar/toolbar.component.html +++ b/src/app/toolbar/toolbar.component.html @@ -1,6 +1,16 @@ - Lukas Eisenhauer - + Lukas Eisenhauer + Log In - \ No newline at end of file + + + + + + + + + \ No newline at end of file diff --git a/src/app/toolbar/toolbar.component.scss b/src/app/toolbar/toolbar.component.scss index e69de29..98ca747 100644 --- a/src/app/toolbar/toolbar.component.scss +++ b/src/app/toolbar/toolbar.component.scss @@ -0,0 +1,4 @@ +mat-toolbar-row { + display: flex; + justify-content: space-between; +} \ No newline at end of file diff --git a/src/app/toolbar/toolbar.component.ts b/src/app/toolbar/toolbar.component.ts index 16fb31d..400ceb5 100644 --- a/src/app/toolbar/toolbar.component.ts +++ b/src/app/toolbar/toolbar.component.ts @@ -1,4 +1,6 @@ import { Component, OnInit } from '@angular/core'; +import { AccountService } from '../account.service'; + @Component({ selector: 'app-toolbar', @@ -7,7 +9,7 @@ }) export class ToolbarComponent implements OnInit { - constructor() { } + constructor(public account: AccountService) { } ngOnInit(): void { } diff --git a/src/environments/environment.ts b/src/environments/environment.ts index f56ff47..ed4f097 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -3,7 +3,8 @@ // The list of file replacements can be found in `angular.json`. export const environment = { - production: false + production: false, + apiURL: "/api", }; /* diff --git a/src/proxy.conf.json b/src/proxy.conf.json new file mode 100644 index 0000000..99b4828 --- /dev/null +++ b/src/proxy.conf.json @@ -0,0 +1,6 @@ +{ + "/api": { + "target": "http://localhost:3000", + "secure": false + } +} \ No newline at end of file diff --git a/angular.json b/angular.json index 90d80e5..f23dd59 100644 --- a/angular.json +++ b/angular.json @@ -1,113 +1,113 @@ { - "$schema": "./node_modules/@angular/cli/lib/config/schema.json", - "version": 1, - "newProjectRoot": "projects", - "projects": { - "Website": { - "projectType": "application", - "schematics": { - "@schematics/angular:component": { - "style": "scss" - }, - "@schematics/angular:application": { - "strict": true - } - }, - "root": "", - "sourceRoot": "src", - "prefix": "app", - "architect": { - "build": { - "builder": "@angular-devkit/build-angular:browser", - "options": { - "outputPath": "dist/Website", - "index": "src/index.html", - "main": "src/main.ts", - "polyfills": "src/polyfills.ts", - "tsConfig": "tsconfig.app.json", - "inlineStyleLanguage": "scss", - "assets": [ - "src/favicon.ico", - "src/assets" - ], - "styles": [ - "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", - "src/styles.scss" - ], - "scripts": [] - }, - "configurations": { - "production": { - "budgets": [ - { - "type": "initial", - "maximumWarning": "500kb", - "maximumError": "1mb" + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "newProjectRoot": "projects", + "projects": { + "Website": { + "projectType": "application", + "schematics": { + "@schematics/angular:component": { + "style": "scss" }, - { - "type": "anyComponentStyle", - "maximumWarning": "2kb", - "maximumError": "4kb" + "@schematics/angular:application": { + "strict": true } - ], - "fileReplacements": [ - { - "replace": "src/environments/environment.ts", - "with": "src/environments/environment.prod.ts" + }, + "root": "", + "sourceRoot": "src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:browser", + "options": { + "outputPath": "dist/Website", + "index": "src/index.html", + "main": "src/main.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "tsconfig.app.json", + "inlineStyleLanguage": "scss", + "assets": [ + "src/favicon.ico", + "src/assets" + ], + "styles": [ + "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", + "src/styles.scss" + ], + "scripts": [] + }, + "configurations": { + "production": { + "budgets": [{ + "type": "initial", + "maximumWarning": "500kb", + "maximumError": "1mb" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "2kb", + "maximumError": "4kb" + } + ], + "fileReplacements": [{ + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.prod.ts" + }], + "outputHashing": "all" + }, + "development": { + "buildOptimizer": false, + "optimization": false, + "vendorChunk": true, + "extractLicenses": false, + "sourceMap": true, + "namedChunks": true + } + }, + "defaultConfiguration": "production" + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "configurations": { + "production": { + "browserTarget": "Website:build:production" + }, + "development": { + "browserTarget": "Website:build:development" + } + }, + "options": { + "proxyConfig": "src/proxy.conf.json" + }, + "defaultConfiguration": "development" + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n", + "options": { + "browserTarget": "Website:build" + } + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "main": "src/test.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "tsconfig.spec.json", + "karmaConfig": "karma.conf.js", + "inlineStyleLanguage": "scss", + "assets": [ + "src/favicon.ico", + "src/assets" + ], + "styles": [ + "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", + "src/styles.scss" + ], + "scripts": [] + } } - ], - "outputHashing": "all" - }, - "development": { - "buildOptimizer": false, - "optimization": false, - "vendorChunk": true, - "extractLicenses": false, - "sourceMap": true, - "namedChunks": true } - }, - "defaultConfiguration": "production" - }, - "serve": { - "builder": "@angular-devkit/build-angular:dev-server", - "configurations": { - "production": { - "browserTarget": "Website:build:production" - }, - "development": { - "browserTarget": "Website:build:development" - } - }, - "defaultConfiguration": "development" - }, - "extract-i18n": { - "builder": "@angular-devkit/build-angular:extract-i18n", - "options": { - "browserTarget": "Website:build" - } - }, - "test": { - "builder": "@angular-devkit/build-angular:karma", - "options": { - "main": "src/test.ts", - "polyfills": "src/polyfills.ts", - "tsConfig": "tsconfig.spec.json", - "karmaConfig": "karma.conf.js", - "inlineStyleLanguage": "scss", - "assets": [ - "src/favicon.ico", - "src/assets" - ], - "styles": [ - "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", - "src/styles.scss" - ], - "scripts": [] - } } - } - } - }, - "defaultProject": "Website" -} + }, + "defaultProject": "Website" +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 3713633..5807647 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "@angular/router": "~12.2.0", "hammerjs": "^2.0.8", "rxjs": "~6.6.0", + "sha.js": "^2.4.11", "tslib": "^2.3.0", "zone.js": "~0.11.4" }, @@ -29,6 +30,7 @@ "@angular/compiler-cli": "~12.2.0", "@types/jasmine": "~3.8.0", "@types/node": "^12.11.1", + "@types/sha.js": "^2.4.0", "jasmine-core": "~3.8.0", "karma": "~6.3.0", "karma-chrome-launcher": "~3.1.0", @@ -2614,6 +2616,15 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "node_modules/@types/sha.js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/sha.js/-/sha.js-2.4.0.tgz", + "integrity": "sha512-amxKgPy6WJTKuw8mpUwjX2BSxuBtBmZfRwIUDIuPJKNwGN8CWDli8JTg5ONTWOtcTkHIstvT7oAhhYXqEjStHQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/source-list-map": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", @@ -7279,8 +7290,7 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/ini": { "version": "2.0.0", @@ -12765,8 +12775,7 @@ "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "node_modules/safe-regex": { "version": "1.1.0", @@ -13106,6 +13115,18 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "dev": true }, + "node_modules/sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "bin": { + "sha.js": "bin.js" + } + }, "node_modules/shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", @@ -17552,6 +17573,15 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "@types/sha.js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/sha.js/-/sha.js-2.4.0.tgz", + "integrity": "sha512-amxKgPy6WJTKuw8mpUwjX2BSxuBtBmZfRwIUDIuPJKNwGN8CWDli8JTg5ONTWOtcTkHIstvT7oAhhYXqEjStHQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/source-list-map": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", @@ -21129,8 +21159,7 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "ini": { "version": "2.0.0", @@ -25219,8 +25248,7 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "safe-regex": { "version": "1.1.0", @@ -25494,6 +25522,15 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "dev": true }, + "sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, "shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", diff --git a/package.json b/package.json index 092ece5..2c9a675 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "@angular/router": "~12.2.0", "hammerjs": "^2.0.8", "rxjs": "~6.6.0", + "sha.js": "^2.4.11", "tslib": "^2.3.0", "zone.js": "~0.11.4" }, @@ -31,6 +32,7 @@ "@angular/compiler-cli": "~12.2.0", "@types/jasmine": "~3.8.0", "@types/node": "^12.11.1", + "@types/sha.js": "^2.4.0", "jasmine-core": "~3.8.0", "karma": "~6.3.0", "karma-chrome-launcher": "~3.1.0", diff --git a/src/app/account.service.spec.ts b/src/app/account.service.spec.ts new file mode 100644 index 0000000..2ffad6f --- /dev/null +++ b/src/app/account.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { AccountService } from './account.service'; + +describe('AccountService', () => { + let service: AccountService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(AccountService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/account.service.ts b/src/app/account.service.ts new file mode 100644 index 0000000..2b64b6a --- /dev/null +++ b/src/app/account.service.ts @@ -0,0 +1,96 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { environment } from 'src/environments/environment'; +import { sha256 } from 'sha.js'; +import { Router } from '@angular/router'; + +class Answer { + success: Boolean = false +} + +class PresaltAnswer extends Answer { + presalt: string = '' +} + +class AuthenticateAnswer extends Answer { + token: string = '' +} + +class CheckTokenAnswer extends Answer { + valid: Boolean = false +} + +class TokenData { + iss: string = '' + dat: string = '' + typ: string = '' + usr: string = '' +} + +@Injectable({ + providedIn: 'root' +}) +export class AccountService { + public username = '?'; + private userId: bigint = BigInt(0); + private userToken: bigint = BigInt(0); + + constructor(private http: HttpClient, private router: Router) { + this.updateUserInfo() + } + + public login(username: string, password: string, onFail: Function, onConnectionError: Function) { + this.http.post(`${environment.apiURL}/users/presalt`, {username}) + .subscribe((answer: Object) => { + var presaltAnswer = Object.assign(new PresaltAnswer(), answer) + if (!presaltAnswer.success) { + onFail() + return + } + // this presalting and hashing is done to protect the user's password in case of a man-in-the-middle-attack, + // where an attacker might be able to intercept a password if not hashed before. The password still has to be + // hashed on the server-side to be sure but this method prevents dumb users from having their passweord + // they use everywhere easily leaked, because you still need to crack this one layer first + // also, typical automated hacking software will hopefully be confused by my weird code + var passwordHash = new sha256().update(password + presaltAnswer.presalt).digest('hex') + this.http.post(`${environment.apiURL}/users/authenticate`, {username, passwordHash}) + .subscribe((answer) => { + var authenticateAnswer = Object.assign(new AuthenticateAnswer(), answer) + var loginSuccess = authenticateAnswer.success == true + if (loginSuccess) { + localStorage.setItem('userToken', authenticateAnswer.token) + this.updateUserInfo() + this.username = username + this.router.navigate(['admin']) + } else { + onFail() + } + }, (error) => {onConnectionError()}) + }, (error) => {onConnectionError()}) + } + + updateUserInfo() { + var token = localStorage.getItem('userToken') + if (token == null) { + this.username = '' + return + } + this.http.post(`${environment.apiURL}/users/checkToken`, {token}).subscribe((answer: Object) => { + var checkTokenAnswer = Object.assign(new CheckTokenAnswer(), answer) + if (checkTokenAnswer.valid == true) { + var data = JSON.parse(atob(token?.split('.')[1] as string)) + var tokenData = Object.assign(new TokenData(), data) + this.username = tokenData.usr + } else { + this.username = '' + } + }, () => { + this.username = '' + }) + } + + logout() { + this.username = '' + localStorage.removeItem('userToken') + } +} diff --git a/src/app/admin/admin.component.html b/src/app/admin/admin.component.html new file mode 100644 index 0000000..f949c13 --- /dev/null +++ b/src/app/admin/admin.component.html @@ -0,0 +1,6 @@ +

Hello {{account.username}}!

+

Welcome to the admin panel

+ +

There is nothing to do here yet...

+ + \ No newline at end of file diff --git a/src/app/admin/admin.component.scss b/src/app/admin/admin.component.scss new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/app/admin/admin.component.scss diff --git a/src/app/admin/admin.component.spec.ts b/src/app/admin/admin.component.spec.ts new file mode 100644 index 0000000..eb28e42 --- /dev/null +++ b/src/app/admin/admin.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AdminComponent } from './admin.component'; + +describe('AdminComponent', () => { + let component: AdminComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ AdminComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(AdminComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/admin/admin.component.ts b/src/app/admin/admin.component.ts new file mode 100644 index 0000000..85273be --- /dev/null +++ b/src/app/admin/admin.component.ts @@ -0,0 +1,20 @@ +import { Component, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; +import { AccountService } from '../account.service'; + +@Component({ + selector: 'app-admin', + templateUrl: './admin.component.html', + styleUrls: ['./admin.component.scss'] +}) +export class AdminComponent implements OnInit { + + constructor(public account: AccountService, private router: Router) { + if (account.username.length == 0) { + router.navigate(['login']) + } + } + + ngOnInit(): void { + } +} diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index b250123..9a07a6e 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -1,9 +1,13 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; +import { AdminComponent } from './admin/admin.component'; import { HomeComponent } from './home/home.component'; +import { LoginComponent } from './login/login.component'; const routes: Routes = [ - { path: "", component: HomeComponent } + { path: "", component: HomeComponent }, + { path: "login", component: LoginComponent }, + { path: "admin", component: AdminComponent }, ]; @NgModule({ diff --git a/src/app/app.module.ts b/src/app/app.module.ts index a6f5aac..1fb908c 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -4,6 +4,15 @@ import { MatSidenavModule } from '@angular/material/sidenav'; import { MatListModule } from '@angular/material/list'; import { MatIconModule } from '@angular/material/icon'; +import { MatCardModule } from '@angular/material/card'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatButtonModule } from '@angular/material/button'; +import { MatInputModule } from '@angular/material/input'; +import { MatMenuModule } from '@angular/material/menu'; +import { MatTableModule } from '@angular/material/table'; +import { MatSlideToggleModule } from '@angular/material/slide-toggle'; +import { MatSelectModule } from '@angular/material/select'; +import { MatOptionModule } from '@angular/material/core'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; @@ -13,13 +22,21 @@ import { FooterComponent } from './footer/footer.component'; import { GridComponent } from './grid/grid.component'; +import { AccountService } from './account.service'; +import { LoginComponent } from './login/login.component' +import { HttpClientModule } from '@angular/common/http'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { AdminComponent } from './admin/admin.component'; + @NgModule({ declarations: [ AppComponent, ToolbarComponent, HomeComponent, FooterComponent, - GridComponent + GridComponent, + LoginComponent, + AdminComponent ], imports: [ BrowserModule, @@ -29,8 +46,20 @@ MatSidenavModule, MatListModule, MatIconModule, + HttpClientModule, + MatCardModule, + MatFormFieldModule, + MatInputModule, + FormsModule, + ReactiveFormsModule, + MatButtonModule, + MatMenuModule, + MatTableModule, + MatSlideToggleModule, + MatSelectModule, + MatOptionModule, ], - providers: [], + providers: [AccountService], bootstrap: [AppComponent] }) export class AppModule { } diff --git a/src/app/login/login.component.html b/src/app/login/login.component.html new file mode 100644 index 0000000..54bebb0 --- /dev/null +++ b/src/app/login/login.component.html @@ -0,0 +1,21 @@ + +

Login

+
+

Enter your credentials here to log into the website.

+
+ + Username + this field must not be empty + + + + Password + this field must not be empty + + + +

Something went wrong while authenticating you. Make sure both the username and password fields are filled and are correct.

+

Could not connect to the API at {{environment.apiURL}}

+
+
+
\ No newline at end of file diff --git a/src/app/login/login.component.scss b/src/app/login/login.component.scss new file mode 100644 index 0000000..75bbd4b --- /dev/null +++ b/src/app/login/login.component.scss @@ -0,0 +1,13 @@ +mat-card { + max-width: 40ch; +} + +:host { + display: flex; + justify-content: space-around; + padding: 20px; +} + +mat-form-field { + width: 100%; +} \ No newline at end of file diff --git a/src/app/login/login.component.spec.ts b/src/app/login/login.component.spec.ts new file mode 100644 index 0000000..d2c0e6c --- /dev/null +++ b/src/app/login/login.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { LoginComponent } from './login.component'; + +describe('LoginComponent', () => { + let component: LoginComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ LoginComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(LoginComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/login/login.component.ts b/src/app/login/login.component.ts new file mode 100644 index 0000000..450a8f0 --- /dev/null +++ b/src/app/login/login.component.ts @@ -0,0 +1,43 @@ +import { Component, OnInit } from '@angular/core'; +import { FormBuilder, FormGroup, NgForm, Validators } from '@angular/forms'; +import { environment } from 'src/environments/environment'; +import { AccountService } from '../account.service'; + +class LoginInformation { + constructor(public username: string, public password: string) { + + } +} + +@Component({ + selector: 'app-login', + templateUrl: './login.component.html', + styleUrls: ['./login.component.scss'] +}) +export class LoginComponent implements OnInit { + model = new LoginInformation('', '') + public form: FormGroup + public valid = true + public connectionError = false + public environment = environment + constructor(private formBuilder: FormBuilder, private account: AccountService) { + this.form = this.formBuilder.group({ + username: ['', Validators.required], + password: ['', Validators.required] + }) + } + + ngOnInit(): void { + } + + onSubmit() { + if (this.form.invalid) { + this.valid = false + return + } + this.valid = true + this.connectionError = false + this.account.login(this.form.controls.username.value, this.form.controls.password.value, + () => {this.valid = false}, () => {this.connectionError = true}); + } +} diff --git a/src/app/toolbar/toolbar.component.html b/src/app/toolbar/toolbar.component.html index c5f7157..b39e10b 100644 --- a/src/app/toolbar/toolbar.component.html +++ b/src/app/toolbar/toolbar.component.html @@ -1,6 +1,16 @@ - Lukas Eisenhauer - + Lukas Eisenhauer + Log In - \ No newline at end of file + + + + + + + + + \ No newline at end of file diff --git a/src/app/toolbar/toolbar.component.scss b/src/app/toolbar/toolbar.component.scss index e69de29..98ca747 100644 --- a/src/app/toolbar/toolbar.component.scss +++ b/src/app/toolbar/toolbar.component.scss @@ -0,0 +1,4 @@ +mat-toolbar-row { + display: flex; + justify-content: space-between; +} \ No newline at end of file diff --git a/src/app/toolbar/toolbar.component.ts b/src/app/toolbar/toolbar.component.ts index 16fb31d..400ceb5 100644 --- a/src/app/toolbar/toolbar.component.ts +++ b/src/app/toolbar/toolbar.component.ts @@ -1,4 +1,6 @@ import { Component, OnInit } from '@angular/core'; +import { AccountService } from '../account.service'; + @Component({ selector: 'app-toolbar', @@ -7,7 +9,7 @@ }) export class ToolbarComponent implements OnInit { - constructor() { } + constructor(public account: AccountService) { } ngOnInit(): void { } diff --git a/src/environments/environment.ts b/src/environments/environment.ts index f56ff47..ed4f097 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -3,7 +3,8 @@ // The list of file replacements can be found in `angular.json`. export const environment = { - production: false + production: false, + apiURL: "/api", }; /* diff --git a/src/proxy.conf.json b/src/proxy.conf.json new file mode 100644 index 0000000..99b4828 --- /dev/null +++ b/src/proxy.conf.json @@ -0,0 +1,6 @@ +{ + "/api": { + "target": "http://localhost:3000", + "secure": false + } +} \ No newline at end of file diff --git a/src/styles.scss b/src/styles.scss index 697ae39..10cb87c 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -19,6 +19,7 @@ $color: mat-get-color-config($Website-theme); $primary: map-get($color, primary); $accent: map-get($color, accent); +$error: map-get($color, warn); $background: map_get($mat-grey, 800); a { color: mat-color($primary); @@ -42,4 +43,8 @@ .accent { color: mat-color($accent); +} + +.error { + color: mat-color($error); } \ No newline at end of file