Skip to content

Commit 793302f

Browse files
committed
Prevent login if user is disabled
1 parent 90b551e commit 793302f

File tree

4 files changed

+53
-30
lines changed

4 files changed

+53
-30
lines changed

src/common/exceptions/unauthenticated.exception.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,16 @@ import { HttpStatus } from '@nestjs/common';
22
import { ClientException } from './exception';
33

44
/**
5-
* We cannot identify the requester.
5+
* Any authentication-related problem
66
*/
7-
export class UnauthenticatedException extends ClientException {
7+
export abstract class AuthenticationException extends ClientException {
88
readonly status = HttpStatus.UNAUTHORIZED;
9+
}
910

11+
/**
12+
* We cannot identify the requester.
13+
*/
14+
export class UnauthenticatedException extends AuthenticationException {
1015
constructor(message?: string, previous?: Error) {
1116
super(message ?? `Not authenticated`, previous);
1217
}

src/core/authentication/authentication.gel.repository.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -64,17 +64,17 @@ export class AuthenticationGelRepository
6464
},
6565
);
6666

67-
async getPasswordHash({ email }: LoginInput) {
68-
return await this.db.run(this.getPasswordHashQuery, { email });
67+
async getInfoForLogin({ email }: LoginInput) {
68+
return await this.db.run(this.getInfoForLoginQuery, { email });
6969
}
70-
private readonly getPasswordHashQuery = e.params(
70+
private readonly getInfoForLoginQuery = e.params(
7171
{ email: e.str },
72-
({ email }) => {
73-
const identity = e.select(e.Auth.Identity, (identity) => ({
72+
({ email }) =>
73+
e.select(e.Auth.Identity, (identity) => ({
7474
filter_single: e.op(identity.user.email, '=', email),
75-
}));
76-
return identity.passwordHash;
77-
},
75+
passwordHash: true,
76+
status: identity.user.status,
77+
})),
7878
);
7979

8080
async connectSessionToUser(input: LoginInput, session: Session): Promise<ID> {

src/core/authentication/authentication.repository.ts

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common';
22
import { node, relation } from 'cypher-query-builder';
33
import { DateTime } from 'luxon';
44
import { type ID, type Role, ServerException } from '~/common';
5+
import { type UserStatus } from '../../components/user/dto';
56
import { DatabaseService, DbTraceLayer, OnIndex } from '../database';
67
import {
78
ACTIVE,
@@ -97,27 +98,34 @@ export class AuthenticationRepository {
9798
.run();
9899
}
99100

100-
async getPasswordHash(input: LoginInput) {
101+
async getInfoForLogin(input: LoginInput) {
101102
const result = await this.db
102103
.query()
103-
.raw(
104-
`
105-
MATCH
106-
(:EmailAddress {value: $email})
107-
<-[:email {active: true}]-
108-
(user:User)
109-
-[:password {active: true}]->
110-
(password:Property)
111-
RETURN
112-
password.value as pash
113-
`,
114-
{
115-
email: input.email,
116-
},
117-
)
118-
.asResult<{ pash: string }>()
104+
.match([
105+
[
106+
node('email', 'EmailAddress', {
107+
value: input.email,
108+
}),
109+
relation('in', '', 'email', ACTIVE),
110+
node('user', 'User'),
111+
],
112+
[
113+
node('user'),
114+
relation('out', '', 'password', ACTIVE),
115+
node('password', 'Property'),
116+
],
117+
[
118+
node('user'),
119+
relation('out', '', 'status', ACTIVE),
120+
node('status', 'Property'),
121+
],
122+
])
123+
.return<{ passwordHash: string; status: UserStatus }>([
124+
'password.value as passwordHash',
125+
'status.value as status',
126+
])
119127
.first();
120-
return result?.pash ?? null;
128+
return result ?? null;
121129
}
122130

123131
async connectSessionToUser(input: LoginInput, session: Session) {

src/core/authentication/authentication.service.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common';
22
import { ModuleRef } from '@nestjs/core';
33
import { EmailService } from '@seedcompany/nestjs-email';
44
import {
5+
AuthenticationException,
56
DuplicateException,
67
type ID,
78
InputException,
@@ -73,11 +74,14 @@ export class AuthenticationService {
7374
}
7475

7576
async login(input: LoginInput): Promise<ID> {
76-
const hash = await this.repo.getPasswordHash(input);
77+
const info = await this.repo.getInfoForLogin(input);
7778

78-
if (!(await this.crypto.verify(hash, input.password))) {
79+
if (!(await this.crypto.verify(info?.passwordHash, input.password))) {
7980
throw new UnauthenticatedException('Invalid credentials');
8081
}
82+
if (info!.status === 'Disabled') {
83+
throw new UserDisabledException();
84+
}
8185

8286
const userId = await this.repo.connectSessionToUser(
8387
input,
@@ -152,3 +156,9 @@ export class AuthenticationService {
152156
await this.repo.removeAllEmailTokensForEmail(emailToken.email);
153157
}
154158
}
159+
160+
export class UserDisabledException extends AuthenticationException {
161+
constructor() {
162+
super('User is disabled');
163+
}
164+
}

0 commit comments

Comments
 (0)