diff --git a/idp/.env.skeleton b/idp/.env.skeleton index fc54d64..4768b0b 100644 --- a/idp/.env.skeleton +++ b/idp/.env.skeleton @@ -11,5 +11,6 @@ IGNORE_PASSWORDS=FALSE # hier kann man sich nur mit dem Username einloggen, nich JWT_SECRET=super-geheimes-secret JWT_EXPIRES_IN=10m JWT_REFRESH_EXPIRES_IN=1w +SESSION_SECRET=MYSESSIONSECRET2024 APPLICATIONPORT=5000 \ No newline at end of file diff --git a/idp/package-lock.json b/idp/package-lock.json index 1a45654..148b650 100644 --- a/idp/package-lock.json +++ b/idp/package-lock.json @@ -21,6 +21,7 @@ "class-transformer": "^0.5.1", "class-validator": "^0.14.1", "dotenv": "^16.4.5", + "express-session": "^1.18.0", "mysql2": "^3.11.0", "nestjs-form-data": "^1.9.91", "passport": "^0.7.0", @@ -36,6 +37,7 @@ "@nestjs/schematics": "^10.0.0", "@nestjs/testing": "^10.0.0", "@types/express": "^4.17.17", + "@types/express-session": "^1.18.0", "@types/jest": "^29.5.2", "@types/node": "^20.3.1", "@types/supertest": "^6.0.0", @@ -2206,6 +2208,15 @@ "@types/send": "*" } }, + "node_modules/@types/express-session": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/@types/express-session/-/express-session-1.18.0.tgz", + "integrity": "sha512-27JdDRgor6PoYlURY+Y5kCakqp5ulC0kmf7y+QwaY+hv9jEFuQOThgkjyA53RP3jmKuBsH5GR6qEfFmvb8mwOA==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, "node_modules/@types/graceful-fs": { "version": "4.1.9", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", @@ -4652,6 +4663,42 @@ "node": ">= 0.10.0" } }, + "node_modules/express-session": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.0.tgz", + "integrity": "sha512-m93QLWr0ju+rOwApSsyso838LQwgfs44QtOP/WBiwtAgPIo/SAh1a5c6nn2BR6mFNZehTpqKDESzP+fRHVbxwQ==", + "dependencies": { + "cookie": "0.6.0", + "cookie-signature": "1.0.7", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-headers": "~1.0.2", + "parseurl": "~1.3.3", + "safe-buffer": "5.2.1", + "uid-safe": "~2.1.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/express-session/node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==" + }, + "node_modules/express-session/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express-session/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, "node_modules/express/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -7293,6 +7340,14 @@ "node": ">= 0.8" } }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -7843,6 +7898,14 @@ } ] }, + "node_modules/random-bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -9405,6 +9468,17 @@ "node": ">=8" } }, + "node_modules/uid-safe": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", + "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", + "dependencies": { + "random-bytes": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/undici-types": { "version": "6.19.8", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", diff --git a/idp/package.json b/idp/package.json index c14d054..04fa6df 100644 --- a/idp/package.json +++ b/idp/package.json @@ -32,6 +32,7 @@ "class-transformer": "^0.5.1", "class-validator": "^0.14.1", "dotenv": "^16.4.5", + "express-session": "^1.18.0", "mysql2": "^3.11.0", "nestjs-form-data": "^1.9.91", "passport": "^0.7.0", @@ -47,6 +48,7 @@ "@nestjs/schematics": "^10.0.0", "@nestjs/testing": "^10.0.0", "@types/express": "^4.17.17", + "@types/express-session": "^1.18.0", "@types/jest": "^29.5.2", "@types/node": "^20.3.1", "@types/supertest": "^6.0.0", diff --git a/idp/src/app.module.ts b/idp/src/app.module.ts index 3246054..9210011 100644 --- a/idp/src/app.module.ts +++ b/idp/src/app.module.ts @@ -1,4 +1,4 @@ -import { Module } from '@nestjs/common'; +import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common'; import { AppService } from './app.service'; import { AuthModule } from './auth/auth.module'; import { TypeOrmModule } from '@nestjs/typeorm'; @@ -6,9 +6,12 @@ import { ConfigModule } from '@nestjs/config'; import { ServeStaticModule } from '@nestjs/serve-static'; import { join } from 'path'; import { LoggerModule } from './core/logger.module'; +import { SessionMiddleware } from './core/session.middleware'; +import { ClientModule } from './client/client.module'; @Module({ imports: [ AuthModule, + ClientModule, ConfigModule.forRoot({ envFilePath: ['.env'], isGlobal: true, @@ -47,4 +50,8 @@ import { LoggerModule } from './core/logger.module'; providers: [AppService], exports: [], }) -export class AppModule {} +export class AppModule implements NestModule { + configure(consumer: MiddlewareConsumer) { + consumer.apply(SessionMiddleware).forRoutes('*'); + } +} diff --git a/idp/src/auth/auth.controller.ts b/idp/src/auth/auth.controller.ts index 0fd90e3..3087f55 100644 --- a/idp/src/auth/auth.controller.ts +++ b/idp/src/auth/auth.controller.ts @@ -1,4 +1,4 @@ -import { Body, Controller, Get, Post, Query } from '@nestjs/common'; +import { Body, Controller, Get, Post, Query, Req } from '@nestjs/common'; import { UsersService } from 'src/users/users.service'; import { ClientService } from 'src/client/client.service'; import { FormDataRequest } from 'nestjs-form-data'; @@ -37,9 +37,11 @@ export class AuthController { @Post('login-with-session-id') async loginWithCode( + @Req() request: Request, @Body('code') code: string, @Body('client_id') clientId: string, ) { + console.log(request['session']); return this.usersService.loginWithSessionKey(code, clientId); } diff --git a/idp/src/core/session.middleware.ts b/idp/src/core/session.middleware.ts new file mode 100644 index 0000000..c6aa276 --- /dev/null +++ b/idp/src/core/session.middleware.ts @@ -0,0 +1,13 @@ +import { Injectable, NestMiddleware } from '@nestjs/common'; +import { Request, Response, NextFunction } from 'express'; + +@Injectable() +export class SessionMiddleware implements NestMiddleware { + use(req: Request, res: Response, next: NextFunction) { + const request = req as any; + request.session.visits = request.session.visits + ? request.session.visits + 1 + : 1; + next(); + } +} diff --git a/idp/src/main.ts b/idp/src/main.ts index d3cabef..8908a46 100644 --- a/idp/src/main.ts +++ b/idp/src/main.ts @@ -2,6 +2,7 @@ import { NestFactory, Reflector } from '@nestjs/core'; import { AppModule } from './app.module'; import { Logger, ValidationPipe } from '@nestjs/common'; import { ClassSerializerInterceptor } from '@nestjs/common'; +import * as session from 'express-session'; async function bootstrap() { const app = await NestFactory.create(AppModule); @@ -12,6 +13,14 @@ async function bootstrap() { app.enableCors(); + app.use( + session({ + secret: process.env.SESSION_SECRET, + resave: false, + saveUninitialized: false, + }), + ); + app.useGlobalPipes( new ValidationPipe({ transform: true, // Transform is recomended configuration for avoind issues with arrays of files transformations diff --git a/idp/src/users/users.service.ts b/idp/src/users/users.service.ts index ff63a9d..4d01ca0 100644 --- a/idp/src/users/users.service.ts +++ b/idp/src/users/users.service.ts @@ -48,7 +48,7 @@ export class UsersService { username: string, password: string, clientId: string, - ): Promise<{ code: string; session_key: string }> { + ): Promise<{ code: string; session_key: string; access?: string }> { const user = await this.userRepo.findByUsername(username); if (!user) { this.logger.error(`User ${username} not found`); @@ -74,7 +74,9 @@ export class UsersService { }); const session = await this.sessionRepo.save(s); - return { code: token.code, session_key: session.id }; + const refresh = this.createAccessToken(user); + + return { code: token.code, session_key: session.id, access: refresh }; } async createAuthToken(user: User, client: Client) { @@ -111,7 +113,9 @@ export class UsersService { throw new HttpException('User is not active', 401); } - const token = this.createAuthToken(user, client); + const token = await this.createAuthToken(user, client); + const refresh = this.createAccessToken(user); + token.user['access'] = refresh; this.logger.log(`User logged in with code on client ${clientId}`); return token; } @@ -182,7 +186,7 @@ export class UsersService { } private createRefreshToken(user: User) { - this.jwtService.sign( + return this.jwtService.sign( { type: 'refresh', id: user.id, diff --git a/idp_client/src/app/app.component.html b/idp_client/src/app/app.component.html index 955af37..90c6b64 100644 --- a/idp_client/src/app/app.component.html +++ b/idp_client/src/app/app.component.html @@ -1,2 +1 @@ - -iodsuj \ No newline at end of file + \ No newline at end of file diff --git a/idp_client/src/app/app.routes.ts b/idp_client/src/app/app.routes.ts index 84377bd..5f7aa2b 100644 --- a/idp_client/src/app/app.routes.ts +++ b/idp_client/src/app/app.routes.ts @@ -1,9 +1,11 @@ import { Routes } from '@angular/router'; import { LoginComponent } from './auth/login/login.component'; import { RegisterComponent } from './auth/register/register.component'; +import { DashboardComponent } from './dashboard/dashboard.component'; export const routes: Routes = [ { path: 'login', component: LoginComponent }, { path: 'register', component: RegisterComponent }, + { path: 'dashboard', component: DashboardComponent }, { path: '', component: LoginComponent }, ]; diff --git a/idp_client/src/app/auth/login/login.component.html b/idp_client/src/app/auth/login/login.component.html index bc30657..0fd9c27 100644 --- a/idp_client/src/app/auth/login/login.component.html +++ b/idp_client/src/app/auth/login/login.component.html @@ -11,7 +11,7 @@ - diff --git a/idp_client/src/app/auth/login/login.component.ts b/idp_client/src/app/auth/login/login.component.ts index 4286e07..f26ff09 100644 --- a/idp_client/src/app/auth/login/login.component.ts +++ b/idp_client/src/app/auth/login/login.component.ts @@ -35,7 +35,7 @@ export class LoginComponent { } loginWithSessionId() { - const id = window.localStorage.getItem("auth_sesion_key"); + const id = window.localStorage.getItem("auth_session_key"); if (!id ||id.length < 2) { return; } this.http.post('api/auth/login-with-session-id', { @@ -50,7 +50,15 @@ export class LoginComponent { ).subscribe({ next: (data) => { if (data["code"] != null) { - location.href = this.redirectUri + "?code=" + data["code"]; + if (this.redirectUri) { + location.href = this.redirectUri + "?code=" + data["code"]; + } else { + if (data['user'] && data['user']['access']) { + sessionStorage.setItem('access', data['user']['access']); + this.router.navigate(['dashboard']) + } + } + } }, error: (error) => { @@ -91,8 +99,15 @@ export class LoginComponent { .subscribe({ next: (data) => { if (data["code"] != null) { - window.localStorage.setItem("auth_sesion_key", data["session_key"]); - location.href = this.redirectUri + "?code=" + data["code"]; + window.localStorage.setItem("auth_session_key", data["session_key"]); + if (this.redirectUri) { + location.href = this.redirectUri + "?code=" + data["code"]; + } else { + if (data['user'] && data['user']['access']) { + sessionStorage.setItem('access', data['user']['access']); + this.router.navigate(['dashboard']) + } + } } }, error: (error) => { diff --git a/idp_client/src/app/dashboard/dashboard.component.html b/idp_client/src/app/dashboard/dashboard.component.html new file mode 100644 index 0000000..9c5fce9 --- /dev/null +++ b/idp_client/src/app/dashboard/dashboard.component.html @@ -0,0 +1 @@ +

dashboard works!

diff --git a/idp_client/src/app/dashboard/dashboard.component.scss b/idp_client/src/app/dashboard/dashboard.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/idp_client/src/app/dashboard/dashboard.component.spec.ts b/idp_client/src/app/dashboard/dashboard.component.spec.ts new file mode 100644 index 0000000..30e39a2 --- /dev/null +++ b/idp_client/src/app/dashboard/dashboard.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { DashboardComponent } from './dashboard.component'; + +describe('DashboardComponent', () => { + let component: DashboardComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [DashboardComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(DashboardComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/idp_client/src/app/dashboard/dashboard.component.ts b/idp_client/src/app/dashboard/dashboard.component.ts new file mode 100644 index 0000000..ebbda56 --- /dev/null +++ b/idp_client/src/app/dashboard/dashboard.component.ts @@ -0,0 +1,52 @@ +import { HttpClient } from '@angular/common/http'; +import { Component, inject, OnInit } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { User } from '../model/user.interface'; + +@Component({ + selector: 'app-dashboard', + standalone: true, + imports: [], + templateUrl: './dashboard.component.html', + styleUrl: './dashboard.component.scss' +}) +export class DashboardComponent implements OnInit { + private router: Router = inject(Router); + private http: HttpClient = inject(HttpClient); + private user: User; + + accessToken: string; + + + ngOnInit(): void { + + const refresh = sessionStorage.getItem('access'); + if (!refresh || refresh.length == 0) { + this.router.navigate(['login']); + } else { + this.accessToken = refresh; + this.refreshToken(refresh); + this.getClients(); + } + + } + + refreshToken(token: string) { + this.http.post('api/auth/verify', { access_token: token}).subscribe({ + next: res => { + console.log(res) + + } + }) + } + + + getClients() { + this.http.get('api/client', {headers: { auth: 'Bearer ' + this.accessToken}}).subscribe({ + next: result => { + console.log(result); + } + }) + } + +} diff --git a/idp_client/src/app/model/user.interface.ts b/idp_client/src/app/model/user.interface.ts new file mode 100644 index 0000000..85f3604 --- /dev/null +++ b/idp_client/src/app/model/user.interface.ts @@ -0,0 +1,7 @@ +export interface User { + id: string; + username: string; + firstName: string; + lastName: string; + createdAt: string; +} \ No newline at end of file