app login

This commit is contained in:
Bastian Wagner
2024-09-05 11:46:15 +02:00
parent afeea2f0f2
commit 767e3cbb41
19 changed files with 315 additions and 67 deletions

View File

@@ -8,6 +8,7 @@ import { join } from 'path';
import { LoggerModule } from './core/logger.module';
import { SessionMiddleware } from './core/session.middleware';
import { ClientModule } from './client/client.module';
import { ApplicationModule } from './application/application.module';
@Module({
imports: [
AuthModule,
@@ -35,6 +36,7 @@ import { ClientModule } from './client/client.module';
retryDelay: 10000,
}),
}),
ApplicationModule,
// TypeOrmModule.forRoot({
// type: 'mysql',
// host: '85.215.137.185', // MySQL Hostname

View File

@@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { ApplicationController } from './application.controller';
describe('ApplicationController', () => {
let controller: ApplicationController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [ApplicationController],
}).compile();
controller = module.get<ApplicationController>(ApplicationController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});

View File

@@ -0,0 +1,22 @@
import { Body, Controller, Get, Post } from '@nestjs/common';
import { LoginUserDto } from 'src/model/dto';
import { User } from 'src/model/user.entity';
import { UsersService } from 'src/users/users.service';
@Controller('app')
export class ApplicationController {
constructor(private userService: UsersService) {}
@Get()
getIt() {
console.log('HERE');
return { success: true };
}
@Post('login')
loginUser(@Body() b: LoginUserDto): Promise<User> {
return this.userService.loginUser({
username: b.username,
password: b.password,
});
}
}

View File

@@ -0,0 +1,12 @@
import { Module } from '@nestjs/common';
import { ApplicationController } from './application.controller';
import { UserModule } from './user/user.module';
import { LoggerModule } from 'src/core/logger.module';
import { SecureModule } from 'src/core/secure/secure.module';
@Module({
controllers: [ApplicationController],
providers: [],
imports: [LoggerModule, UserModule, SecureModule],
})
export class ApplicationModule {}

View File

@@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { UserController } from './user.controller';
describe('UserController', () => {
let controller: UserController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [UserController],
}).compile();
controller = module.get<UserController>(UserController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});

View File

@@ -0,0 +1,11 @@
import { Controller, Get, UseGuards } from '@nestjs/common';
import { AuthGuard } from 'src/core/auth.guard';
@UseGuards(AuthGuard)
@Controller('app/user')
export class UserController {
@Get()
getIt() {
return 'secure';
}
}

View File

@@ -0,0 +1,9 @@
import { Module } from '@nestjs/common';
import { UserController } from './user.controller';
import { SecureModule } from 'src/core/secure/secure.module';
@Module({
controllers: [UserController],
imports: [SecureModule],
})
export class UserModule {}

View File

@@ -1,4 +1,4 @@
import { Body, Controller, Get, Post, Query, Req } from '@nestjs/common';
import { Body, Controller, Get, Post, Query } from '@nestjs/common';
import { UsersService } from 'src/users/users.service';
import { ClientService } from 'src/client/client.service';
import { FormDataRequest } from 'nestjs-form-data';
@@ -37,14 +37,17 @@ 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);
}
@Post('login-with-session-id/userlogin')
async loginUserWithCode(@Body('code') code: string) {
return this.usersService.loginWithSessionKey(code, null, true);
}
@Get()
async getClient(
@Query('client_id') clientId: string,

View File

@@ -0,0 +1,47 @@
import {
Injectable,
CanActivate,
ExecutionContext,
UnauthorizedException,
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { JwtService } from '@nestjs/jwt';
import { UsersService } from 'src/users/users.service';
@Injectable()
export class AuthGuard implements CanActivate {
constructor(
private readonly jwtService: JwtService,
private reflector: Reflector,
private readonly userService: UsersService,
) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const authHeader = request.headers['authorization'];
if (!authHeader || !authHeader.startsWith('Bearer ')) {
throw new UnauthorizedException(
'Authorization header is missing or invalid',
);
}
const token = authHeader.split(' ')[1];
try {
// Token validieren
const decoded = this.jwtService.verify(token);
const user = await this.userService.getUserById(decoded.id);
if (!user) {
throw new UnauthorizedException('Verification not successfull');
}
request.user = user;
return true;
} catch (error) {
console.log(error);
throw new UnauthorizedException(error.message);
}
}
}

View File

@@ -0,0 +1,50 @@
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { JwtModule } from '@nestjs/jwt';
import { TypeOrmModule } from '@nestjs/typeorm';
import { NestjsFormDataModule } from 'nestjs-form-data';
import { ClientService } from 'src/client/client.service';
import {
AuthorizationCode,
AuthorizationCodeRepository,
} from 'src/model/auth-code.entity';
import { Client, ClientRepository } from 'src/model/client.entity';
import { RedirectUri } from 'src/model/redirect-uri.entity';
import { SessionKey, SessionKeyRepository } from 'src/model/session-key.entity';
import { User, UserRepository } from 'src/model/user.entity';
import { UsersService } from 'src/users/users.service';
import { AuthGuard } from '../auth.guard';
import { LoggerModule } from '../logger.module';
@Module({
imports: [
JwtModule.registerAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: async (config: ConfigService) => ({
secret: config.get('JWT_SECRET'),
signOptions: { expiresIn: config.get('JWT_EXPIRES_IN') },
}),
}),
NestjsFormDataModule,
TypeOrmModule.forFeature([
User,
Client,
RedirectUri,
AuthorizationCode,
SessionKey,
]),
LoggerModule,
],
providers: [
UsersService,
ClientService,
UserRepository,
ClientRepository,
AuthorizationCodeRepository,
SessionKeyRepository,
AuthGuard,
],
exports: [JwtModule, UsersService, AuthGuard],
})
export class SecureModule {}

View File

@@ -0,0 +1,2 @@
export * from './create-user.dto';
export * from './login-user.dto';

View File

@@ -0,0 +1,4 @@
export interface LoginUserDto {
username: string;
password: string;
}

View File

@@ -44,6 +44,9 @@ export class User {
@Exclude()
@OneToMany(() => SessionKey, (key) => key.user)
sessionKeys: SessionKey[];
accessToken?: string;
refreshToken?: string;
}
@Injectable()

View File

@@ -12,6 +12,7 @@ import { CustomLogger } from 'src/core/custom.logger';
import { CreateUserDto } from 'src/model/dto/create-user.dto';
import { SessionKeyRepository } from 'src/model/session-key.entity';
import { Client } from 'src/model/client.entity';
import { LoginUserDto } from 'src/model/dto';
@Injectable()
export class UsersService {
@@ -48,7 +49,7 @@ export class UsersService {
username: string,
password: string,
clientId: string,
): Promise<{ code: string; session_key: string; access?: string }> {
): Promise<{ code: string; session_key: string }> {
const user = await this.userRepo.findByUsername(username);
if (!user) {
this.logger.error(`User ${username} not found`);
@@ -74,9 +75,7 @@ export class UsersService {
});
const session = await this.sessionRepo.save(s);
const refresh = this.createAccessToken(user);
return { code: token.code, session_key: session.id, access: refresh };
return { code: token.code, session_key: session.id };
}
async createAuthToken(user: User, client: Client) {
@@ -90,7 +89,11 @@ export class UsersService {
return token;
}
async loginWithSessionKey(sessionKey: string, clientId: string) {
async loginWithSessionKey(
sessionKey: string,
clientId: string,
getUserAccessToken = false,
) {
const client = await this.clientService.getClientById(clientId);
if (!client) {
@@ -113,9 +116,13 @@ export class UsersService {
throw new HttpException('User is not active', 401);
}
if (getUserAccessToken) {
user.accessToken = this.createAccessToken(user);
user.refreshToken = this.createRefreshToken(user);
return user;
}
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;
}
@@ -225,4 +232,28 @@ export class UsersService {
throw new HttpException(e.message, 401);
}
}
getUserById(id: string): Promise<User> {
return this.userRepo.findById(id);
}
async loginUser({ username, password }: LoginUserDto): Promise<User> {
const user = await this.userRepo.findByUsername(username);
if (!user) {
this.logger.error(`User ${username} not found`);
throw new HttpException('Invalid credentials', 401);
}
const valid = await bcrypt.compare(password, user.password);
if (!valid) {
this.logger.error(`User ${username} provided invalid credentials`);
throw new HttpException('Invalid credentials', 401);
}
const access = this.createAccessToken(user);
const refresh = this.createRefreshToken(user);
user.accessToken = access;
user.refreshToken = refresh;
return user;
}
}

View File

@@ -4,6 +4,8 @@ import { ActivatedRoute, Router } from '@angular/router';
import { CommonModule } from '@angular/common';
import { FormControl, FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms';
import { HotToastService } from '@ngxpert/hot-toast';
import { UserService } from '../user.service';
import { User } from '../../model/user.interface';
@Component({
selector: 'app-login',
@@ -17,6 +19,7 @@ export class LoginComponent {
private route: ActivatedRoute = inject(ActivatedRoute);
private toast: HotToastService = inject(HotToastService);
private router: Router = inject(Router);
private userService: UserService = inject(UserService);
redirectUri = null;
client: string = "";
@@ -38,7 +41,9 @@ export class LoginComponent {
const id = window.localStorage.getItem("auth_session_key");
if (!id ||id.length < 2) { return; }
this.http.post('api/auth/login-with-session-id', {
const url = this.client_id ? 'api/auth/login-with-session-id' : 'api/auth/login-with-session-id/userlogin'
this.http.post(url, {
code: id,
client_id: this.client_id
}).pipe(
@@ -49,17 +54,7 @@ export class LoginComponent {
})
).subscribe({
next: (data) => {
if (data["code"] != null) {
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'])
}
}
}
this.handleLoginData(data);
},
error: (error) => {
console.error(error);
@@ -67,12 +62,28 @@ export class LoginComponent {
});
}
private handleLoginData(data: any) {
if (data["code"] != null) {
if (this.redirectUri) {
location.href = this.redirectUri + "?code=" + data["code"];
}
} else if (data["id"] != null) {
this.userService.user = data as User;
this.navigateToDashboard();
}
}
private navigateToDashboard() {
this.router.navigateByUrl("/dashboard");
}
getclient() {
const params = (this.route.snapshot.queryParamMap as any)["params"];
this.redirectUri = params.redirect_uri;
this.client_id = params.client_id;
if (!this.client_id) { return; }
this.http.get<any>('api/auth/', {
params
}).subscribe({
@@ -88,7 +99,9 @@ export class LoginComponent {
login() {
this.isLoading = true;
this.http.post('api/auth/login?'+ 'client_id=' + this.client_id, this.loginForm.value).
const url = this.client_id ? `api/auth/login?client_id=${this.client_id}` : 'api/app/login';
console.log(url, this.client_id)
this.http.post(url, this.loginForm.value).
pipe(
this.toast.observe({
loading: 'Logging in...',
@@ -98,17 +111,7 @@ export class LoginComponent {
)
.subscribe({
next: (data) => {
if (data["code"] != null) {
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'])
}
}
}
this.handleLoginData(data);
},
error: (error) => {
console.error(error);

View File

@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { UserService } from './user.service';
describe('UserService', () => {
let service: UserService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(UserService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View File

@@ -0,0 +1,21 @@
import { Injectable } from '@angular/core';
import { User } from '../model/user.interface';
@Injectable({
providedIn: 'root'
})
export class UserService {
private _user: User;
constructor() { }
set user(user: User) {
this._user = user;
}
get user(): User {
return this._user;
}
}

View File

@@ -1,7 +1,6 @@
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';
import { UserService } from '../auth/user.service';
import { Router } from '@angular/router';
@Component({
selector: 'app-dashboard',
@@ -11,42 +10,17 @@ import { User } from '../model/user.interface';
styleUrl: './dashboard.component.scss'
})
export class DashboardComponent implements OnInit {
private router: Router = inject(Router);
private http: HttpClient = inject(HttpClient);
private user: User;
accessToken: string;
private userService: UserService = inject(UserService);
private router: Router = inject(Router);
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();
if (!this.userService.user) {
this.router.navigateByUrl("/login");
}
}
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);
}
})
}
}

View File

@@ -4,4 +4,6 @@ export interface User {
firstName: string;
lastName: string;
createdAt: string;
accessToken: string;
refreshToken: string;
}