add Documentation and start with tests
This commit is contained in:
parent
264da24033
commit
9ed6c497c0
20
README.md
20
README.md
@ -57,15 +57,14 @@ const passwordModule = PasswordHasherModule.forRoot();
|
|||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
ConfigModule.forRoot(),
|
ConfigModule.forRoot(),
|
||||||
emailVerificationModule,
|
EmailVerificationModule.forRoot(),
|
||||||
sessionModule,
|
InMemorySessionsModule.forRoot(),
|
||||||
jwtModule,
|
JwtGeneratorModule.forRoot(),
|
||||||
passwordModule,
|
PasswordHasherModule.forRoot(),
|
||||||
TokenAuthenticationModule.forRoot([
|
TokenAuthenticationModule.forRoot(),
|
||||||
...takeExportedProviders(emailVerificationModule),
|
],
|
||||||
...takeExportedProviders(sessionModule),
|
controllers: [LoginController, LogoutController],
|
||||||
...takeExportedProviders(jwtModule),
|
providers: [
|
||||||
...takeExportedProviders(passwordModule),
|
|
||||||
// register the Repositories with InMemoryRepository
|
// register the Repositories with InMemoryRepository
|
||||||
{
|
{
|
||||||
provide: "@apihub24/organization_repository",
|
provide: "@apihub24/organization_repository",
|
||||||
@ -83,10 +82,7 @@ const passwordModule = PasswordHasherModule.forRoot();
|
|||||||
provide: "@apihub24/right_repository",
|
provide: "@apihub24/right_repository",
|
||||||
useClass: InMemoryRepository<Right>,
|
useClass: InMemoryRepository<Right>,
|
||||||
},
|
},
|
||||||
]),
|
|
||||||
],
|
],
|
||||||
controllers: [LoginController, LogoutController],
|
|
||||||
providers: [],
|
|
||||||
// required exports for the TokenGuard
|
// required exports for the TokenGuard
|
||||||
exports: [TokenAuthenticationModule, jwtModule],
|
exports: [TokenAuthenticationModule, jwtModule],
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,11 +1,32 @@
|
|||||||
import { IGroup } from "./group";
|
import { IGroup } from "./group";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* represent the Users Account
|
||||||
|
*/
|
||||||
export interface IAccount {
|
export interface IAccount {
|
||||||
id: string;
|
id: string;
|
||||||
|
/**
|
||||||
|
* the Accounts name
|
||||||
|
*/
|
||||||
accountName: string;
|
accountName: string;
|
||||||
|
/**
|
||||||
|
* the hashed Password
|
||||||
|
*/
|
||||||
passwordHash: string;
|
passwordHash: string;
|
||||||
|
/**
|
||||||
|
* a E-Mail Address for this Account
|
||||||
|
*/
|
||||||
email: string;
|
email: string;
|
||||||
|
/**
|
||||||
|
* represents if the E-Mail Address was verified
|
||||||
|
*/
|
||||||
emailVerified: boolean;
|
emailVerified: boolean;
|
||||||
|
/**
|
||||||
|
* represents if the User was activated for Login
|
||||||
|
*/
|
||||||
active: boolean;
|
active: boolean;
|
||||||
|
/**
|
||||||
|
* the Groups this User was member
|
||||||
|
*/
|
||||||
groups: IGroup[];
|
groups: IGroup[];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,21 @@
|
|||||||
import { IOrganization } from "./organization";
|
import { IOrganization } from "./organization";
|
||||||
import { IRight } from "./right";
|
import { IRight } from "./right";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* represents a Group that a User can Join
|
||||||
|
*/
|
||||||
export interface IGroup {
|
export interface IGroup {
|
||||||
id: string;
|
id: string;
|
||||||
|
/**
|
||||||
|
* the Name of the Group
|
||||||
|
*/
|
||||||
name: string;
|
name: string;
|
||||||
|
/**
|
||||||
|
* the Organization in that the Group are
|
||||||
|
*/
|
||||||
organization: IOrganization;
|
organization: IOrganization;
|
||||||
|
/**
|
||||||
|
* the Rights that have the Group
|
||||||
|
*/
|
||||||
rights: IRight[];
|
rights: IRight[];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,10 @@
|
|||||||
|
/**
|
||||||
|
* represent a Organization Unit
|
||||||
|
*/
|
||||||
export interface IOrganization {
|
export interface IOrganization {
|
||||||
id: string;
|
id: string;
|
||||||
|
/**
|
||||||
|
* the Name of the Organization
|
||||||
|
*/
|
||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* represents Registration Data
|
||||||
|
*/
|
||||||
export interface IRegistration {
|
export interface IRegistration {
|
||||||
|
/**
|
||||||
|
* the Account Name to register
|
||||||
|
*/
|
||||||
accountName: string;
|
accountName: string;
|
||||||
|
/**
|
||||||
|
* the Password to register
|
||||||
|
*/
|
||||||
password: string;
|
password: string;
|
||||||
|
/**
|
||||||
|
* the repeated Password
|
||||||
|
*/
|
||||||
passwordComparer: string;
|
passwordComparer: string;
|
||||||
|
/**
|
||||||
|
* the E-Mail Address to register
|
||||||
|
*/
|
||||||
email: string;
|
email: string;
|
||||||
|
/**
|
||||||
|
* the Group Ids the Account must be Part
|
||||||
|
*/
|
||||||
groupIds: string[];
|
groupIds: string[];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,10 @@
|
|||||||
|
/**
|
||||||
|
* represent a Application Right
|
||||||
|
*/
|
||||||
export interface IRight {
|
export interface IRight {
|
||||||
id: string;
|
id: string;
|
||||||
|
/**
|
||||||
|
* the Right Name
|
||||||
|
*/
|
||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,16 @@
|
|||||||
import { IAccount } from "./account";
|
import { IAccount } from "./account";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* represents a Session
|
||||||
|
*/
|
||||||
export interface ISession {
|
export interface ISession {
|
||||||
id: string;
|
id: string;
|
||||||
|
/**
|
||||||
|
* the Account of the Session
|
||||||
|
*/
|
||||||
account: IAccount;
|
account: IAccount;
|
||||||
|
/**
|
||||||
|
* some MetaData
|
||||||
|
*/
|
||||||
metaData: Record<string, any>;
|
metaData: Record<string, any>;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,13 @@
|
|||||||
|
/**
|
||||||
|
* represent a Sign In Request
|
||||||
|
*/
|
||||||
export interface ISignIn {
|
export interface ISignIn {
|
||||||
|
/**
|
||||||
|
* the Account Name to sign in
|
||||||
|
*/
|
||||||
accountName: string;
|
accountName: string;
|
||||||
|
/**
|
||||||
|
* the Password to sign in
|
||||||
|
*/
|
||||||
password: string;
|
password: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,25 @@
|
|||||||
import { IAccount } from "../models/account";
|
import { IAccount } from "../models/account";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @interface IMailVerificationService
|
||||||
|
* @description Defines the contract for a service that handles email verification,
|
||||||
|
* including sending verification emails and validating verification codes.
|
||||||
|
*/
|
||||||
export interface IMailVerificationService {
|
export interface IMailVerificationService {
|
||||||
|
/**
|
||||||
|
* Sends a verification email to a specified account.
|
||||||
|
* The implementation should generate a unique verification code and send it to the account's email address.
|
||||||
|
* @param account The `IAccount` object to which the verification email should be sent.
|
||||||
|
* @returns A promise that resolves to `void` upon successful sending of the email.
|
||||||
|
*/
|
||||||
sendVerificationMail(account: IAccount): Promise<void>;
|
sendVerificationMail(account: IAccount): Promise<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies a given code against a user's email address.
|
||||||
|
* This method checks if the provided code matches the one sent to the specified email address.
|
||||||
|
* @param email The email address to verify.
|
||||||
|
* @param code The verification code submitted by the user.
|
||||||
|
* @returns A promise that resolves to `true` if the code is valid, otherwise `false`.
|
||||||
|
*/
|
||||||
verify(email: string, code: string): Promise<boolean>;
|
verify(email: string, code: string): Promise<boolean>;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* @interface IPasswordService
|
||||||
|
* @description Defines the contract for a service that handles secure password operations,
|
||||||
|
* including hashing and verification.
|
||||||
|
*/
|
||||||
export interface IPasswordService {
|
export interface IPasswordService {
|
||||||
|
/**
|
||||||
|
* Hashes a plain-text password to a secure, one-way hash.
|
||||||
|
* This method should be used to store passwords securely in a database.
|
||||||
|
* @param plainTextPassword The password string in plain text to be hashed.
|
||||||
|
* @returns A promise that resolves to the hashed password string.
|
||||||
|
*/
|
||||||
hash(plainTextPassword: string): Promise<string>;
|
hash(plainTextPassword: string): Promise<string>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies a plain-text password against a stored password hash.
|
||||||
|
* This method is crucial for user authentication, ensuring the entered password
|
||||||
|
* matches the stored hash without needing to store the plain-text version.
|
||||||
|
* @param plainTextPassword The password string in plain text provided by the user.
|
||||||
|
* @param passwordHash The stored, hashed password string to compare against.
|
||||||
|
* @returns A promise that resolves to `true` if the plain-text password matches the hash, otherwise `false`.
|
||||||
|
*/
|
||||||
verify(plainTextPassword: string, passwordHash: string): Promise<boolean>;
|
verify(plainTextPassword: string, passwordHash: string): Promise<boolean>;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,37 @@
|
|||||||
import { IAccount } from "../models/account";
|
import { IAccount } from "../models/account";
|
||||||
import { ISession } from "../models/session";
|
import { ISession } from "../models/session";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @interface ISessionService
|
||||||
|
* @description Defines the contract for a service that manages user sessions.
|
||||||
|
* It provides methods for creating, retrieving, and removing sessions.
|
||||||
|
*/
|
||||||
export interface ISessionService {
|
export interface ISessionService {
|
||||||
|
/**
|
||||||
|
* Creates a new session for a given account.
|
||||||
|
* @param account The `IAccount` object for which to create the session.
|
||||||
|
* @returns A promise that resolves to the newly created `ISession` object.
|
||||||
|
*/
|
||||||
create(account: IAccount): Promise<ISession>;
|
create(account: IAccount): Promise<ISession>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves sessions that match a given filter.
|
||||||
|
* @param filter A function that takes an `IAccount` object and returns a boolean, serving as the condition for selecting sessions.
|
||||||
|
* @returns A promise that resolves to an array of `ISession` objects.
|
||||||
|
*/
|
||||||
getBy(filter: (account: IAccount) => boolean): Promise<ISession[]>;
|
getBy(filter: (account: IAccount) => boolean): Promise<ISession[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves a single session by its unique identifier.
|
||||||
|
* @param sessionId The ID of the session to retrieve.
|
||||||
|
* @returns A promise that resolves to the `ISession` object, or `null` if no session is found.
|
||||||
|
*/
|
||||||
getById(sessionId: string): Promise<ISession | null>;
|
getById(sessionId: string): Promise<ISession | null>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a session based on its unique identifier.
|
||||||
|
* @param sessionId The ID of the session to remove.
|
||||||
|
* @returns A promise that resolves to `void` after the session has been removed.
|
||||||
|
*/
|
||||||
remove(sessionId: string): Promise<void>;
|
remove(sessionId: string): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,13 +3,38 @@ import { ISession } from "../models/session";
|
|||||||
|
|
||||||
export type Algorithm = "HS256" | "HS384" | "HS512";
|
export type Algorithm = "HS256" | "HS384" | "HS512";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @interface ITokenService
|
||||||
|
* @description Defines the contract for a service that handles token-related operations,
|
||||||
|
* such as generation, validation, and extraction of account information from tokens.
|
||||||
|
*/
|
||||||
export interface ITokenService {
|
export interface ITokenService {
|
||||||
|
/**
|
||||||
|
* Generates a new JWT token for a given session and subject.
|
||||||
|
* @param session The session object containing data to be included in the token payload.
|
||||||
|
* @param subject The subject of the token, typically the account name or ID.
|
||||||
|
* @param expires Optional string specifying the token's expiration duration (e.g., '1h', '30m').
|
||||||
|
* @param algorithm Optional string specifying the signing algorithm (e.g., 'HS256', 'RS512').
|
||||||
|
* @returns A promise that resolves to the generated token string.
|
||||||
|
*/
|
||||||
generate(
|
generate(
|
||||||
session: ISession,
|
session: ISession,
|
||||||
subject: string,
|
subject: string,
|
||||||
expires?: string,
|
expires?: string,
|
||||||
algorithm?: Algorithm
|
algorithm?: Algorithm
|
||||||
): Promise<string>;
|
): Promise<string>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates a given token to ensure it is authentic and not expired.
|
||||||
|
* @param token The token string to be validated.
|
||||||
|
* @returns A promise that resolves to `true` if the token is valid, otherwise `false`.
|
||||||
|
*/
|
||||||
validate(token: string): Promise<boolean>;
|
validate(token: string): Promise<boolean>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts and retrieves the account associated with a given token.
|
||||||
|
* @param token The token string from which to extract account information.
|
||||||
|
* @returns A promise that resolves to the `IAccount` object or `null` if the token is invalid or no account is found.
|
||||||
|
*/
|
||||||
getAccount(token: string): Promise<IAccount | null>;
|
getAccount(token: string): Promise<IAccount | null>;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,9 @@
|
|||||||
import { SetMetadata } from "@nestjs/common";
|
import { SetMetadata } from "@nestjs/common";
|
||||||
|
|
||||||
export const ORGANIZATIONS_KEY = "organizations";
|
export const ORGANIZATIONS_KEY = "organizations";
|
||||||
|
/**
|
||||||
|
* register some Organizations for the TokenGuard that are required for this Operation
|
||||||
|
* @param organizations some Organization Names that are required
|
||||||
|
*/
|
||||||
export const Organizations = (...organizations: string[]) =>
|
export const Organizations = (...organizations: string[]) =>
|
||||||
SetMetadata(ORGANIZATIONS_KEY, organizations);
|
SetMetadata(ORGANIZATIONS_KEY, organizations);
|
||||||
|
|||||||
@ -1,4 +1,8 @@
|
|||||||
import { SetMetadata } from "@nestjs/common";
|
import { SetMetadata } from "@nestjs/common";
|
||||||
|
|
||||||
export const RIGHTS_KEY = "rights";
|
export const RIGHTS_KEY = "rights";
|
||||||
|
/**
|
||||||
|
* register some Right for the TokenGuard that are required for this Operation
|
||||||
|
* @param rights some Right Names that are required
|
||||||
|
*/
|
||||||
export const Rights = (...rights: string[]) => SetMetadata(RIGHTS_KEY, rights);
|
export const Rights = (...rights: string[]) => SetMetadata(RIGHTS_KEY, rights);
|
||||||
|
|||||||
@ -1,4 +1,8 @@
|
|||||||
import { SetMetadata } from "@nestjs/common";
|
import { SetMetadata } from "@nestjs/common";
|
||||||
|
|
||||||
export const ROLES_KEY = "roles";
|
export const ROLES_KEY = "roles";
|
||||||
|
/**
|
||||||
|
* register some Roles for the TokenGuard that are required for this Operation
|
||||||
|
* @param roles some Role Names that are required
|
||||||
|
*/
|
||||||
export const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles);
|
export const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles);
|
||||||
|
|||||||
@ -10,14 +10,29 @@ import { ROLES_KEY } from "./roles.decorator";
|
|||||||
import * as tokenService from "../contracts/services/token.service";
|
import * as tokenService from "../contracts/services/token.service";
|
||||||
import { ORGANIZATIONS_KEY } from "./organization.decorator";
|
import { ORGANIZATIONS_KEY } from "./organization.decorator";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `TokenGuard` is a NestJS guard that checks for user authorization based on a JWT token.
|
||||||
|
* It verifies if the user has the required rights, roles, and/or organizations to access a specific route.
|
||||||
|
*/
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class TokenGuard implements CanActivate {
|
export class TokenGuard implements CanActivate {
|
||||||
|
/**
|
||||||
|
* Initializes the guard with the `Reflector` to read metadata and the `tokenService` for token validation.
|
||||||
|
* @param reflector The `Reflector` instance to retrieve custom metadata.
|
||||||
|
* @param tokenService a SessionService implementation apihub24/token_service
|
||||||
|
*/
|
||||||
constructor(
|
constructor(
|
||||||
private reflector: Reflector,
|
private reflector: Reflector,
|
||||||
@Inject("@apihub24/token_service")
|
@Inject("@apihub24/token_service")
|
||||||
private readonly tokenService: tokenService.ITokenService
|
private readonly tokenService: tokenService.ITokenService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if the current request can proceed.
|
||||||
|
* This is the core logic of the guard. It checks the token and the user's permissions.
|
||||||
|
* @param context The `ExecutionContext` object, providing access to the request, response, and handler.
|
||||||
|
* @returns A Promise that resolves to `true` if the request is authorized, otherwise `false`.
|
||||||
|
*/
|
||||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||||
const requiredRights = this.reflector.getAllAndOverride<string[]>(
|
const requiredRights = this.reflector.getAllAndOverride<string[]>(
|
||||||
RIGHTS_KEY,
|
RIGHTS_KEY,
|
||||||
|
|||||||
@ -27,4 +27,4 @@ export * from "./services/registration.service";
|
|||||||
export * from "./services/right.factory.service";
|
export * from "./services/right.factory.service";
|
||||||
export * from "./services/right.service";
|
export * from "./services/right.service";
|
||||||
|
|
||||||
export * from "./token-authentication.module";
|
export * from "./token.authentication.module";
|
||||||
|
|||||||
@ -7,23 +7,52 @@ import {
|
|||||||
ValidateNested,
|
ValidateNested,
|
||||||
} from "class-validator";
|
} from "class-validator";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* represent the Users Account
|
||||||
|
*/
|
||||||
export class Account implements IAccount {
|
export class Account implements IAccount {
|
||||||
id: string;
|
id: string;
|
||||||
|
|
||||||
@MinLength(3)
|
/**
|
||||||
|
* the Accounts name
|
||||||
|
*/
|
||||||
|
@MinLength(3, { message: "Account accountName must have min. 3 letters" })
|
||||||
accountName: string;
|
accountName: string;
|
||||||
|
|
||||||
@MinLength(3)
|
/**
|
||||||
|
* the hashed Password
|
||||||
|
*/
|
||||||
|
@MinLength(3, { message: "Account passwordHash must have min. 3 letters" })
|
||||||
passwordHash: string;
|
passwordHash: string;
|
||||||
|
|
||||||
@IsEmail()
|
/**
|
||||||
|
* a E-Mail Address for this Account
|
||||||
|
*/
|
||||||
|
@IsEmail(undefined, {
|
||||||
|
message: "Account email must be a valid email address",
|
||||||
|
})
|
||||||
email: string;
|
email: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* represents if the E-Mail Address was verified
|
||||||
|
*/
|
||||||
emailVerified: boolean;
|
emailVerified: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* represents if the User was activated for Login
|
||||||
|
*/
|
||||||
active: boolean;
|
active: boolean;
|
||||||
|
|
||||||
@IsNotEmpty({ each: true })
|
/**
|
||||||
@ValidateNested({ each: true })
|
* the Groups this User was member
|
||||||
|
*/
|
||||||
|
@IsNotEmpty({
|
||||||
|
each: true,
|
||||||
|
message: "Account groups can not be undefined or null",
|
||||||
|
})
|
||||||
|
@ValidateNested({
|
||||||
|
each: true,
|
||||||
|
message: "Account groups some group was invalid",
|
||||||
|
})
|
||||||
groups: Group[];
|
groups: Group[];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,17 +3,32 @@ import { Organization } from "./organization";
|
|||||||
import { Right } from "./right";
|
import { Right } from "./right";
|
||||||
import { IsNotEmpty, MinLength, ValidateNested } from "class-validator";
|
import { IsNotEmpty, MinLength, ValidateNested } from "class-validator";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* represents a Group that a User can Join
|
||||||
|
*/
|
||||||
export class Group implements IGroup {
|
export class Group implements IGroup {
|
||||||
id: string;
|
id: string;
|
||||||
|
|
||||||
@MinLength(3)
|
/**
|
||||||
|
* the Name of the Group
|
||||||
|
*/
|
||||||
|
@MinLength(3, { message: "Group name must have min. 3 letters" })
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
@IsNotEmpty()
|
/**
|
||||||
@ValidateNested()
|
* the Organization in that the Group are
|
||||||
|
*/
|
||||||
|
@IsNotEmpty({ message: "Group organization can not be undefined or null" })
|
||||||
|
@ValidateNested({ message: "Group organization was invalid" })
|
||||||
organization: Organization;
|
organization: Organization;
|
||||||
|
|
||||||
@IsNotEmpty({ each: true })
|
/**
|
||||||
@ValidateNested({ each: true })
|
* the Rights that have the Group
|
||||||
|
*/
|
||||||
|
@IsNotEmpty({
|
||||||
|
each: true,
|
||||||
|
message: "Group rights can not be undefined or null",
|
||||||
|
})
|
||||||
|
@ValidateNested({ each: true, message: "Group rights some right is invalid" })
|
||||||
rights: Right[];
|
rights: Right[];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,16 @@
|
|||||||
import { IsNotEmpty, MinLength } from "class-validator";
|
import { IsNotEmpty, MinLength } from "class-validator";
|
||||||
import { IOrganization } from "../contracts/models/organization";
|
import { IOrganization } from "../contracts/models/organization";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* represent a Organization Unit
|
||||||
|
*/
|
||||||
export class Organization implements IOrganization {
|
export class Organization implements IOrganization {
|
||||||
id: string;
|
id: string;
|
||||||
|
|
||||||
@IsNotEmpty()
|
/**
|
||||||
@MinLength(3)
|
* the Name of the Organization
|
||||||
|
*/
|
||||||
|
@IsNotEmpty({ message: "Organization name can not be undefined or null" })
|
||||||
|
@MinLength(3, { message: "Organization name must have min. 3 letters" })
|
||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,19 +1,46 @@
|
|||||||
import { IsEmail, IsNotEmpty, MinLength } from "class-validator";
|
import { IsEmail, IsNotEmpty, MinLength } from "class-validator";
|
||||||
import { IRegistration } from "../contracts/models/registration";
|
import { IRegistration } from "../contracts/models/registration";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* represents Registration Data
|
||||||
|
*/
|
||||||
export class Registration implements IRegistration {
|
export class Registration implements IRegistration {
|
||||||
@MinLength(3)
|
/**
|
||||||
|
* the Account Name to register
|
||||||
|
*/
|
||||||
|
@MinLength(3, {
|
||||||
|
message: "Registration accountName must have min. 3 letters",
|
||||||
|
})
|
||||||
accountName: string;
|
accountName: string;
|
||||||
|
|
||||||
@MinLength(3)
|
/**
|
||||||
|
* the Password to register
|
||||||
|
*/
|
||||||
|
@MinLength(3, { message: "Registration password must have min. 3 letters" })
|
||||||
password: string;
|
password: string;
|
||||||
|
|
||||||
@MinLength(3)
|
/**
|
||||||
|
* the repeated Password
|
||||||
|
*/
|
||||||
|
@MinLength(3, {
|
||||||
|
message: "Registration passwordComparer must have min. 3 letters",
|
||||||
|
})
|
||||||
passwordComparer: string;
|
passwordComparer: string;
|
||||||
|
|
||||||
@IsEmail()
|
/**
|
||||||
|
* the E-Mail Address to register
|
||||||
|
*/
|
||||||
|
@IsEmail(undefined, {
|
||||||
|
message: "Registration email must be a valid email address",
|
||||||
|
})
|
||||||
email: string;
|
email: string;
|
||||||
|
|
||||||
@IsNotEmpty({ each: true })
|
/**
|
||||||
|
* the Group Ids the Account must be Part
|
||||||
|
*/
|
||||||
|
@IsNotEmpty({
|
||||||
|
each: true,
|
||||||
|
message: "Registration groupIds can not be undefined or empty",
|
||||||
|
})
|
||||||
groupIds: string[];
|
groupIds: string[];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,15 @@
|
|||||||
import { MinLength } from "class-validator";
|
import { MinLength } from "class-validator";
|
||||||
import { IRight } from "../contracts/models/right";
|
import { IRight } from "../contracts/models/right";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* represent a Application Right
|
||||||
|
*/
|
||||||
export class Right implements IRight {
|
export class Right implements IRight {
|
||||||
id: string;
|
id: string;
|
||||||
|
|
||||||
@MinLength(3)
|
/**
|
||||||
|
* the Right Name
|
||||||
|
*/
|
||||||
|
@MinLength(3, { message: "Right name must have min. 3 letters" })
|
||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,12 +2,21 @@ import { IsNotEmpty, ValidateNested } from "class-validator";
|
|||||||
import { Account } from "./account";
|
import { Account } from "./account";
|
||||||
import { ISession } from "../contracts/models/session";
|
import { ISession } from "../contracts/models/session";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* represents a Session
|
||||||
|
*/
|
||||||
export class Session implements ISession {
|
export class Session implements ISession {
|
||||||
id: string;
|
id: string;
|
||||||
|
|
||||||
@IsNotEmpty()
|
/**
|
||||||
@ValidateNested()
|
* the Account of the Session
|
||||||
|
*/
|
||||||
|
@IsNotEmpty({ message: "Session account can not be undefined or empty" })
|
||||||
|
@ValidateNested({ message: "Session account is invalid" })
|
||||||
account: Account;
|
account: Account;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* some MetaData
|
||||||
|
*/
|
||||||
metaData: Record<string, any>;
|
metaData: Record<string, any>;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,19 @@
|
|||||||
import { MinLength } from "class-validator";
|
import { MinLength } from "class-validator";
|
||||||
import { ISignIn } from "../contracts/models/sign.in";
|
import { ISignIn } from "../contracts/models/sign.in";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* represent a Sign In Request
|
||||||
|
*/
|
||||||
export class SignIn implements ISignIn {
|
export class SignIn implements ISignIn {
|
||||||
@MinLength(3)
|
/**
|
||||||
|
* the Account Name to sign in
|
||||||
|
*/
|
||||||
|
@MinLength(3, { message: "SignIn accountName must have min. 3 letters" })
|
||||||
accountName: string;
|
accountName: string;
|
||||||
|
|
||||||
@MinLength(3)
|
/**
|
||||||
|
* the Password to sign in
|
||||||
|
*/
|
||||||
|
@MinLength(3, { message: "SignIn password must have min. 3 letters" })
|
||||||
password: string;
|
password: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,6 +17,11 @@ export class AccountFactoryService {
|
|||||||
private readonly groupService: GroupService
|
private readonly groupService: GroupService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* create a new Account from a Registration
|
||||||
|
* @param registration a Registration
|
||||||
|
* @returns a new User Account
|
||||||
|
*/
|
||||||
async createFromRegistration(registration: IRegistration): Promise<IAccount> {
|
async createFromRegistration(registration: IRegistration): Promise<IAccount> {
|
||||||
let validationErrors = await validate(
|
let validationErrors = await validate(
|
||||||
plainToClass(Registration, registration)
|
plainToClass(Registration, registration)
|
||||||
|
|||||||
@ -12,19 +12,46 @@ export class AccountService {
|
|||||||
private readonly groupRepository: repository.IRepository<IGroup>
|
private readonly groupRepository: repository.IRepository<IGroup>
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This asynchronous method retrieves accounts that match a given filter.
|
||||||
|
* @param filter A function that takes an `IAccount` object and returns a boolean. It serves as the condition for selecting accounts.
|
||||||
|
* @returns A Promise that resolves to an array of `IAccount` objects that satisfy the filter condition.
|
||||||
|
*/
|
||||||
async getBy(filter: (account: IAccount) => boolean): Promise<IAccount[]> {
|
async getBy(filter: (account: IAccount) => boolean): Promise<IAccount[]> {
|
||||||
return await this.accountRepository.getBy(filter);
|
return await this.accountRepository.getBy(filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This asynchronous method saves a single `IAccount` object. It uses the `accountRepository` to persist the account. If the save operation fails, it logs a warning and returns null.
|
||||||
|
* @param account The `IAccount` object to be saved.
|
||||||
|
* @returns A Promise that resolves to the saved `IAccount` object, or `null` if the save operation fails.
|
||||||
|
*/
|
||||||
async save(account: IAccount): Promise<IAccount | null> {
|
async save(account: IAccount): Promise<IAccount | null> {
|
||||||
|
try {
|
||||||
const accounts = await this.accountRepository.save([account]);
|
const accounts = await this.accountRepository.save([account]);
|
||||||
return accounts?.length ? accounts[0] : null;
|
return accounts?.length ? accounts[0] : null;
|
||||||
|
} catch (err) {
|
||||||
|
console.warn(err);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This asynchronous method deletes accounts based on a provided filter.
|
||||||
|
* @param filter A function that takes an `IAccount` object and returns a boolean. It specifies which accounts to delete.
|
||||||
|
* @returns A Promise that resolves to `true` if the deletion is successful, and `false` otherwise.
|
||||||
|
*/
|
||||||
async delete(filter: (account: IAccount) => boolean): Promise<boolean> {
|
async delete(filter: (account: IAccount) => boolean): Promise<boolean> {
|
||||||
return await this.accountRepository.deleteBy(filter);
|
return await this.accountRepository.deleteBy(filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This asynchronous method adds a specific account to a group. It first finds both the account and the group using their IDs. If they are found, it adds the group to the account's groups array and saves the updated account.
|
||||||
|
* @param accountId The unique identifier of the account.
|
||||||
|
* @param groupId The unique identifier of the group to which the account should be added.
|
||||||
|
* @returns A Promise that resolves to the updated `IAccount` object.
|
||||||
|
* @throws An error if the account or the group cannot be found, or if the updated account cannot be saved.
|
||||||
|
*/
|
||||||
async addAccountToGroup(
|
async addAccountToGroup(
|
||||||
accountId: string,
|
accountId: string,
|
||||||
groupId: string
|
groupId: string
|
||||||
@ -39,6 +66,13 @@ export class AccountService {
|
|||||||
return accountsSaved[0];
|
return accountsSaved[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This asynchronous method removes a specific account from a group. It first finds the account and group, then filters the group out of the account's groups array before saving the updated account.
|
||||||
|
* @param accountId The unique identifier of the account.
|
||||||
|
* @param groupId The unique identifier of the group to be removed.
|
||||||
|
* @returns A Promise that resolves to the updated `IAccount` object.
|
||||||
|
* @throws An error if the account or the group cannot be found, or if the updated account cannot be saved.
|
||||||
|
*/
|
||||||
async removeAccountFromGroup(
|
async removeAccountFromGroup(
|
||||||
accountId: string,
|
accountId: string,
|
||||||
groupId: string
|
groupId: string
|
||||||
|
|||||||
@ -14,6 +14,13 @@ export class GroupFactoryService {
|
|||||||
private readonly organizationService: OrganizationService
|
private readonly organizationService: OrganizationService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* create a new Group in a Organization and Adds some Rights
|
||||||
|
* @param name a Name of the Group
|
||||||
|
* @param organizationId the Organization Id
|
||||||
|
* @param rightIds the Right Ids
|
||||||
|
* @returns the new Group
|
||||||
|
*/
|
||||||
async createGroup(
|
async createGroup(
|
||||||
name: string,
|
name: string,
|
||||||
organizationId: string,
|
organizationId: string,
|
||||||
|
|||||||
@ -12,19 +12,41 @@ export class GroupService {
|
|||||||
private readonly rightRepository: repository.IRepository<IRight>
|
private readonly rightRepository: repository.IRepository<IRight>
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This asynchronous method retrieves groups that match a given filter.
|
||||||
|
* @param filter A function that takes a `IGroup` object and returns a boolean. It serves as the condition for selecting groups.
|
||||||
|
* @returns A Promise that resolves to an array of `IGroup` objects that satisfy the filter condition.
|
||||||
|
*/
|
||||||
async getBy(filter: (group: IGroup) => boolean): Promise<IGroup[]> {
|
async getBy(filter: (group: IGroup) => boolean): Promise<IGroup[]> {
|
||||||
return await this.groupRepository.getBy(filter);
|
return await this.groupRepository.getBy(filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This asynchronous method saves a single `IGroup` object. It uses the `groupRepository` to persist the group.
|
||||||
|
* @param group The `IGroup` object to be saved.
|
||||||
|
* @returns A Promise that resolves to the saved `IGroup` object, or `null` if the save operation fails.
|
||||||
|
*/
|
||||||
async save(group: IGroup): Promise<IGroup | null> {
|
async save(group: IGroup): Promise<IGroup | null> {
|
||||||
const groups = await this.groupRepository.save([group]);
|
const groups = await this.groupRepository.save([group]);
|
||||||
return groups?.length ? groups[0] : null;
|
return groups?.length ? groups[0] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This asynchronous method deletes groups based on a provided filter.
|
||||||
|
* @param filter A function that takes a `IGroup` object and returns a boolean. It specifies which groups to delete.
|
||||||
|
* @returns A Promise that resolves to `true` if the deletion is successful, and `false` otherwise.
|
||||||
|
*/
|
||||||
async delete(filter: (group: IGroup) => boolean): Promise<boolean> {
|
async delete(filter: (group: IGroup) => boolean): Promise<boolean> {
|
||||||
return this.groupRepository.deleteBy(filter);
|
return this.groupRepository.deleteBy(filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This asynchronous method adds a specific right to a group. It first finds both the group and the right using their IDs. If they are found, it adds the right to the group's rights array and saves the updated group.
|
||||||
|
* @param groupId The unique identifier of the group.
|
||||||
|
* @param rightId The unique identifier of the right to be added.
|
||||||
|
* @returns A Promise that resolves to the updated `IGroup` object.
|
||||||
|
* @throws An error if the group or right is not found, or if the updated group cannot be saved.
|
||||||
|
*/
|
||||||
async addRightToGroup(groupId: string, rightId: string) {
|
async addRightToGroup(groupId: string, rightId: string) {
|
||||||
const [group, right] = await this.getGroupAndRight(groupId, rightId);
|
const [group, right] = await this.getGroupAndRight(groupId, rightId);
|
||||||
group.rights = group.rights.filter((x) => x.id !== rightId);
|
group.rights = group.rights.filter((x) => x.id !== rightId);
|
||||||
@ -36,6 +58,13 @@ export class GroupService {
|
|||||||
return savedGroups[0];
|
return savedGroups[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This asynchronous method removes a specific right from a group. It first finds the group and right, then filters the right out of the group's rights array before saving the updated group.
|
||||||
|
* @param groupId The unique identifier of the group.
|
||||||
|
* @param rightId The unique identifier of the right to be removed.
|
||||||
|
* @returns A Promise that resolves to the updated `IGroup` object.
|
||||||
|
* @throws An error if the group or right is not found, or if the updated group cannot be saved.
|
||||||
|
*/
|
||||||
async removeRightFromGroup(groupId: string, rightId: string) {
|
async removeRightFromGroup(groupId: string, rightId: string) {
|
||||||
const [group] = await this.getGroupAndRight(groupId, rightId);
|
const [group] = await this.getGroupAndRight(groupId, rightId);
|
||||||
group.rights = group.rights.filter((x) => x.id !== rightId);
|
group.rights = group.rights.filter((x) => x.id !== rightId);
|
||||||
|
|||||||
@ -18,6 +18,14 @@ export class LoginService {
|
|||||||
private readonly accountService: AccountService
|
private readonly accountService: AccountService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This asynchronous method authenticates a user and creates a JWT token for a new session.
|
||||||
|
* It first finds an active account with the given `accountName`, verifies the password, and if successful, creates a new session and generates a signed token.
|
||||||
|
* @param signIn An `ISignIn` object containing the `accountName` and `password`.
|
||||||
|
* @param expires An optional string specifying the token's expiration duration (default: "1h").
|
||||||
|
* @param algorithm An optional string specifying the signing algorithm for the token (default: "HS512").
|
||||||
|
* @returns A Promise that resolves to a JWT token string. Returns an empty string if authentication fails.
|
||||||
|
*/
|
||||||
async signIn(
|
async signIn(
|
||||||
signIn: ISignIn,
|
signIn: ISignIn,
|
||||||
expires: string = "1h",
|
expires: string = "1h",
|
||||||
@ -48,6 +56,12 @@ export class LoginService {
|
|||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This asynchronous method logs a user out by removing their active session.
|
||||||
|
* It finds the account by its `id` and then locates and removes the corresponding session.
|
||||||
|
* @param accountId The unique identifier (`id`) of the account to be logged out.
|
||||||
|
* @returns A Promise that resolves to `void` after the operation is complete.
|
||||||
|
*/
|
||||||
async signOut(accountId: string): Promise<void> {
|
async signOut(accountId: string): Promise<void> {
|
||||||
const accounts = await this.accountService.getBy((x) => x.id === accountId);
|
const accounts = await this.accountService.getBy((x) => x.id === accountId);
|
||||||
if (!accounts.length) {
|
if (!accounts.length) {
|
||||||
|
|||||||
@ -4,6 +4,11 @@ import { Organization } from "../models/organization";
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class OrganizationFactoryService {
|
export class OrganizationFactoryService {
|
||||||
|
/**
|
||||||
|
* creates a Organization from a Name
|
||||||
|
* @param name a Organization Name
|
||||||
|
* @returns the new Organization
|
||||||
|
*/
|
||||||
createFromName(name: string): IOrganization {
|
createFromName(name: string): IOrganization {
|
||||||
const organization = new Organization();
|
const organization = new Organization();
|
||||||
organization.name = name;
|
organization.name = name;
|
||||||
|
|||||||
@ -9,12 +9,23 @@ export class OrganizationService {
|
|||||||
private readonly organizationRepository: repository.IRepository<IOrganization>
|
private readonly organizationRepository: repository.IRepository<IOrganization>
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This asynchronous method retrieves organizations that match a given filter.
|
||||||
|
* @param filter A function that takes an `IOrganization` object and returns a boolean. It serves as the condition for selecting organizations.
|
||||||
|
* @returns A Promise that resolves to an array of `IOrganization` objects that satisfy the filter condition.
|
||||||
|
*/
|
||||||
async getBy(
|
async getBy(
|
||||||
filter: (organization: IOrganization) => boolean
|
filter: (organization: IOrganization) => boolean
|
||||||
): Promise<IOrganization[]> {
|
): Promise<IOrganization[]> {
|
||||||
return await this.organizationRepository.getBy(filter);
|
return await this.organizationRepository.getBy(filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This asynchronous method saves a single `IOrganization` object.
|
||||||
|
* @param organization The `IOrganization` object to be saved.
|
||||||
|
* @returns A Promise that resolves to the saved `IOrganization` object.
|
||||||
|
* @throws An error if the organization could not be saved.
|
||||||
|
*/
|
||||||
async save(organization: IOrganization): Promise<IOrganization> {
|
async save(organization: IOrganization): Promise<IOrganization> {
|
||||||
const savedOrganizations = await this.organizationRepository.save([
|
const savedOrganizations = await this.organizationRepository.save([
|
||||||
organization,
|
organization,
|
||||||
@ -25,6 +36,11 @@ export class OrganizationService {
|
|||||||
return savedOrganizations[0];
|
return savedOrganizations[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This asynchronous method deletes organizations based on a provided filter.
|
||||||
|
* @param filter A function that takes an `IOrganization` object and returns a boolean. It specifies which organizations to delete.
|
||||||
|
* @returns A Promise that resolves to `true` if the deletion is successful, and `false` otherwise.
|
||||||
|
*/
|
||||||
async deleteBy(
|
async deleteBy(
|
||||||
filter: (organization: IOrganization) => boolean
|
filter: (organization: IOrganization) => boolean
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
|
|||||||
@ -16,6 +16,12 @@ export class RegistrationService {
|
|||||||
private readonly mailVerificationService: mailVerificationService.IMailVerificationService
|
private readonly mailVerificationService: mailVerificationService.IMailVerificationService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This asynchronous method registers a new account based on the provided registration data.
|
||||||
|
* It uses an `AccountFactoryService` to create a new `IAccount` object and then saves it using the `AccountService`.
|
||||||
|
* @param registration An `IRegistration` object containing the data for the new account.
|
||||||
|
* @returns A Promise that resolves to the newly created and saved `IAccount` object, or `null` if the operation fails.
|
||||||
|
*/
|
||||||
async registerAccount(registration: IRegistration): Promise<IAccount | null> {
|
async registerAccount(registration: IRegistration): Promise<IAccount | null> {
|
||||||
const newAccount = await this.accountFactory.createFromRegistration(
|
const newAccount = await this.accountFactory.createFromRegistration(
|
||||||
registration
|
registration
|
||||||
@ -23,6 +29,13 @@ export class RegistrationService {
|
|||||||
return this.accountService.save(newAccount);
|
return this.accountService.save(newAccount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This asynchronous method verifies a user's email address.
|
||||||
|
* It checks the provided code with the `mailVerificationService`. If successful, it finds all unverified accounts with that email, sets their `emailVerified` flag to `true`, and saves the changes.
|
||||||
|
* @param email The email address to be verified.
|
||||||
|
* @param code The verification code sent to the email address.
|
||||||
|
* @returns A Promise that resolves to `true` if the verification was successful, otherwise `false`.
|
||||||
|
*/
|
||||||
async verify(email: string, code: string): Promise<boolean> {
|
async verify(email: string, code: string): Promise<boolean> {
|
||||||
const verified = await this.mailVerificationService.verify(email, code);
|
const verified = await this.mailVerificationService.verify(email, code);
|
||||||
if (!verified) {
|
if (!verified) {
|
||||||
@ -38,6 +51,12 @@ export class RegistrationService {
|
|||||||
return verified;
|
return verified;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This asynchronous method activates an account.
|
||||||
|
* It finds the account by its `id`, sets the `active` attribute to `true`, and saves the change.
|
||||||
|
* @param accountId The unique identifier (`id`) of the account to activate.
|
||||||
|
* @returns A Promise that resolves to `true` if the activation was successful, otherwise `false`.
|
||||||
|
*/
|
||||||
async activateAccount(accountId: string): Promise<boolean> {
|
async activateAccount(accountId: string): Promise<boolean> {
|
||||||
const accounts = await this.accountService.getBy((x) => x.id === accountId);
|
const accounts = await this.accountService.getBy((x) => x.id === accountId);
|
||||||
if (!accounts?.length) {
|
if (!accounts?.length) {
|
||||||
@ -48,6 +67,12 @@ export class RegistrationService {
|
|||||||
return savedAccount?.active === true;
|
return savedAccount?.active === true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This asynchronous method deactivates an account.
|
||||||
|
* It finds the account by its `id`, sets the `active` attribute to `false`, and saves the change.
|
||||||
|
* @param accountId The unique identifier (`id`) of the account to deactivate.
|
||||||
|
* @returns A Promise that resolves to `true` if the deactivation was successful, otherwise `false`. Returns `true` if the account is not found, as it is already in a "deactivated" state.
|
||||||
|
*/
|
||||||
async deactivateAccount(accountId: string): Promise<boolean> {
|
async deactivateAccount(accountId: string): Promise<boolean> {
|
||||||
const accounts = await this.accountService.getBy((x) => x.id === accountId);
|
const accounts = await this.accountService.getBy((x) => x.id === accountId);
|
||||||
if (!accounts?.length) {
|
if (!accounts?.length) {
|
||||||
@ -58,6 +83,12 @@ export class RegistrationService {
|
|||||||
return savedAccount?.active === false;
|
return savedAccount?.active === false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This asynchronous method unregister (deletes) an account.
|
||||||
|
* It finds the account by its `id` and triggers its deletion using the `accountService`. The operation is "fire-and-forget" and does not wait for completion.
|
||||||
|
* @param accountId The unique identifier (`id`) of the account to unregister.
|
||||||
|
* @returns A Promise that resolves to `void`.
|
||||||
|
*/
|
||||||
async unregisterAccount(accountId: string): Promise<void> {
|
async unregisterAccount(accountId: string): Promise<void> {
|
||||||
const accounts = await this.accountService.getBy((x) => x.id === accountId);
|
const accounts = await this.accountService.getBy((x) => x.id === accountId);
|
||||||
if (!accounts?.length) {
|
if (!accounts?.length) {
|
||||||
|
|||||||
@ -5,6 +5,11 @@ import { Right } from "../models/right";
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class RightFactoryService {
|
export class RightFactoryService {
|
||||||
|
/**
|
||||||
|
* creates a Right from a Name
|
||||||
|
* @param name a Name for a Right
|
||||||
|
* @returns the new Right
|
||||||
|
*/
|
||||||
async createRight(name: string): Promise<IRight> {
|
async createRight(name: string): Promise<IRight> {
|
||||||
const right = new Right();
|
const right = new Right();
|
||||||
right.name = name;
|
right.name = name;
|
||||||
|
|||||||
@ -9,13 +9,30 @@ export class RightService {
|
|||||||
private readonly rightRepository: repository.IRepository<IRight>
|
private readonly rightRepository: repository.IRepository<IRight>
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This asynchronous method retrieves rights that match a given filter.
|
||||||
|
* @param filter A function that takes an `IRight` object and returns a boolean. It serves as the condition for selecting rights.
|
||||||
|
* @returns A Promise that resolves to an array of `IRight` objects that satisfy the filter condition.
|
||||||
|
*/
|
||||||
async getBy(filter: (right: IRight) => boolean): Promise<IRight[]> {
|
async getBy(filter: (right: IRight) => boolean): Promise<IRight[]> {
|
||||||
return await this.rightRepository.getBy(filter);
|
return await this.rightRepository.getBy(filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This asynchronous method saves a single `IRight` object. It uses the `rightRepository` to persist the right.
|
||||||
|
* @param right The `IRight` object to be saved.
|
||||||
|
* @returns A Promise that resolves to the saved `IRight` object, or `null` if the save operation fails.
|
||||||
|
*/
|
||||||
async save(right: IRight): Promise<IRight | null> {
|
async save(right: IRight): Promise<IRight | null> {
|
||||||
const rights = await this.rightRepository.save([right]);
|
const rights = await this.rightRepository.save([right]);
|
||||||
return rights?.length ? rights[0] : null;
|
return rights?.length ? rights[0] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This asynchronous method deletes rights based on a provided filter.
|
||||||
|
* @param filter A function that takes an `IRight` object and returns a boolean. It specifies which rights to delete.
|
||||||
|
* @returns A Promise that resolves to `true` if the deletion is successful, and `false` otherwise.
|
||||||
|
*/
|
||||||
async delete(filter: (right: IRight) => boolean): Promise<boolean> {
|
async delete(filter: (right: IRight) => boolean): Promise<boolean> {
|
||||||
return await this.rightRepository.deleteBy(filter);
|
return await this.rightRepository.deleteBy(filter);
|
||||||
}
|
}
|
||||||
|
|||||||
35
src/token.authentication.module.spec.ts
Normal file
35
src/token.authentication.module.spec.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import { Test, TestingModule } from "@nestjs/testing";
|
||||||
|
import { AccountRepositoryMockModule } from "../test/account.repository.mock";
|
||||||
|
import { GroupRepositoryMockModule } from "../test/group.repository.mock";
|
||||||
|
import { MailServiceMockModule } from "../test/mail.verification.service.mock";
|
||||||
|
import { OrganizationRepositoryMockModule } from "../test/organization.repository.mock";
|
||||||
|
import { PasswordServiceMockModule } from "../test/password.service.mock";
|
||||||
|
import { RightRepositoryMockModule } from "../test/right.repository.mock";
|
||||||
|
import { SessionServiceMockModule } from "../test/session.service.mock";
|
||||||
|
import { TokenServiceMockModule } from "../test/token.service.mock";
|
||||||
|
import { AccountFactoryService, TokenAuthenticationModule } from "../src";
|
||||||
|
|
||||||
|
describe("TokenAuthenticationModule Tests", () => {
|
||||||
|
let module: TestingModule | null = null;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
module = await Test.createTestingModule({
|
||||||
|
imports: [
|
||||||
|
MailServiceMockModule.forRoot(),
|
||||||
|
PasswordServiceMockModule.forRoot(),
|
||||||
|
SessionServiceMockModule.forRoot(),
|
||||||
|
TokenServiceMockModule.forRoot(),
|
||||||
|
AccountRepositoryMockModule.forRoot(),
|
||||||
|
GroupRepositoryMockModule.forRoot(),
|
||||||
|
RightRepositoryMockModule.forRoot(),
|
||||||
|
OrganizationRepositoryMockModule.forRoot(),
|
||||||
|
TokenAuthenticationModule,
|
||||||
|
],
|
||||||
|
}).compile();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should exports AccountFactoryService", () => {
|
||||||
|
expect(module).toBeDefined();
|
||||||
|
expect(module?.get(AccountFactoryService)).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import { DynamicModule, Module, Provider } from "@nestjs/common";
|
import { DynamicModule, Global, Module, Provider } from "@nestjs/common";
|
||||||
import { RightService } from "./services/right.service";
|
import { RightService } from "./services/right.service";
|
||||||
import { GroupService } from "./services/group.service";
|
import { GroupService } from "./services/group.service";
|
||||||
import { AccountService } from "./services/account.service";
|
import { AccountService } from "./services/account.service";
|
||||||
@ -11,9 +11,10 @@ import { RightFactoryService } from "./services/right.factory.service";
|
|||||||
import { OrganizationService } from "./services/organization.service";
|
import { OrganizationService } from "./services/organization.service";
|
||||||
import { OrganizationFactoryService } from "./services/organization.factory.service";
|
import { OrganizationFactoryService } from "./services/organization.factory.service";
|
||||||
|
|
||||||
|
@Global()
|
||||||
@Module({})
|
@Module({})
|
||||||
export class TokenAuthenticationModule {
|
export class TokenAuthenticationModule {
|
||||||
static forRoot(providers: Provider[]): DynamicModule {
|
static forRoot(): DynamicModule {
|
||||||
return {
|
return {
|
||||||
module: TokenAuthenticationModule,
|
module: TokenAuthenticationModule,
|
||||||
imports: [ConfigModule.forRoot()],
|
imports: [ConfigModule.forRoot()],
|
||||||
@ -28,7 +29,6 @@ export class TokenAuthenticationModule {
|
|||||||
AccountFactoryService,
|
AccountFactoryService,
|
||||||
GroupFactoryService,
|
GroupFactoryService,
|
||||||
RightFactoryService,
|
RightFactoryService,
|
||||||
...providers,
|
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
RightService,
|
RightService,
|
||||||
33
test/account.repository.mock.ts
Normal file
33
test/account.repository.mock.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { DynamicModule, Global, Module } from "@nestjs/common";
|
||||||
|
import { IAccount } from "../src";
|
||||||
|
import { IRepository } from "@apihub24/repository";
|
||||||
|
|
||||||
|
@Global()
|
||||||
|
@Module({})
|
||||||
|
export class AccountRepositoryMockModule {
|
||||||
|
static forRoot(): DynamicModule {
|
||||||
|
const providers = [
|
||||||
|
{
|
||||||
|
provide: "@apihub24/account_repository",
|
||||||
|
useClass: AccountRepositoryMock,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
return {
|
||||||
|
module: AccountRepositoryMockModule,
|
||||||
|
providers: [...providers],
|
||||||
|
exports: [...providers],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AccountRepositoryMock implements IRepository<IAccount> {
|
||||||
|
getBy(filter: (model: IAccount) => boolean): Promise<IAccount[]> {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
save(models: IAccount[]): Promise<IAccount[]> {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
deleteBy(filter: (model: IAccount) => boolean): Promise<boolean> {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
}
|
||||||
33
test/group.repository.mock.ts
Normal file
33
test/group.repository.mock.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { DynamicModule, Global, Module } from "@nestjs/common";
|
||||||
|
import { IGroup } from "../src";
|
||||||
|
import { IRepository } from "@apihub24/repository";
|
||||||
|
|
||||||
|
@Global()
|
||||||
|
@Module({})
|
||||||
|
export class GroupRepositoryMockModule {
|
||||||
|
static forRoot(): DynamicModule {
|
||||||
|
const providers = [
|
||||||
|
{
|
||||||
|
provide: "@apihub24/group_repository",
|
||||||
|
useClass: GroupRepositoryMock,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
return {
|
||||||
|
module: GroupRepositoryMockModule,
|
||||||
|
providers: [...providers],
|
||||||
|
exports: [...providers],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class GroupRepositoryMock implements IRepository<IGroup> {
|
||||||
|
getBy(filter: (model: IGroup) => boolean): Promise<IGroup[]> {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
save(models: IGroup[]): Promise<IGroup[]> {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
deleteBy(filter: (model: IGroup) => boolean): Promise<boolean> {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
}
|
||||||
29
test/mail.verification.service.mock.ts
Normal file
29
test/mail.verification.service.mock.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { DynamicModule, Global, Module } from "@nestjs/common";
|
||||||
|
import { IAccount, ISession, IMailVerificationService } from "../src";
|
||||||
|
|
||||||
|
@Global()
|
||||||
|
@Module({})
|
||||||
|
export class MailServiceMockModule {
|
||||||
|
static forRoot(): DynamicModule {
|
||||||
|
const providers = [
|
||||||
|
{
|
||||||
|
provide: "@apihub24/mail_verification_service",
|
||||||
|
useClass: MailServiceMock,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
return {
|
||||||
|
module: MailServiceMockModule,
|
||||||
|
providers: [...providers],
|
||||||
|
exports: [...providers],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MailServiceMock implements IMailVerificationService {
|
||||||
|
sendVerificationMail(account: IAccount): Promise<void> {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
verify(email: string, code: string): Promise<boolean> {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
}
|
||||||
33
test/organization.repository.mock.ts
Normal file
33
test/organization.repository.mock.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { DynamicModule, Global, Module } from "@nestjs/common";
|
||||||
|
import { IOrganization } from "../src";
|
||||||
|
import { IRepository } from "@apihub24/repository";
|
||||||
|
|
||||||
|
@Global()
|
||||||
|
@Module({})
|
||||||
|
export class OrganizationRepositoryMockModule {
|
||||||
|
static forRoot(): DynamicModule {
|
||||||
|
const providers = [
|
||||||
|
{
|
||||||
|
provide: "@apihub24/organization_repository",
|
||||||
|
useClass: OrganizationRepositoryMock,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
return {
|
||||||
|
module: OrganizationRepositoryMockModule,
|
||||||
|
providers: [...providers],
|
||||||
|
exports: [...providers],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrganizationRepositoryMock implements IRepository<IOrganization> {
|
||||||
|
getBy(filter: (model: IOrganization) => boolean): Promise<IOrganization[]> {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
save(models: IOrganization[]): Promise<IOrganization[]> {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
deleteBy(filter: (model: IOrganization) => boolean): Promise<boolean> {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
}
|
||||||
26
test/password.service.mock.ts
Normal file
26
test/password.service.mock.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { DynamicModule, Global, Module } from "@nestjs/common";
|
||||||
|
import { IPasswordService } from "../src";
|
||||||
|
|
||||||
|
@Global()
|
||||||
|
@Module({})
|
||||||
|
export class PasswordServiceMockModule {
|
||||||
|
static forRoot(): DynamicModule {
|
||||||
|
const providers = [
|
||||||
|
{ provide: "@apihub24/password_service", useClass: PasswordServiceMock },
|
||||||
|
];
|
||||||
|
return {
|
||||||
|
module: PasswordServiceMock,
|
||||||
|
providers: [...providers],
|
||||||
|
exports: [...providers],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PasswordServiceMock implements IPasswordService {
|
||||||
|
hash(plainTextPassword: string): Promise<string> {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
verify(plainTextPassword: string, passwordHash: string): Promise<boolean> {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
}
|
||||||
33
test/right.repository.mock.ts
Normal file
33
test/right.repository.mock.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { DynamicModule, Global, Module } from "@nestjs/common";
|
||||||
|
import { IRight } from "../src";
|
||||||
|
import { IRepository } from "@apihub24/repository";
|
||||||
|
|
||||||
|
@Global()
|
||||||
|
@Module({})
|
||||||
|
export class RightRepositoryMockModule {
|
||||||
|
static forRoot(): DynamicModule {
|
||||||
|
const providers = [
|
||||||
|
{
|
||||||
|
provide: "@apihub24/right_repository",
|
||||||
|
useClass: RightRepositoryMock,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
return {
|
||||||
|
module: RightRepositoryMockModule,
|
||||||
|
providers: [...providers],
|
||||||
|
exports: [...providers],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RightRepositoryMock implements IRepository<IRight> {
|
||||||
|
getBy(filter: (model: IRight) => boolean): Promise<IRight[]> {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
save(models: IRight[]): Promise<IRight[]> {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
deleteBy(filter: (model: IRight) => boolean): Promise<boolean> {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
}
|
||||||
32
test/session.service.mock.ts
Normal file
32
test/session.service.mock.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import { DynamicModule, Global, Module } from "@nestjs/common";
|
||||||
|
import { IAccount, ISession, ISessionService } from "../src";
|
||||||
|
|
||||||
|
@Global()
|
||||||
|
@Module({})
|
||||||
|
export class SessionServiceMockModule {
|
||||||
|
static forRoot(): DynamicModule {
|
||||||
|
const providers = [
|
||||||
|
{ provide: "@apihub24/session_service", useClass: SessionServiceMock },
|
||||||
|
];
|
||||||
|
return {
|
||||||
|
module: SessionServiceMockModule,
|
||||||
|
providers: [...providers],
|
||||||
|
exports: [...providers],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SessionServiceMock implements ISessionService {
|
||||||
|
create(account: IAccount): Promise<ISession> {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
getBy(filter: (account: IAccount) => boolean): Promise<ISession[]> {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
getById(sessionId: string): Promise<ISession | null> {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
remove(sessionId: string): Promise<void> {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
}
|
||||||
34
test/token.service.mock.ts
Normal file
34
test/token.service.mock.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { DynamicModule, Global, Module } from "@nestjs/common";
|
||||||
|
import { Algorithm, IAccount, ISession, ITokenService } from "../src";
|
||||||
|
|
||||||
|
@Global()
|
||||||
|
@Module({})
|
||||||
|
export class TokenServiceMockModule {
|
||||||
|
static forRoot(): DynamicModule {
|
||||||
|
const providers = [
|
||||||
|
{ provide: "@apihub24/token_service", useClass: TokenServiceMock },
|
||||||
|
];
|
||||||
|
return {
|
||||||
|
module: TokenServiceMockModule,
|
||||||
|
providers: [...providers],
|
||||||
|
exports: [...providers],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TokenServiceMock implements ITokenService {
|
||||||
|
generate(
|
||||||
|
session: ISession,
|
||||||
|
subject: string,
|
||||||
|
expires?: string,
|
||||||
|
algorithm?: Algorithm
|
||||||
|
): Promise<string> {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
validate(token: string): Promise<boolean> {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
getAccount(token: string): Promise<IAccount | null> {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user