This commit is contained in:
Bastian Wagner
2024-09-12 21:33:11 +02:00
parent 6abfdcb632
commit c00aad559d
36 changed files with 1118 additions and 397 deletions

View File

@@ -0,0 +1,6 @@
import { IsNotEmpty } from 'class-validator';
export class AuthCodeDto {
@IsNotEmpty()
code: string;
}

View File

@@ -0,0 +1,9 @@
import { IsEmail, IsNotEmpty } from 'class-validator';
export class CreateUserDto {
@IsEmail()
username: string;
@IsNotEmpty()
externalId: string;
}

View File

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

View File

@@ -0,0 +1,9 @@
import { IsEmail, IsNotEmpty } from 'class-validator';
export class LoginDTO {
@IsEmail()
username: string;
@IsNotEmpty()
password: string;
}

View File

@@ -0,0 +1,2 @@
export * from './sso.user.entity';
export * from './user.entity';

View File

@@ -0,0 +1,18 @@
import { Column, Entity, JoinColumn, OneToOne, PrimaryColumn } from 'typeorm';
import { User } from './user.entity';
@Entity()
export class SSOUser {
@PrimaryColumn({ type: 'uuid', unique: true })
externalId: string;
@OneToOne(() => User, (user) => user.external)
@JoinColumn()
user: User;
@Column({ nullable: true, type: 'text' })
accessToken: string;
@Column({ nullable: true, type: 'text' })
refreshToken: string;
}

View File

@@ -0,0 +1,44 @@
import { Exclude } from 'class-transformer';
import {
Column,
CreateDateColumn,
Entity,
OneToOne,
PrimaryGeneratedColumn,
} from 'typeorm';
import { IUser } from '../interface';
import { SSOUser } from './sso.user.entity';
import { IsEmail } from 'class-validator';
@Entity()
export class User implements IUser {
@PrimaryGeneratedColumn('uuid')
id: string;
@IsEmail()
@Column({ unique: true })
username: string;
@Column({ name: 'first_name', default: '' })
firstName: string;
@Column({ name: 'last_name', default: '' })
lastName: string;
@CreateDateColumn({ name: 'created_at' })
createdAt: Date;
@Column({ default: null })
lastLogin: Date;
@Exclude()
@OneToOne(() => SSOUser, (sso) => sso.user, { eager: true, cascade: true })
external: SSOUser;
@Exclude()
@Column({ default: true })
isActive: boolean;
accessToken?: string;
refreshToken?: string;
}

View File

@@ -0,0 +1,10 @@
export interface IExternalAccessPayload {
username: string;
id: string;
firstName: string;
lastName: string;
iss: string;
aud: string;
iat: number;
exp: number;
}

View File

@@ -0,0 +1,3 @@
export * from './user.interface';
export * from './external-access-token.payload.interface';
export * from './payload.interface';

View File

@@ -0,0 +1,5 @@
export interface IPayload {
id: string;
username: string;
type: 'access' | 'refresh';
}

View File

@@ -0,0 +1,10 @@
export interface IUser {
id: string;
username: string;
firstName: string;
lastName: string;
accessToken?: string;
refreshToken?: string;
}

View File

@@ -0,0 +1,2 @@
export * from './user.repository';
export * from './ssouser.repository';

View File

@@ -0,0 +1,18 @@
import { Injectable } from '@nestjs/common';
import { Repository, DataSource } from 'typeorm';
import { SSOUser } from '../entitites';
@Injectable()
export class SsoUserRepository extends Repository<SSOUser> {
constructor(dataSource: DataSource) {
super(SSOUser, dataSource.createEntityManager());
}
findOneByUserId(id: string): Promise<SSOUser> {
return this.findOne({ where: { user: { id: id } } });
}
findByExternalId(id: string): Promise<SSOUser> {
return this.findOne({ where: { externalId: id } });
}
}

View File

@@ -0,0 +1,61 @@
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { Repository, DataSource } from 'typeorm';
import { User } from '../entitites';
import { CreateUserDto } from '../dto/create-user.dto';
import { SsoUserRepository } from './ssouser.repository';
@Injectable()
export class UserRepository extends Repository<User> {
constructor(
dataSource: DataSource,
private ssoRepo: SsoUserRepository,
) {
super(User, dataSource.createEntityManager());
}
findByUsername(username: string): Promise<User> {
return this.findOne({ where: { username }, relations: ['external'] });
}
findById(id: string): Promise<User> {
return this.findOne({ where: { id }, relations: ['external'] });
}
async createUser(createUserDto: CreateUserDto): Promise<User> {
if (
!(await this.checkIfCanInserted(
createUserDto.username,
createUserDto.externalId,
))
) {
throw new HttpException('user_exists', HttpStatus.UNPROCESSABLE_ENTITY);
}
try {
const created = this.create(createUserDto);
const sso = this.ssoRepo.create({
user: created,
externalId: createUserDto.externalId,
});
created.external = sso;
const user = await this.save(created);
sso.user = user;
this.ssoRepo.save(sso);
return user;
} catch {
throw new HttpException(
'not_successfull',
HttpStatus.UNPROCESSABLE_ENTITY,
);
}
}
private async checkIfCanInserted(
username: string,
externalId: string,
): Promise<boolean> {
const user = await this.findOneBy({ username });
const sso = await this.ssoRepo.findByExternalId(externalId);
return user == null && sso == null;
}
}