Skip to content

Commit

Permalink
Merge pull request #135 from conceptadev/feature/password-service
Browse files Browse the repository at this point in the history
feat(password): add hash object optional method
  • Loading branch information
MrMaz authored Nov 7, 2023
2 parents c66ed22 + 4a77bbf commit f2d7c5c
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,21 @@ export interface PasswordStorageServiceInterface {
hashObject<T extends PasswordPlainInterface>(
object: T,
salt?: string,
): Promise<PasswordStorageInterface>;
): Promise<Omit<T, 'password'> & PasswordStorageInterface>;

/**
* Hash password for an object if password property exists.
*
* @param object An object containing the new password to hash.
* @param salt Optional salt. If not provided, one will be generated.
* @returns A new object with the password hashed, with salt added.
*/
hashObjectOptional<T extends Partial<PasswordPlainInterface>>(
object: T,
salt?: string,
): Promise<
Omit<T, 'password'> | (Omit<T, 'password'> & PasswordStorageInterface)
>;

/**
* Validate if password matches and its valid.
Expand Down
44 changes: 37 additions & 7 deletions packages/nestjs-password/src/services/password-storage.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,20 +46,50 @@ export class PasswordStorageService implements PasswordStorageServiceInterface {
object: T,
salt?: string,
): Promise<Omit<T, 'password'> & PasswordStorageInterface> {
// hash the password
const hashed = await this.hash(object.password, salt);
// extract password property
const { password, ...safeObject } = object;

// remove the password property
delete (object as Partial<T>).password;
// hash the password
const hashed = await this.hash(password, salt);

// return the object with password hashed
return {
...object,
passwordHash: hashed.passwordHash,
passwordSalt: hashed.passwordSalt,
...safeObject,
...hashed,
};
}

/**
* Hash password for an object if password property exists.
*
* @param object An object containing the new password to hash.
* @param salt Optional salt. If not provided, one will be generated.
* @returns A new object with the password hashed, with salt added.
*/
async hashObjectOptional<T extends Partial<PasswordPlainInterface>>(
object: T,
salt?: string,
): Promise<
Omit<T, 'password'> | (Omit<T, 'password'> & PasswordStorageInterface)
> {
// extract password property
const { password, ...safeObject } = object;

// is the password in the object?
if (typeof password === 'string') {
// hash the password
const hashed = await this.hash(password, salt);

// return the object with password hashed
return {
...safeObject,
...hashed,
};
} else {
return safeObject;
}
}

/**
* Validate if password matches and its valid.
*
Expand Down
30 changes: 5 additions & 25 deletions packages/nestjs-user/src/user.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import {
} from '@concepta/nestjs-crud';
import { ApiTags } from '@nestjs/swagger';
import {
PasswordPlainInterface,
UserCreatableInterface,
UserUpdatableInterface,
} from '@concepta/ts-common';
Expand Down Expand Up @@ -107,7 +106,9 @@ export class UserController
// loop all dtos
for (const userCreateDto of userCreateManyDto.bulk) {
// hash it
hashed.push(await this.maybeHashPassword(userCreateDto));
hashed.push(
await this.passwordStorageService.hashObjectOptional(userCreateDto),
);
}

// call crud service to create
Expand All @@ -129,7 +130,7 @@ export class UserController
// call crud service to create
return this.userCrudService.createOne(
crudRequest,
await this.maybeHashPassword(userCreateDto),
await this.passwordStorageService.hashObjectOptional(userCreateDto),
);
}

Expand All @@ -147,7 +148,7 @@ export class UserController
) {
return this.userCrudService.updateOne(
crudRequest,
await this.maybeHashPassword(userUpdateDto),
await this.passwordStorageService.hashObjectOptional(userUpdateDto),
);
}

Expand All @@ -172,25 +173,4 @@ export class UserController
async recoverOne(@CrudRequest() crudRequest: CrudRequestInterface) {
return this.userCrudService.recoverOne(crudRequest);
}

/**
* Maybe hash passwords on a dto.
*
* @param dto The object on which to hash password.
*/
protected async maybeHashPassword<T>(dto: T | (T & PasswordPlainInterface)) {
// get a password?
if (
dto &&
typeof dto === 'object' &&
'password' in dto &&
typeof dto.password === 'string'
) {
// yes, hash it
return this.passwordStorageService.hashObject(dto);
} else {
// return untouched
return dto;
}
}
}

0 comments on commit f2d7c5c

Please sign in to comment.