Skip to content

Commit

Permalink
Merge pull request #132 from conceptadev/feature/user-active
Browse files Browse the repository at this point in the history
feat(user): user active flag
  • Loading branch information
MrMaz authored Oct 27, 2023
2 parents 872592a + 2afce63 commit 884ae16
Show file tree
Hide file tree
Showing 27 changed files with 273 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export const LOGIN_SUCCESS = {

export const USER_SUCCESS: AuthLocalCredentialsInterface = {
id: randomUUID(),
active: true,
passwordHash: LOGIN_SUCCESS.password,
passwordSalt: LOGIN_SUCCESS.password,
username: LOGIN_SUCCESS.username,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export class UserFixture
{
id!: string;
username!: string;
active!: boolean;
password!: string;
passwordHash!: string | null;
passwordSalt!: string | null;
Expand Down
6 changes: 6 additions & 0 deletions packages/nestjs-auth-local/src/auth-local.constants.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
export const AUTH_LOCAL_MODULE_ISSUE_TOKEN_SERVICE_TOKEN =
'AUTH_LOCAL_MODULE_ISSUE_TOKEN_SERVICE_TOKEN';

export const AUTH_LOCAL_MODULE_VALIDATE_USER_SERVICE_TOKEN =
'AUTH_LOCAL_MODULE_VALIDATE_USER_SERVICE_TOKEN';

export const AUTH_LOCAL_MODULE_SETTINGS_TOKEN =
'AUTH_LOCAL_MODULE_SETTINGS_TOKEN';

Expand All @@ -10,4 +13,7 @@ export const AUTH_LOCAL_MODULE_DEFAULT_SETTINGS_TOKEN =
export const AUTH_LOCAL_MODULE_USER_LOOKUP_SERVICE_TOKEN =
'AUTH_LOCAL_MODULE_USER_LOOKUP_SERVICE_TOKEN';

export const AUTH_LOCAL_MODULE_PASSWORD_STORAGE_SERVICE_TOKEN =
'AUTH_LOCAL_MODULE_PASSWORD_STORAGE_SERVICE_TOKEN';

export const AUTH_LOCAL_STRATEGY_NAME = 'local';
54 changes: 53 additions & 1 deletion packages/nestjs-auth-local/src/auth-local.module-definition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,21 @@ import {
import { ConfigModule } from '@nestjs/config';

import { createSettingsProvider } from '@concepta/nestjs-common';
import { PasswordStorageService } from '@concepta/nestjs-password';
import {
PasswordStorageService,
PasswordStorageServiceInterface,
} from '@concepta/nestjs-password';
import {
IssueTokenService,
IssueTokenServiceInterface,
} from '@concepta/nestjs-authentication';

import {
AUTH_LOCAL_MODULE_ISSUE_TOKEN_SERVICE_TOKEN,
AUTH_LOCAL_MODULE_PASSWORD_STORAGE_SERVICE_TOKEN,
AUTH_LOCAL_MODULE_SETTINGS_TOKEN,
AUTH_LOCAL_MODULE_USER_LOOKUP_SERVICE_TOKEN,
AUTH_LOCAL_MODULE_VALIDATE_USER_SERVICE_TOKEN,
} from './auth-local.constants';

import { AuthLocalOptionsExtrasInterface } from './interfaces/auth-local-options-extras.interface';
Expand All @@ -24,6 +29,8 @@ import { AuthLocalSettingsInterface } from './interfaces/auth-local-settings.int
import { authLocalDefaultConfig } from './config/auth-local-default.config';
import { AuthLocalController } from './auth-local.controller';
import { AuthLocalStrategy } from './auth-local.strategy';
import { AuthLocalValidateUserService } from './services/auth-local-validate-user.service';
import { AuthLocalUserLookupServiceInterface } from './interfaces/auth-local-user-lookup-service.interface';

const RAW_OPTIONS_TOKEN = Symbol('__AUTH_LOCAL_MODULE_RAW_OPTIONS_TOKEN__');

Expand Down Expand Up @@ -73,6 +80,8 @@ export function createAuthLocalExports(): string[] {
AUTH_LOCAL_MODULE_SETTINGS_TOKEN,
AUTH_LOCAL_MODULE_USER_LOOKUP_SERVICE_TOKEN,
AUTH_LOCAL_MODULE_ISSUE_TOKEN_SERVICE_TOKEN,
AUTH_LOCAL_MODULE_VALIDATE_USER_SERVICE_TOKEN,
AUTH_LOCAL_MODULE_PASSWORD_STORAGE_SERVICE_TOKEN,
];
}

Expand All @@ -85,9 +94,12 @@ export function createAuthLocalProviders(options: {
IssueTokenService,
PasswordStorageService,
AuthLocalStrategy,
AuthLocalValidateUserService,
createAuthLocalOptionsProvider(options.overrides),
createAuthLocalValidateUserServiceProvider(options.overrides),
createAuthLocalIssueTokenServiceProvider(options.overrides),
createAuthLocalUserLookupServiceProvider(options.overrides),
createAuthLocalPasswordStorageServiceProvider(options.overrides),
];
}

Expand All @@ -113,6 +125,30 @@ export function createAuthLocalOptionsProvider(
});
}

export function createAuthLocalValidateUserServiceProvider(
optionsOverrides?: AuthLocalOptions,
): Provider {
return {
provide: AUTH_LOCAL_MODULE_VALIDATE_USER_SERVICE_TOKEN,
inject: [
RAW_OPTIONS_TOKEN,
AUTH_LOCAL_MODULE_USER_LOOKUP_SERVICE_TOKEN,
AUTH_LOCAL_MODULE_PASSWORD_STORAGE_SERVICE_TOKEN,
],
useFactory: async (
options: AuthLocalOptionsInterface,
userLookupService: AuthLocalUserLookupServiceInterface,
passwordStorageService: PasswordStorageServiceInterface,
) =>
optionsOverrides?.validateUserService ??
options.validateUserService ??
new AuthLocalValidateUserService(
userLookupService,
passwordStorageService,
),
};
}

export function createAuthLocalIssueTokenServiceProvider(
optionsOverrides?: AuthLocalOptions,
): Provider {
Expand All @@ -129,6 +165,22 @@ export function createAuthLocalIssueTokenServiceProvider(
};
}

export function createAuthLocalPasswordStorageServiceProvider(
optionsOverrides?: AuthLocalOptions,
): Provider {
return {
provide: AUTH_LOCAL_MODULE_PASSWORD_STORAGE_SERVICE_TOKEN,
inject: [RAW_OPTIONS_TOKEN, PasswordStorageService],
useFactory: async (
options: AuthLocalOptionsInterface,
defaultService: PasswordStorageServiceInterface,
) =>
optionsOverrides?.passwordStorageService ??
options.passwordStorageService ??
defaultService,
};
}

export function createAuthLocalUserLookupServiceProvider(
optionsOverrides?: AuthLocalOptions,
): Provider {
Expand Down
11 changes: 11 additions & 0 deletions packages/nestjs-auth-local/src/auth-local.module.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ import {
IssueTokenService,
IssueTokenServiceInterface,
} from '@concepta/nestjs-authentication';
import {
PasswordStorageService,
PasswordStorageServiceInterface,
} from '@concepta/nestjs-password';

import {
AUTH_LOCAL_MODULE_ISSUE_TOKEN_SERVICE_TOKEN,
Expand All @@ -30,6 +34,7 @@ import { AuthLocalSettingsInterface } from './interfaces/auth-local-settings.int

import { UserLookupServiceFixture } from './__fixtures__/user/user-lookup.service.fixture';
import { UserModuleFixture } from './__fixtures__/user/user.module.fixture';
import { AuthLocalValidateUserService } from './services/auth-local-validate-user.service';

describe(AuthLocalModule, () => {
const jwtAccessService = new NestJwtService();
Expand All @@ -43,7 +48,9 @@ describe(AuthLocalModule, () => {
let testModule: TestingModule;
let authLocalModule: AuthLocalModule;
let userLookupService: AuthLocalUserLookupServiceInterface;
let validateUserService: AuthLocalUserLookupServiceInterface;
let issueTokenService: IssueTokenServiceInterface;
let passwordStorageService: PasswordStorageServiceInterface;

describe(AuthLocalModule.forRoot, () => {
beforeEach(async () => {
Expand Down Expand Up @@ -194,13 +201,17 @@ describe(AuthLocalModule, () => {
function commonVars(module: TestingModule) {
authLocalModule = module.get(AuthLocalModule);
userLookupService = module.get(UserLookupServiceFixture);
validateUserService = module.get(AuthLocalValidateUserService);
issueTokenService = module.get(IssueTokenService);
passwordStorageService = module.get(PasswordStorageService);
}

function commonTests() {
expect(authLocalModule).toBeInstanceOf(AuthLocalModule);
expect(userLookupService).toBeInstanceOf(UserLookupServiceFixture);
expect(issueTokenService).toBeInstanceOf(IssueTokenService);
expect(passwordStorageService).toBeInstanceOf(PasswordStorageService);
expect(validateUserService).toBeInstanceOf(AuthLocalValidateUserService);
}
});

Expand Down
34 changes: 12 additions & 22 deletions packages/nestjs-auth-local/src/auth-local.strategy.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import { mock } from 'jest-mock-extended';
import { AuthLocalStrategy } from './auth-local.strategy';
import { AuthLocalSettingsInterface } from './interfaces/auth-local-settings.interface';
import { AuthLocalUserLookupServiceInterface } from './interfaces/auth-local-user-lookup-service.interface';
import { AuthLocalValidateUserServiceInterface } from './interfaces/auth-local-validate-user-service.interface';
import { AuthLocalValidateUserService } from './services/auth-local-validate-user.service';

import { UserFixture } from './__fixtures__/user/user.entity.fixture';

describe(AuthLocalStrategy, () => {
Expand All @@ -14,6 +17,7 @@ describe(AuthLocalStrategy, () => {
let user: UserFixture;
let settings: AuthLocalSettingsInterface;
let userLookUpService: AuthLocalUserLookupServiceInterface;
let validateUserService: AuthLocalValidateUserServiceInterface;
let passwordStorageService: PasswordStorageService;
let authLocalStrategy: AuthLocalStrategy;

Expand All @@ -26,26 +30,23 @@ describe(AuthLocalStrategy, () => {

userLookUpService = mock<AuthLocalUserLookupServiceInterface>();
passwordStorageService = mock<PasswordStorageService>();
authLocalStrategy = new AuthLocalStrategy(
settings,
validateUserService = new AuthLocalValidateUserService(
userLookUpService,
passwordStorageService,
);
authLocalStrategy = new AuthLocalStrategy(settings, validateUserService);

user = new UserFixture();
user.id = randomUUID();
user.active = true;
jest.spyOn(userLookUpService, 'byUsername').mockResolvedValue(user);
});

it('constructor', async () => {
settings = mock<Partial<AuthLocalSettingsInterface>>({
loginDto: undefined,
});
authLocalStrategy = new AuthLocalStrategy(
settings,
userLookUpService,
passwordStorageService,
);
authLocalStrategy = new AuthLocalStrategy(settings, validateUserService);
expect(true).toBeTruthy();
});

Expand Down Expand Up @@ -95,11 +96,7 @@ describe(AuthLocalStrategy, () => {
usernameField: USERNAME,
passwordField: PASSWORD,
});
authLocalStrategy = new AuthLocalStrategy(
settings,
userLookUpService,
passwordStorageService,
);
authLocalStrategy = new AuthLocalStrategy(settings, validateUserService);
const t = () => authLocalStrategy['assertSettings']();
expect(t).toThrowError();
});
Expand All @@ -110,25 +107,18 @@ describe(AuthLocalStrategy, () => {
usernameField: undefined,
passwordField: PASSWORD,
});
authLocalStrategy = new AuthLocalStrategy(
settings,
userLookUpService,
passwordStorageService,
);
authLocalStrategy = new AuthLocalStrategy(settings, validateUserService);
const t = () => authLocalStrategy['assertSettings']();
expect(t).toThrowError();
});

it('should throw error for no passwordField', async () => {
settings = mock<Partial<AuthLocalSettingsInterface>>({
loginDto: UserFixture,
usernameField: USERNAME,
passwordField: undefined,
});
authLocalStrategy = new AuthLocalStrategy(
settings,
userLookUpService,
passwordStorageService,
);
authLocalStrategy = new AuthLocalStrategy(settings, validateUserService);
const t = () => authLocalStrategy['assertSettings']();
expect(t).toThrowError();
});
Expand Down
39 changes: 20 additions & 19 deletions packages/nestjs-auth-local/src/auth-local.strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,17 @@ import {
Injectable,
UnauthorizedException,
} from '@nestjs/common';
import { ReferenceUsername } from '@concepta/ts-core';
import { ReferenceIdInterface, ReferenceUsername } from '@concepta/ts-core';
import { PassportStrategyFactory } from '@concepta/nestjs-authentication';
import { PasswordStorageService } from '@concepta/nestjs-password';

import {
AUTH_LOCAL_MODULE_SETTINGS_TOKEN,
AUTH_LOCAL_MODULE_USER_LOOKUP_SERVICE_TOKEN,
AUTH_LOCAL_MODULE_VALIDATE_USER_SERVICE_TOKEN,
AUTH_LOCAL_STRATEGY_NAME,
} from './auth-local.constants';

import { AuthLocalSettingsInterface } from './interfaces/auth-local-settings.interface';
import { AuthLocalUserLookupServiceInterface } from './interfaces/auth-local-user-lookup-service.interface';
import { AuthLocalValidateUserServiceInterface } from './interfaces/auth-local-validate-user-service.interface';

/**
* Define the Local strategy using passport.
Expand All @@ -34,14 +33,13 @@ export class AuthLocalStrategy extends PassportStrategyFactory<Strategy>(
*
* @param userLookupService The service used to get the user
* @param settings The settings for the local strategy
* @param passwordService The service used to hash and validate passwords
* @param passwordStorageService The service used to hash and validate passwords
*/
constructor(
@Inject(AUTH_LOCAL_MODULE_SETTINGS_TOKEN)
private settings: AuthLocalSettingsInterface,
@Inject(AUTH_LOCAL_MODULE_USER_LOOKUP_SERVICE_TOKEN)
private userLookupService: AuthLocalUserLookupServiceInterface,
private passwordService: PasswordStorageService,
@Inject(AUTH_LOCAL_MODULE_VALIDATE_USER_SERVICE_TOKEN)
private validateUserService: AuthLocalValidateUserServiceInterface,
) {
super({
usernameField: settings?.usernameField,
Expand Down Expand Up @@ -71,21 +69,24 @@ export class AuthLocalStrategy extends PassportStrategyFactory<Strategy>(
throw new BadRequestException(e);
}

const user = await this.userLookupService.byUsername(dto[usernameField]);
let validatedUser: ReferenceIdInterface;

if (!user) {
try {
// try to get fully validated user
validatedUser = await this.validateUserService.validateUser({
username,
password,
});
// did we get a valid user?
if (!validatedUser) {
throw new Error(`No valid user found: ${username}`);
}
} catch (e) {
// TODO: maybe log original?
throw new UnauthorizedException();
}

// validate password
const isValid = await this.passwordService.validateObject(
dto[passwordField],
user,
);

if (!isValid) throw new UnauthorizedException();

return user;
return validatedUser;
}

/**
Expand Down
2 changes: 2 additions & 0 deletions packages/nestjs-auth-local/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export { AuthLocalValidateUserInterface } from './interfaces/auth-local-validate-user.interface';
export { AuthLocalUserLookupServiceInterface } from './interfaces/auth-local-user-lookup-service.interface';
export { AuthLocalValidateUserServiceInterface } from './interfaces/auth-local-validate-user-service.interface';

export * from './auth-local.module';
export * from './auth-local.controller';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
ReferenceActiveInterface,
ReferenceIdInterface,
ReferenceUsernameInterface,
} from '@concepta/ts-core';
Expand All @@ -10,4 +11,5 @@ import { PasswordStorageInterface } from '@concepta/nestjs-password';
export interface AuthLocalCredentialsInterface
extends ReferenceIdInterface,
ReferenceUsernameInterface,
ReferenceActiveInterface,
PasswordStorageInterface {}
Loading

0 comments on commit 884ae16

Please sign in to comment.