Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/password by role #347

Open
wants to merge 12 commits into
base: main
Choose a base branch
from

Conversation

tnramalho
Copy link
Collaborator

Defining password strength based on role

* main:
  feat: move event manager options to contructor
  feat: fix tsdoc type
  feat: implement event manager on dependent modules
  feat: add event manager
@MrMaz
Copy link
Collaborator

MrMaz commented Feb 6, 2025

Just a couple of high level things before deeper review of the latest commits.

  • You have UserRoleService hard coded on the providers.
  • The normalizeRoleNames is something the implementations need to do. The service should return the entire role object by default.

import { RoleInterface } from './role.interface';

export interface RoleOwnableInterface {
roleId?: ReferenceId;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

roleId property should be required

Suggested change
roleId?: ReferenceId;
roleId: ReferenceId;

import { RoleOwnableInterface } from '../../role/interfaces/role-ownable.interface';

export interface PasswordStrengthTransformOptionsInterface {
roles: RoleOwnableInterface[];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

roles propert should be optional

Suggested change
roles: RoleOwnableInterface[];
roles?: RoleOwnableInterface[];

* Optional password strength requirement. If provided, will validate
* that password meets minimum strength requirements.
*/
passwordStrength?: PasswordStrengthEnum | null;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

undefined is better

Suggested change
passwordStrength?: PasswordStrengthEnum | null;
passwordStrength?: PasswordStrengthEnum | undefined;

* Password Strength Options Interface
*/
export interface PasswordStrengthOptionsInterface {
passwordStrength?: PasswordStrengthEnum | null;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

undefined is better

Suggested change
passwordStrength?: PasswordStrengthEnum | null;
passwordStrength?: PasswordStrengthEnum | undefined;

Comment on lines 84 to 86
!this.passwordStrengthService.isStrong(password, {
passwordStrength: options?.passwordStrength,
})
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we have a try/catch here?

): boolean {
const { passwordStrength } = options || {};

// TODO: Should we allow overriding the minimum password strength even if the provided strength is lower than the configured minimum?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did you align with Gabriel on this point?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, what ever user define with the roles should overwrite

Comment on lines 40 to 42
passwordStrength ||
this.settings?.minPasswordStrength ||
PasswordStrengthEnum.None;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

coalesce is safer i think in case strength is explicitly zero? recommend test coverage to be sure.

Suggested change
passwordStrength ||
this.settings?.minPasswordStrength ||
PasswordStrengthEnum.None;
passwordStrength ??
this.settings?.minPasswordStrength ??
PasswordStrengthEnum.None;

*/
getUserRoles(
userDto: UserRolesInterface,
userToUpdateId?: ReferenceId,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think this is redundant, it would always be the id in the UserDto

Copy link
Collaborator Author

@tnramalho tnramalho Feb 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not always, on create user for example we wont have a userId inside dto, however now that i review maybe we can remove the dto from this function, and keep function only to get roles based on user id, like
image
then i can verify before i call this function if i actually need it
this.userRoleService.resolvePasswordStrength

* @returns Array of role names, or empty array if no roles found
*/
getUserRoles(
userDto: UserRolesInterface,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should require the user id on the object?

Suggested change
userDto: UserRolesInterface,
userDto: ReferenceIdInterface & UserRolesInterface,

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe we dont even need that object anymore, it was only being used to get the userRoles of it

*/
resolvePasswordStrength(
roles?: RoleOwnableInterface[],
): PasswordStrengthEnum | null | undefined;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think null doesn't make sense

Suggested change
): PasswordStrengthEnum | null | undefined;
): PasswordStrengthEnum | undefined;

@@ -45,7 +46,7 @@ export class UserMutateService
}

protected async transform<T extends DeepPartial<UserEntityInterface>>(
user: T | (T & PasswordPlainInterface),
user: T | (T & PasswordPlainInterface & UserRolesInterface),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

adding roles interface like this is slightly weird because the only property is optional, we need to review this.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we need this to pass to getPasswordStrength to be able to get strength based on roles

Comment on lines 46 to 48
@Optional()
@Inject(UserRoleService)
private userRoleService?: UserRoleServiceInterface,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be last parameter for better back compat

Comment on lines 137 to 138
this.userRoleService.getUserRoles &&
this.userRoleService.resolvePasswordStrength
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if you need to check that these methods exist, is a weird smell

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

true, i had injected wrong service, thats why lol,

Comment on lines 140 to 146
const roles = await this.userRoleService.getUserRoles(
userDto,
userToUpdateId,
);
passwordStrength = await this.userRoleService.resolvePasswordStrength(
roles,
);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should only be making one call to the service, resolving the roles here is not necessary

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets discuss this one

Comment on lines 31 to 32
const user: (ReferenceIdInterface<string> & UserRolesInterface) | null =
await this.userLookupService.byId(userToUpdateId);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

needs a try/catch?

if (userToUpdateId) {
const user: (ReferenceIdInterface<string> & UserRolesInterface) | null =
await this.userLookupService.byId(userToUpdateId);
if (user && user.userRoles) return user.userRoles;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please use brackets

}

// get roles from payload
if (userDto.userRoles && userDto.userRoles.length > 0)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please use brackets

return (
roles &&
this.userSettings.passwordStrength?.passwordStrengthTransform &&
this.userSettings.passwordStrength?.passwordStrengthTransform({ roles })
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

must have a try/catch on this

export enum UserResource {
'One' = 'user',
'Many' = 'user-list',
}

export type PasswordStrengthTransform = (
options: PasswordStrengthTransformOptionsInterface,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

options parameter should be optional

Suggested change
options: PasswordStrengthTransformOptionsInterface,
options?: PasswordStrengthTransformOptionsInterface,


export type PasswordStrengthTransform = (
options: PasswordStrengthTransformOptionsInterface,
) => PasswordStrengthEnum | null;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

undefined is better than null

Suggested change
) => PasswordStrengthEnum | null;
) => PasswordStrengthEnum | undefined;


export const defaultPasswordStrengthTransform: PasswordStrengthTransform = (
options: PasswordStrengthTransformOptionsInterface,
): PasswordStrengthEnum | null => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

undefined is better than null

Suggested change
): PasswordStrengthEnum | null => {
): PasswordStrengthEnum | undefined => {

import { PasswordStrengthTransformOptionsInterface } from '@concepta/nestjs-common';

export const defaultPasswordStrengthTransform: PasswordStrengthTransform = (
options: PasswordStrengthTransformOptionsInterface,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

options parameter should be optional

Suggested change
options: PasswordStrengthTransformOptionsInterface,
options?: PasswordStrengthTransformOptionsInterface,

): PasswordStrengthEnum | null => {
const { roles } = options;

if (roles.some((role) => role.role?.name === 'admin')) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

role param in lambda needs to be typed

if (roles.some((role) => role.role?.name === 'admin')) {
return PasswordStrengthEnum.VeryStrong;
}
if (roles.some((role) => role.role?.name === 'user')) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

role param in lambda needs to be typed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants