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 @@
+
+
+
+
Enter your credentials here to log into the 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/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 @@
+
+
+
+
Enter your credentials here to log into the website.
+
+
+
\ 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 @@
+
+
+
+
Enter your credentials here to log into the website.
+
+
+
\ 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 @@
+
+
+
+
Enter your credentials here to log into the website.
+
+
+
\ 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 @@
+
+
+
+
Enter your credentials here to log into the website.
+
+
+
\ 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 @@
+
+
+
+
Enter your credentials here to log into the website.
+
+
+
\ 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 @@
+
+
+
+
Enter your credentials here to log into the website.
+
+
+
\ 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 @@
+
+
+
+
Enter your credentials here to log into the website.
+
+
+
\ 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 @@
+
+
+
+
Enter your credentials here to log into the website.
+
+
+
\ 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 @@
+
+
+
+
Enter your credentials here to log into the website.
+
+
+
\ 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