diff --git a/.gitignore b/.gitignore index 42b7fa639bc932b406003702ed1d80f18d7e4487..3c05752f8f36d3406e5337a9ab9ee62ab68074af 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,8 @@ yarn-error.log* .idea .vscode + +# keys and .env +jwtRS256.key +jwtRS256.key.pub +server/.env \ No newline at end of file diff --git a/client/package-lock.json b/client/package-lock.json index 5e65f7c6bad07adf6696b4b3e2464f63ef2c8924..6e9af44fc4954699b8c0d81bc6c309002639e693 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -15,6 +15,7 @@ "@angular/platform-browser": "~11.1.1", "@angular/platform-browser-dynamic": "~11.1.1", "@angular/router": "~11.1.1", + "@auth0/angular-jwt": "^5.0.2", "rxjs": "~6.6.0", "tslib": "^2.0.0", "zone.js": "~0.11.3" @@ -434,6 +435,17 @@ "rxjs": "^6.5.3" } }, + "node_modules/@auth0/angular-jwt": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@auth0/angular-jwt/-/angular-jwt-5.0.2.tgz", + "integrity": "sha512-rSamC9mu+gUxoR86AXcIo+KD7xRIro+/iu1F2Ld85YAZEVKlpB5vYG+g0yGaEOqjtQWP/i0H6fi6XMGPVHSYYQ==", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@angular/common": ">=9.0.0" + } + }, "node_modules/@babel/code-frame": { "version": "7.12.13", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", @@ -19277,6 +19289,14 @@ "tslib": "^2.0.0" } }, + "@auth0/angular-jwt": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@auth0/angular-jwt/-/angular-jwt-5.0.2.tgz", + "integrity": "sha512-rSamC9mu+gUxoR86AXcIo+KD7xRIro+/iu1F2Ld85YAZEVKlpB5vYG+g0yGaEOqjtQWP/i0H6fi6XMGPVHSYYQ==", + "requires": { + "tslib": "^2.0.0" + } + }, "@babel/code-frame": { "version": "7.12.13", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", @@ -20926,13 +20946,15 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz", "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==", - "dev": true + "dev": true, + "requires": {} }, "ajv-keywords": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true + "dev": true, + "requires": {} }, "alphanum-sort": { "version": "1.0.2", @@ -21902,7 +21924,8 @@ "version": "5.2.2", "resolved": "https://registry.npmjs.org/circular-dependency-plugin/-/circular-dependency-plugin-5.2.2.tgz", "integrity": "sha512-g38K9Cm5WRwlaH6g03B9OEz/0qRizI+2I7n+Gz+L5DxXJAPAiWQvwlYNm1V1jkdpUv95bOe/ASm2vfi/G560jQ==", - "dev": true + "dev": true, + "requires": {} }, "class-utils": { "version": "0.3.6", @@ -22090,13 +22113,15 @@ "version": "9.0.0", "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-9.0.0.tgz", "integrity": "sha512-ctjwuntPfZZT2mNj2NDIVu51t9cvbhl/16epc5xEwyzyDt76pX9UgwvY+MbXrf/C/FWwdtmNtfP698BKI+9leQ==", - "dev": true + "dev": true, + "requires": {} }, "@angular/core": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/@angular/core/-/core-9.0.0.tgz", "integrity": "sha512-6Pxgsrf0qF9iFFqmIcWmjJGkkCaCm6V5QNnxMy2KloO3SDq6QuMVRbN9RtC8Urmo25LP+eZ6ZgYqFYpdD8Hd9w==", - "dev": true + "dev": true, + "requires": {} }, "source-map": { "version": "0.5.7", @@ -25216,7 +25241,8 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", - "dev": true + "dev": true, + "requires": {} }, "ieee754": { "version": "1.2.1", @@ -26258,7 +26284,8 @@ "version": "1.5.4", "resolved": "https://registry.npmjs.org/karma-jasmine-html-reporter/-/karma-jasmine-html-reporter-1.5.4.tgz", "integrity": "sha512-PtilRLno5O6wH3lDihRnz0Ba8oSn0YUJqKjjux1peoYGwo0AQqrWRbdWk/RLzcGlb+onTyXAnHl6M+Hu3UxG/Q==", - "dev": true + "dev": true, + "requires": {} }, "karma-source-map-support": { "version": "1.4.0", @@ -28738,7 +28765,8 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", - "dev": true + "dev": true, + "requires": {} }, "postcss-modules-local-by-default": { "version": "4.0.0", @@ -34211,7 +34239,8 @@ "version": "7.4.3", "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.3.tgz", "integrity": "sha512-hr6vCR76GsossIRsr8OLR9acVVm1jyfEWvhbNjtgPOrfvAlKzvyeg/P6r8RuDjRyrcQoPQT7K0DGEPc7Ae6jzA==", - "dev": true + "dev": true, + "requires": {} }, "xml2js": { "version": "0.4.23", diff --git a/client/package.json b/client/package.json index edef3c6d9e8ef53b84e7ff11b7252978790eb2b6..805856fbe06a1a4fcb17a5f41ae89107924f9d83 100644 --- a/client/package.json +++ b/client/package.json @@ -19,6 +19,7 @@ "@angular/platform-browser": "~11.1.1", "@angular/platform-browser-dynamic": "~11.1.1", "@angular/router": "~11.1.1", + "@auth0/angular-jwt": "^5.0.2", "rxjs": "~6.6.0", "tslib": "^2.0.0", "zone.js": "~0.11.3" diff --git a/client/src/app/app.component.html b/client/src/app/app.component.html index f883e04c2b54df54e15f9713c3c0f29e0ca5ba5c..6ad5852607b4366d9b3e425b23e6c75091c51fb8 100644 --- a/client/src/app/app.component.html +++ b/client/src/app/app.component.html @@ -12,4 +12,4 @@ <div class="wrapper"> <router-outlet></router-outlet> -</div> +</div> \ No newline at end of file diff --git a/client/src/app/app.component.ts b/client/src/app/app.component.ts index 6f97d4ed7d36e142c6cfd9a63f139cd068d6a94d..cc061ccd82009ac4a6add0a0df0b8e701e1c8d42 100644 --- a/client/src/app/app.component.ts +++ b/client/src/app/app.component.ts @@ -1,5 +1,4 @@ -import { Component - } from '@angular/core'; +import { Component } from '@angular/core'; @Component({ selector: 'app-root', @@ -7,5 +6,5 @@ import { Component styleUrls: ['./app.component.scss'] }) export class AppComponent { - title = 'client'; + title: string = 'client'; } diff --git a/client/src/app/app.module.ts b/client/src/app/app.module.ts index 28a13e79acd31f0b55aecb2588037c178e72002e..9b89086876c53bd68002c6c0fab318564f4e388b 100644 --- a/client/src/app/app.module.ts +++ b/client/src/app/app.module.ts @@ -1,13 +1,18 @@ import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { HttpClientModule } from '@angular/common/http'; +import { JwtModule } from "@auth0/angular-jwt"; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; import { PostModule } from './posts/post.module'; import { UserModule } from './users/user.module'; +import { AuthModule } from './authentication/auth.module'; import { SharedModule } from './shared/shared.module'; +export function tokenGetter() { + return localStorage.getItem("token"); +} @NgModule({ declarations: [ @@ -16,11 +21,18 @@ import { SharedModule } from './shared/shared.module'; imports: [ BrowserModule, UserModule, - + AuthModule, AppRoutingModule, PostModule, SharedModule, - HttpClientModule + HttpClientModule, + JwtModule.forRoot({ + config: { + tokenGetter: tokenGetter, + allowedDomains: ["localhost"], + disallowedRoutes: [""], + }, + }), ], providers: [], bootstrap: [AppComponent] diff --git a/client/src/app/authentication/auth.module.ts b/client/src/app/authentication/auth.module.ts new file mode 100644 index 0000000000000000000000000000000000000000..d2e4fae6f86c813833e6f24066c50b4076a0d599 --- /dev/null +++ b/client/src/app/authentication/auth.module.ts @@ -0,0 +1,12 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; + + + +@NgModule({ + declarations: [], + imports: [ + CommonModule + ] +}) +export class AuthModule { } diff --git a/client/src/app/authentication/auth.service.spec.ts b/client/src/app/authentication/auth.service.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..3a8899d71403b14ef4e4854ceeca83f9a042afc7 --- /dev/null +++ b/client/src/app/authentication/auth.service.spec.ts @@ -0,0 +1,20 @@ +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; + +import { AuthService } from './auth.service'; + +describe('AuthService', () => { + let service: AuthService; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ HttpClientTestingModule, RouterTestingModule ] + }); + service = TestBed.inject(AuthService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/client/src/app/authentication/auth.service.ts b/client/src/app/authentication/auth.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..0d86deefb6903edb9943838c011c88d46e122c42 --- /dev/null +++ b/client/src/app/authentication/auth.service.ts @@ -0,0 +1,70 @@ +import { HttpClient, HttpEvent, HttpInterceptor, HttpResponse } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { User } from '../models/user.model'; +import { tap, shareReplay } from 'rxjs/operators'; +import { Router } from '@angular/router'; + +interface IUserLogin { + username: string; + password: string; +} + +@Injectable({ + providedIn: 'root' +}) +export class AuthService { + loginUrl = "api/user/login" + + constructor(private http: HttpClient, private router: Router) { } + + login(body: IUserLogin): Promise<string> { + return new Promise<string>( + (resolve, reject) => { + this.login_user(body).subscribe((data: any) => { + try { + resolve(data.token); + } catch (err: any) { + reject(err); + } + }, + (err: any) => { + console.log(err.message); + reject(err); + }); + } + ); + } + private login_user(body: IUserLogin) { + return this.http.post(this.loginUrl, body).pipe( + tap(res =>this.setSession(res)), + shareReplay());; + } + private setSession(authResult) { + console.log(authResult); + localStorage.setItem('token', authResult.token); + } + checkTokenExpiration() { + const token = localStorage.getItem("token"); + if (token) { + const {iat, exp} = JSON.parse(atob(token?.split(".")[1])); + if (iat && exp) { + const issued = new Date(iat*1000); + const expires = new Date(exp*1000); + const now = new Date(); + // Expired token + if (now < issued || now >= expires) { + this.logout(); + this.router.navigateByUrl("/"); + return false + } + return true; + } + } + this.router.navigateByUrl("/") + return false + } + logout() { + localStorage.removeItem("token"); + } + +} diff --git a/client/src/app/users/user-login-form/user-login-form.component.spec.ts b/client/src/app/users/user-login-form/user-login-form.component.spec.ts index 1e5bb79f2b6a8740c5ac7d47f366eb95a493a309..e680f03e5b6d7cfbb17b3ce59431f26a5fcd6e7c 100644 --- a/client/src/app/users/user-login-form/user-login-form.component.spec.ts +++ b/client/src/app/users/user-login-form/user-login-form.component.spec.ts @@ -1,4 +1,6 @@ +import { HttpClientTestingModule } from '@angular/common/http/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; import { UserLoginFormComponent } from './user-login-form.component'; @@ -8,7 +10,8 @@ describe('UserLoginFormComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ UserLoginFormComponent ] + declarations: [ UserLoginFormComponent ], + imports: [ HttpClientTestingModule, RouterTestingModule ] }) .compileComponents(); }); diff --git a/client/src/app/users/user-login-form/user-login-form.component.ts b/client/src/app/users/user-login-form/user-login-form.component.ts index 24057cbcefc53d9e5f0bb169537f82d8515b8006..328d380f561a08a0c64bf9cbc458acba399aef2a 100644 --- a/client/src/app/users/user-login-form/user-login-form.component.ts +++ b/client/src/app/users/user-login-form/user-login-form.component.ts @@ -2,6 +2,7 @@ import { Component, OnInit } from '@angular/core'; import { Router } from '@angular/router'; import { User } from 'src/app/models/user.model'; import { UserService } from '../user.service'; +import { AuthService } from '../../authentication/auth.service'; @Component({ selector: 'app-user-login-form', @@ -14,7 +15,7 @@ export class UserLoginFormComponent implements OnInit { statusMessage: string = ""; - constructor(private userService: UserService, private router: Router) { } + constructor(private userService: UserService, private authService: AuthService, private router: Router) { } ngOnInit(): void { } @@ -46,13 +47,21 @@ export class UserLoginFormComponent implements OnInit { password: this.password, }; - // Adds user to database and changes page afterwards + // Login the user + this.authService.login(request).then(status => { + console.log("User login1: " + JSON.stringify(status)); + this.router.navigateByUrl("/"); + }).catch(error => { + console.log("Error user login: " + error); + }); + /* Old this.userService.login(request).then(status => { - console.log("User login: " + JSON.stringify(status)); + console.log("User login2: " + JSON.stringify(status)); this.router.navigateByUrl("/"); }).catch(error => { console.log("Error adding user: " + error); }); + */ } } diff --git a/client/src/app/users/user-profile/user-profile.component.html b/client/src/app/users/user-profile/user-profile.component.html index fedcb8b611d8a4673ac53b4298a0877c86b35a42..9b8f3504202aab983a5623e14b3db63a95f90819 100644 --- a/client/src/app/users/user-profile/user-profile.component.html +++ b/client/src/app/users/user-profile/user-profile.component.html @@ -1 +1,4 @@ <p>user-profile works!</p> +<p>Userid: {{user.getUserId}}</p> +<p>username: {{user.getUsername}}</p> +<p>email: {{user.getEmail}}</p> \ No newline at end of file diff --git a/client/src/app/users/user-profile/user-profile.component.spec.ts b/client/src/app/users/user-profile/user-profile.component.spec.ts index f0c36edb69f01b01f0236a8681070a3c8dce4027..4855ff37655c5f682abfbb90e35fed5219aa1484 100644 --- a/client/src/app/users/user-profile/user-profile.component.spec.ts +++ b/client/src/app/users/user-profile/user-profile.component.spec.ts @@ -1,4 +1,6 @@ +import { HttpClientTestingModule } from '@angular/common/http/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; import { UserProfileComponent } from './user-profile.component'; @@ -8,7 +10,8 @@ describe('UserProfileComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ UserProfileComponent ] + declarations: [ UserProfileComponent ], + imports: [ HttpClientTestingModule, RouterTestingModule ] }) .compileComponents(); }); diff --git a/client/src/app/users/user-profile/user-profile.component.ts b/client/src/app/users/user-profile/user-profile.component.ts index d7ff7744471c65e7b4e010e463f08f46b0f96d4a..53be4cceb4ffa0990d2be78603cd6f42361c75a3 100644 --- a/client/src/app/users/user-profile/user-profile.component.ts +++ b/client/src/app/users/user-profile/user-profile.component.ts @@ -1,4 +1,9 @@ import { Component, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; +import { AuthService } from 'src/app/authentication/auth.service'; +import { User } from 'src/app/models/user.model'; +import { UserService } from '../user.service'; + @Component({ selector: 'app-user-profile', @@ -7,9 +12,22 @@ import { Component, OnInit } from '@angular/core'; }) export class UserProfileComponent implements OnInit { - constructor() { } + user: User = new User(); + constructor(private userService: UserService, private authService: AuthService, private router: Router) { } ngOnInit(): void { + // Check for token expiration + if (this.authService.checkTokenExpiration()) { // redirects to "/" if token is expired + const token = localStorage.getItem('token'); + // Get user data from JWT token + const user_data = JSON.parse(atob(token.split(".")[1])).data[0]; + // Gets all user information and displays them in the component + this.userService.getUser(user_data.userId).then(user => { + this.user = user; + }).catch (error => { + console.log("Error getting user: " + error); + }); + } + } - } diff --git a/client/src/app/users/user-registration-form/user-registration-form.component.spec.ts b/client/src/app/users/user-registration-form/user-registration-form.component.spec.ts index 142dcbf5fcb915aba5b1c810ba4afd43b6a71d8d..cc0d6286c96ad09899b00e8605843b3670043f26 100644 --- a/client/src/app/users/user-registration-form/user-registration-form.component.spec.ts +++ b/client/src/app/users/user-registration-form/user-registration-form.component.spec.ts @@ -1,4 +1,6 @@ +import { HttpClientTestingModule } from '@angular/common/http/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; import { UserRegistrationFormComponent } from './user-registration-form.component'; @@ -8,7 +10,8 @@ describe('UserRegistrationFormComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ UserRegistrationFormComponent ] + declarations: [ UserRegistrationFormComponent ], + imports: [ HttpClientTestingModule, RouterTestingModule ] }) .compileComponents(); }); diff --git a/server/package-lock.json b/server/package-lock.json index 4908b6d05a1f7ec91518bc06c6a6c292e579b4c4..505bf96189764d4512ec8d93a833b7e99311ab72 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -10,19 +10,24 @@ "dependencies": { "@types/cors": "^2.8.9", "@types/express": "^4.17.11", + "@types/express-jwt": "^6.0.1", "@types/jest": "^26.0.20", "@types/mysql": "^2.15.17", "@types/supertest": "^2.0.10", "body-parser": "^1.19.0", "cors": "^2.8.5", + "dotenv": "^8.2.0", "express": "^4.17.1", + "express-jwt": "^6.0.0", "jest": "^26.6.3", + "jsonwebtoken": "^8.5.1", "mysql": "^2.18.1", "mysql2": "^2.2.5", "supertest": "^6.1.3", "ts-jest": "^26.5.1" }, "devDependencies": { + "@types/jsonwebtoken": "^8.5.0", "nodemon": "^2.0.7", "ts-node": "^9.1.1", "typescript": "^4.1.3" @@ -1014,6 +1019,15 @@ "@types/serve-static": "*" } }, + "node_modules/@types/express-jwt": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@types/express-jwt/-/express-jwt-6.0.1.tgz", + "integrity": "sha512-zB/oXzS8/NTWUzAG343frlqUrsygHPeyYMVcbJ8YYk7rF1G15eUapPgWh0HdeFi51ajFkkUOU+Q540z1Eu4hJQ==", + "dependencies": { + "@types/express": "*", + "@types/express-unless": "*" + } + }, "node_modules/@types/express-serve-static-core": { "version": "4.17.18", "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.18.tgz", @@ -1024,6 +1038,14 @@ "@types/range-parser": "*" } }, + "node_modules/@types/express-unless": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@types/express-unless/-/express-unless-0.5.1.tgz", + "integrity": "sha512-5fuvg7C69lemNgl0+v+CUxDYWVPSfXHhJPst4yTLcqi4zKJpORCxnDrnnilk3k0DTq/WrAUdvXFs01+vUqUZHw==", + "dependencies": { + "@types/express": "*" + } + }, "node_modules/@types/graceful-fs": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", @@ -1062,6 +1084,15 @@ "pretty-format": "^26.0.0" } }, + "node_modules/@types/jsonwebtoken": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-8.5.0.tgz", + "integrity": "sha512-9bVao7LvyorRGZCw0VmH/dr7Og+NdjYSsKAxB43OQoComFbBgsEpoR9JW6+qSq/ogwVBg8GI2MfAlk4SYI4OLg==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/mime": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", @@ -1403,6 +1434,11 @@ "node": ">=0.10.0" } }, + "node_modules/async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -1697,6 +1733,11 @@ "node-int64": "^0.4.0" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" + }, "node_modules/buffer-from": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", @@ -2387,6 +2428,14 @@ "node": ">=8" } }, + "node_modules/dotenv": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz", + "integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==", + "engines": { + "node": ">=8" + } + }, "node_modules/duplexer3": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", @@ -2402,6 +2451,14 @@ "safer-buffer": "^2.1.0" } }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -2744,6 +2801,25 @@ "node": ">= 0.10.0" } }, + "node_modules/express-jwt": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/express-jwt/-/express-jwt-6.0.0.tgz", + "integrity": "sha512-C26y9myRjx7CyhZ+BAT3p+gQyRCoDZ7qo8plCvLDaRT6je6ALIAQknT6XLVQGFKwIy/Ux7lvM2MNap5dt0T7gA==", + "dependencies": { + "async": "^1.5.0", + "express-unless": "^0.3.0", + "jsonwebtoken": "^8.1.0", + "lodash.set": "^4.0.0" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/express-unless": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/express-unless/-/express-unless-0.3.1.tgz", + "integrity": "sha1-JVfBRudb65A+LSR/m1ugFFJpbiA=" + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -4904,6 +4980,32 @@ "node": ">=6" } }, + "node_modules/jsonwebtoken": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", + "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=4", + "npm": ">=1.4.28" + } + }, + "node_modules/jsonwebtoken/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, "node_modules/jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", @@ -4918,6 +5020,25 @@ "verror": "1.10.0" } }, + "node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "node_modules/keyv": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", @@ -4996,6 +5117,46 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" + }, + "node_modules/lodash.set": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz", + "integrity": "sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM=" + }, "node_modules/lodash.sortby": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", @@ -8949,6 +9110,15 @@ "@types/serve-static": "*" } }, + "@types/express-jwt": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@types/express-jwt/-/express-jwt-6.0.1.tgz", + "integrity": "sha512-zB/oXzS8/NTWUzAG343frlqUrsygHPeyYMVcbJ8YYk7rF1G15eUapPgWh0HdeFi51ajFkkUOU+Q540z1Eu4hJQ==", + "requires": { + "@types/express": "*", + "@types/express-unless": "*" + } + }, "@types/express-serve-static-core": { "version": "4.17.18", "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.18.tgz", @@ -8959,6 +9129,14 @@ "@types/range-parser": "*" } }, + "@types/express-unless": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@types/express-unless/-/express-unless-0.5.1.tgz", + "integrity": "sha512-5fuvg7C69lemNgl0+v+CUxDYWVPSfXHhJPst4yTLcqi4zKJpORCxnDrnnilk3k0DTq/WrAUdvXFs01+vUqUZHw==", + "requires": { + "@types/express": "*" + } + }, "@types/graceful-fs": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", @@ -8997,6 +9175,15 @@ "pretty-format": "^26.0.0" } }, + "@types/jsonwebtoken": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-8.5.0.tgz", + "integrity": "sha512-9bVao7LvyorRGZCw0VmH/dr7Og+NdjYSsKAxB43OQoComFbBgsEpoR9JW6+qSq/ogwVBg8GI2MfAlk4SYI4OLg==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/mime": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", @@ -9272,6 +9459,11 @@ "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=" }, + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" + }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -9501,6 +9693,11 @@ "node-int64": "^0.4.0" } }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" + }, "buffer-from": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", @@ -10039,6 +10236,11 @@ "is-obj": "^2.0.0" } }, + "dotenv": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz", + "integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==" + }, "duplexer3": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", @@ -10054,6 +10256,14 @@ "safer-buffer": "^2.1.0" } }, + "ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -10312,6 +10522,22 @@ "vary": "~1.1.2" } }, + "express-jwt": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/express-jwt/-/express-jwt-6.0.0.tgz", + "integrity": "sha512-C26y9myRjx7CyhZ+BAT3p+gQyRCoDZ7qo8plCvLDaRT6je6ALIAQknT6XLVQGFKwIy/Ux7lvM2MNap5dt0T7gA==", + "requires": { + "async": "^1.5.0", + "express-unless": "^0.3.0", + "jsonwebtoken": "^8.1.0", + "lodash.set": "^4.0.0" + } + }, + "express-unless": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/express-unless/-/express-unless-0.3.1.tgz", + "integrity": "sha1-JVfBRudb65A+LSR/m1ugFFJpbiA=" + }, "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -11928,6 +12154,30 @@ "minimist": "^1.2.5" } }, + "jsonwebtoken": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", + "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "requires": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^5.6.0" + }, + "dependencies": { + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } + } + }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", @@ -11939,6 +12189,25 @@ "verror": "1.10.0" } }, + "jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "requires": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "keyv": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", @@ -11999,6 +12268,46 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" }, + "lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" + }, + "lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" + }, + "lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" + }, + "lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" + }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" + }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" + }, + "lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" + }, + "lodash.set": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz", + "integrity": "sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM=" + }, "lodash.sortby": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", diff --git a/server/package.json b/server/package.json index ddee43554ba1ad65283c4c99422e5e6a4cc92e65..96d322e6f3a95aa83ad006b1200ff298c9ba84f4 100644 --- a/server/package.json +++ b/server/package.json @@ -12,19 +12,24 @@ "dependencies": { "@types/cors": "^2.8.9", "@types/express": "^4.17.11", + "@types/express-jwt": "^6.0.1", "@types/jest": "^26.0.20", "@types/mysql": "^2.15.17", "@types/supertest": "^2.0.10", "body-parser": "^1.19.0", "cors": "^2.8.5", + "dotenv": "^8.2.0", "express": "^4.17.1", + "express-jwt": "^6.0.0", "jest": "^26.6.3", + "jsonwebtoken": "^8.5.1", "mysql": "^2.18.1", "mysql2": "^2.2.5", "supertest": "^6.1.3", "ts-jest": "^26.5.1" }, "devDependencies": { + "@types/jsonwebtoken": "^8.5.0", "nodemon": "^2.0.7", "ts-node": "^9.1.1", "typescript": "^4.1.3" diff --git a/server/src/config.ts b/server/src/config.ts index 3ca155ce5e0713f7de4897f7776f914f47815bd8..f2dc97f88fcb1a73d5db5ac435bb2c5d720eb9b0 100644 --- a/server/src/config.ts +++ b/server/src/config.ts @@ -1,6 +1,8 @@ +const { config } = require('dotenv'); +config({ path: __dirname+'/../.env'}); const env = process.env; -const config = { +export default { db: { host: env.DB_HOST || 'mysql.stud.ntnu.no', user: env.DB_USER || 'jonnynl_tdt4140', @@ -12,6 +14,9 @@ const config = { debug: false }, listPerPage: 10, + JWT_KEY : env.JWT_KEY || "", + HOST: env.HOST || "localhost", + PORT: env.HTTPPORT || 3000, + ACCESS_TOKEN_SECRET: env.ACCESS_TOKEN_SECRET, + REFRESH_TOKEN_SECRET: env.REFRESH_TOKEN_SECRET, }; - -export default config; diff --git a/server/src/controllers/userController/index.ts b/server/src/controllers/userController/index.ts index f45e65b84e04f91af4dc713afb020471ef2d36ef..33b925ac4f38ca3feff091493942608d0a840a14 100644 --- a/server/src/controllers/userController/index.ts +++ b/server/src/controllers/userController/index.ts @@ -2,6 +2,9 @@ import { Response, Request } from "express"; import query from '../../services/db_query'; import express from 'express'; import IUser from '../../models/user'; +import * as jwt from 'jsonwebtoken'; +import config from '../../config'; +import authenticateToken from '../../middlewares/auth'; const router = express.Router(); /* ============================= CREATE ============================= */ @@ -35,7 +38,7 @@ router.route('/').get(async (_: Request, response: Response) => { }); // Get user with id `/api/user/:id` -router.route('/:userId').get(async (request: Request, response: Response) => { +router.route('/:userId').get(authenticateToken, async (request: Request, response: Response) => { const userId = request.params.userId; try { const input = `SELECT userId, username, email, create_time FROM user WHERE userId=?;` @@ -50,9 +53,23 @@ router.route('/login').post(async (request: Request, response: Response) => { const {username, password} = request.body; try { const input = "SELECT userId, username, email, create_time FROM user WHERE username=? AND password=?;" - response.status(200).json(await query(input,[username, password])); + const user = await query(input,[username, password]); + // Check if an user object is retrieved + const userObj = Object.values(JSON.parse(JSON.stringify(user.data)))[0]; + if (userObj) { + const jwt_token = jwt.sign({data: user.data}, config.JWT_KEY.replace(/\\n/gm, '\n'), { + algorithm: 'RS256', + expiresIn: 3600*24, // 24 hours + }); + response.status(200).json({ + token: jwt_token, + }); + } else { + response.status(403).send("Invalid combination of username and password given!"); + } } catch (error) { response.status(400).send("Bad Request"); + console.log(error); } }); diff --git a/server/src/index.ts b/server/src/index.ts index d4b49774f337ac7b92acab230b4e8fe2970b442e..77bda9f8885ca4745be8d40879d505b3fb2fe492 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -1,8 +1,10 @@ import app from './app'; +import config from './config'; // REST API config -const port = 3000; +const port = config.PORT; app.listen(port, () => { - console.log(`Listening on port ${port}!`) + const host = config.HOST; + console.log('[*] Server listening at http://%s:%s \n', host, port); }); \ No newline at end of file diff --git a/server/src/middlewares/auth.ts b/server/src/middlewares/auth.ts new file mode 100644 index 0000000000000000000000000000000000000000..023f2683204a0daced88b4c4fc6b9ebfcdf6beeb --- /dev/null +++ b/server/src/middlewares/auth.ts @@ -0,0 +1,10 @@ +import expressJwt from 'express-jwt'; +import config from '../config'; + +const JWT_KEY = config.JWT_KEY.replace(/\\n/gm, '\n'); + +const authenticateToken = expressJwt({ + algorithms: ['RS256'], + secret: JWT_KEY, +}); +export default authenticateToken; \ No newline at end of file