Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
Unike
/
UnikeForm-api
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
7e09e55c
authored
Sep 25, 2025
by
Augusto
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
MVP - Bug fix: Inactive accounts
parent
e3976b1e
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
402 additions
and
4 deletions
+402
-4
USER_STATUS_IMPLEMENTATION.md
+273
-0
src/app.module.ts
+2
-2
src/modules/auth/auth.module.ts
+11
-2
src/modules/auth/auth.service.ts
+9
-0
src/modules/auth/guards/active-user.guard.ts
+33
-0
src/modules/auth/guards/jwt-active-user.guard.ts
+65
-0
src/modules/auth/strategies/jwt.strategy.ts
+9
-0
No files found.
USER_STATUS_IMPLEMENTATION.md
0 → 100644
View file @
7e09e55c
# User Status Implementation - Blocking Inactive Users
## Overview
This implementation adds comprehensive user status checking to prevent inactive users from accessing the platform. The system now blocks inactive users at multiple levels:
1.
**Login Prevention**
: Inactive users cannot login
2.
**Request Blocking**
: Active users who are deactivated while logged in are blocked on their next request
3.
**Global Protection**
: All protected endpoints check user status on every request
## Implementation Details
### 1. Database Schema
The user schema already includes an
`active`
field:
```
sql
active
:
boolean
(
'active'
).
notNull
().
default
(
true
)
```
### 2. Authentication Service Changes
#### `auth.service.ts` - `validateUser` method
-
Added
`active`
field to user selection query
-
Added check for user active status before password validation
-
Throws
`UnauthorizedException`
if user is inactive
```
typescript
// Check if user is active
if
(
!
userWithPassword
.
active
)
{
throw
new
UnauthorizedException
(
'User account is inactive'
);
}
```
### 3. JWT Strategy Updates
#### `jwt.strategy.ts` - `validate` method
-
Added active status check after user retrieval
-
Throws
`UnauthorizedException`
if user becomes inactive
```
typescript
// Check if user is still active
if
(
!
user
.
active
)
{
throw
new
UnauthorizedException
(
'User account is inactive'
);
}
```
### 4. New Guards
#### `active-user.guard.ts`
-
Standalone guard for checking user active status
-
Fetches fresh user data from database
-
Can be applied to specific routes
#### `jwt-active-user.guard.ts`
-
Combined JWT authentication + active user checking
-
Extends
`AuthGuard('jwt')`
with additional active status validation
-
Applied globally to all protected routes
### 5. Global Guard Implementation
#### `app.module.ts`
-
Replaced
`JwtAuthGuard`
with
`JwtActiveUserGuard`
as global guard
-
All protected routes now automatically check user status
## How It Works
### Login Flow
1.
User attempts to login
2.
`AuthService.validateUser()`
checks if user exists and is active
3.
If inactive, throws
`UnauthorizedException`
with message "User account is inactive"
4.
If active, proceeds with password validation
### Request Flow
1.
User makes authenticated request
2.
`JwtActiveUserGuard`
validates JWT token
3.
Fetches fresh user data from database
4.
Checks if user is still active
5.
If inactive, throws
`UnauthorizedException`
6.
If active, allows request to proceed
### User Deactivation Flow
1.
Admin deactivates user via
`UserService.toggleUserStatus()`
2.
User's next API request triggers fresh database lookup
3.
`JwtActiveUserGuard`
detects inactive status
4.
User receives 401 Unauthorized response
5.
Frontend should redirect to login page
## API Endpoints Affected
All protected endpoints now check user status:
-
`/auth/profile`
- Get user profile
-
`/auth/refresh`
- Refresh token
-
`/users/*`
- User management
-
`/occurrences/*`
- Occurrence management
-
`/comments/*`
- Comment management
-
`/attachments/*`
- Attachment management
-
`/conclusions/*`
- Conclusion management
-
`/assistants/*`
- Assistant management
-
`/dashboard/*`
- Dashboard data
## Error Responses
### Inactive User Login
```
json
{
"statusCode"
:
401
,
"message"
:
"User account is inactive"
,
"error"
:
"Unauthorized"
}
```
### Inactive User Request
```
json
{
"statusCode"
:
401
,
"message"
:
"User account is inactive"
,
"error"
:
"Unauthorized"
}
```
## Testing
### Manual Testing Steps
1.
**Create Test Users**
:
```
bash
# Create active user
POST /users
{
"firstName"
:
"Active"
,
"lastName"
:
"User"
,
"email"
:
"active@example.com"
,
"password"
:
"password123"
,
"active"
:
true
}
# Create inactive user
POST /users
{
"firstName"
:
"Inactive"
,
"lastName"
:
"User"
,
"email"
:
"inactive@example.com"
,
"password"
:
"password123"
,
"active"
:
false
}
```
2.
**
Test Inactive User Login
**
:
```
bash
POST /auth/login
{
"email"
:
"inactive@example.com"
,
"password"
:
"password123"
}
# Should return 401 with "User account is inactive"
```
3.
**
Test Active User Login
**
:
```
bash
POST /auth/login
{
"email"
:
"active@example.com"
,
"password"
:
"password123"
}
# Should return JWT token
```
4.
**
Test User Deactivation
**
:
```
bash
# Login as active user and get token
# Then deactivate user
PUT /users/
{
userId
}
/toggle-status
{
"active"
:
false
}
# Next API call with token should fail
GET /auth/profile
Authorization: Bearer
{
token
}
# Should return 401 with "User account is inactive"
```
### Automated Testing
Run the
test
script:
```
bash
node test-user-status.js
```
## Frontend Integration
The frontend should handle 401 responses by:
1. Clearing stored tokens
2. Redirecting to login page
3. Showing appropriate error message
Example error handling:
```
typescript
if (
error.response?.status === 401 &&
error.response?.data?.message === 'User account is inactive'
) {
// Clear tokens and redirect to login
localStorage.removeItem('token');
router.push('/login');
showMessage('Your account has been deactivated');
}
```
## Security Considerations
1.
**Database Queries**
: Each request fetches fresh user data to ensure real-time status checking
2.
**Token Validation**
: JWT tokens are validated on every request
3.
**Immediate Effect**
: User deactivation takes effect on the next API request
4.
**No Caching**
: User status is not cached to prevent stale data
## Performance Impact
-
**Additional Database Query**
: One extra query per authenticated request to check user status
-
**Minimal Overhead**
: Query is optimized with database indexes on
`active`
field
-
**Acceptable Trade-off**
: Security benefit outweighs performance cost
## Migration Notes
-
**Backward Compatible**
: Existing users remain active by default
-
**No Breaking Changes**
: All existing functionality continues to work
-
**Gradual Rollout**
: Can be enabled/disabled by changing the global guard
## Monitoring
Monitor for:
-
Increased 401 responses after user deactivation
-
Failed login attempts from inactive users
-
User complaints about unexpected logouts
## Troubleshooting
### Common Issues
1.
**Users getting logged out unexpectedly**
:
-
Check if user was deactivated
-
Verify database connection
-
Check JWT token expiration
2.
**Inactive users still able to login**
:
-
Verify
`JwtActiveUserGuard`
is applied globally
-
Check if user was recently activated
-
Verify database query is working
3.
**Performance issues**
:
-
Monitor database query performance
-
Consider adding caching for user status (with careful invalidation)
-
Check database indexes on
`active`
field
src/app.module.ts
View file @
7e09e55c
...
...
@@ -12,7 +12,7 @@ import { AttachmentModule } from './modules/attachment/attachment.module';
import
{
ConclusionModule
}
from
'./modules/conclusion/conclusion.module'
;
import
{
AssistantModule
}
from
'./modules/assistant/assistant.module'
;
import
{
DashboardModule
}
from
'./modules/dashboard/dashboard.module'
;
import
{
JwtA
uthGuard
}
from
'./modules/auth/guards/jwt-auth
.guard'
;
import
{
JwtA
ctiveUserGuard
}
from
'./modules/auth/guards/jwt-active-user
.guard'
;
@
Module
({
imports
:
[
...
...
@@ -35,7 +35,7 @@ import { JwtAuthGuard } from './modules/auth/guards/jwt-auth.guard';
DrizzleService
,
{
provide
:
APP_GUARD
,
useClass
:
JwtA
uthGuard
,
// Apply JWT
guard globally
useClass
:
JwtA
ctiveUserGuard
,
// Apply JWT + Active User
guard globally
},
],
exports
:
[
DrizzleService
],
// Export for use in other modules
...
...
src/modules/auth/auth.module.ts
View file @
7e09e55c
...
...
@@ -8,6 +8,8 @@ import { UserModule } from '../user/user.module';
import
{
DrizzleService
}
from
'../../common/drizzle.service'
;
import
{
JwtStrategy
}
from
'./strategies/jwt.strategy'
;
import
{
JwtAuthGuard
}
from
'./guards/jwt-auth.guard'
;
import
{
ActiveUserGuard
}
from
'./guards/active-user.guard'
;
import
{
JwtActiveUserGuard
}
from
'./guards/jwt-active-user.guard'
;
@
Module
({
imports
:
[
...
...
@@ -25,7 +27,14 @@ import { JwtAuthGuard } from './guards/jwt-auth.guard';
}),
],
controllers
:
[
AuthController
],
providers
:
[
AuthService
,
JwtStrategy
,
JwtAuthGuard
,
DrizzleService
],
exports
:
[
AuthService
,
JwtAuthGuard
],
// Export for use in other modules
providers
:
[
AuthService
,
JwtStrategy
,
JwtAuthGuard
,
ActiveUserGuard
,
JwtActiveUserGuard
,
DrizzleService
,
],
exports
:
[
AuthService
,
JwtAuthGuard
,
ActiveUserGuard
,
JwtActiveUserGuard
],
// Export for use in other modules
})
export
class
AuthModule
{}
src/modules/auth/auth.service.ts
View file @
7e09e55c
...
...
@@ -73,6 +73,7 @@ export class AuthService {
email
:
users
.
email
,
user_role
:
users
.
user_role
,
password
:
users
.
password
,
active
:
users
.
active
,
createdAt
:
users
.
createdAt
,
updatedAt
:
users
.
updatedAt
,
})
...
...
@@ -84,6 +85,11 @@ export class AuthService {
return
null
;
}
// Check if user is active
if
(
!
userWithPassword
.
active
)
{
throw
new
UnauthorizedException
(
'User account is inactive'
);
}
// Validate password
const
isPasswordValid
=
await
bcrypt
.
compare
(
password
,
...
...
@@ -98,6 +104,9 @@ export class AuthService {
const
{
password
:
_
,
...
user
}
=
userWithPassword
;
return
user
;
}
catch
(
error
)
{
if
(
error
instanceof
UnauthorizedException
)
{
throw
error
;
}
return
null
;
}
}
...
...
src/modules/auth/guards/active-user.guard.ts
0 → 100644
View file @
7e09e55c
import
{
Injectable
,
CanActivate
,
ExecutionContext
,
UnauthorizedException
,
}
from
'@nestjs/common'
;
import
{
UserService
}
from
'../../user/user.service'
;
@
Injectable
()
export
class
ActiveUserGuard
implements
CanActivate
{
constructor
(
private
readonly
userService
:
UserService
)
{}
async
canActivate
(
context
:
ExecutionContext
):
Promise
<
boolean
>
{
const
request
=
context
.
switchToHttp
().
getRequest
();
const
user
=
request
.
user
;
if
(
!
user
)
{
throw
new
UnauthorizedException
(
'User not authenticated'
);
}
// Get fresh user data from database to check current status
const
currentUser
=
await
this
.
userService
.
findOne
(
user
.
id
);
if
(
!
currentUser
.
active
)
{
throw
new
UnauthorizedException
(
'User account is inactive'
);
}
// Update the request user with fresh data
request
.
user
=
currentUser
;
return
true
;
}
}
src/modules/auth/guards/jwt-active-user.guard.ts
0 → 100644
View file @
7e09e55c
import
{
Injectable
,
ExecutionContext
,
UnauthorizedException
,
}
from
'@nestjs/common'
;
import
{
Reflector
}
from
'@nestjs/core'
;
import
{
AuthGuard
}
from
'@nestjs/passport'
;
import
{
Observable
}
from
'rxjs'
;
import
{
UserService
}
from
'../../user/user.service'
;
@
Injectable
()
export
class
JwtActiveUserGuard
extends
AuthGuard
(
'jwt'
)
{
constructor
(
private
reflector
:
Reflector
,
private
readonly
userService
:
UserService
,
)
{
super
();
}
async
canActivate
(
context
:
ExecutionContext
):
Promise
<
boolean
>
{
// Check if the route is marked as public
const
isPublic
=
this
.
reflector
.
getAllAndOverride
<
boolean
>
(
'isPublic'
,
[
context
.
getHandler
(),
context
.
getClass
(),
]);
if
(
isPublic
)
{
return
true
;
// Skip authentication for public routes
}
// First, perform JWT authentication
const
isAuthenticated
=
await
super
.
canActivate
(
context
);
if
(
!
isAuthenticated
)
{
return
false
;
}
// Get the user from the request
const
request
=
context
.
switchToHttp
().
getRequest
();
const
user
=
request
.
user
;
if
(
!
user
)
{
return
false
;
}
// Check if user is still active
try
{
const
currentUser
=
await
this
.
userService
.
findOne
(
user
.
id
);
if
(
!
currentUser
.
active
)
{
throw
new
UnauthorizedException
(
'User account is inactive'
);
}
// Update the request user with fresh data
request
.
user
=
currentUser
;
return
true
;
}
catch
(
error
)
{
if
(
error
instanceof
UnauthorizedException
)
{
throw
error
;
}
throw
new
UnauthorizedException
(
'Unable to verify user status'
);
}
}
}
src/modules/auth/strategies/jwt.strategy.ts
View file @
7e09e55c
...
...
@@ -32,11 +32,20 @@ export class JwtStrategy extends PassportStrategy(Strategy) {
async
validate
(
payload
:
JwtPayload
)
{
try
{
const
user
=
await
this
.
userService
.
findOne
(
payload
.
sub
);
// Check if user is still active
if
(
!
user
.
active
)
{
throw
new
UnauthorizedException
(
'User account is inactive'
);
}
return
user
;
// This will be available as req.user
}
catch
(
error
)
{
if
(
error
instanceof
NotFoundException
)
{
throw
new
UnauthorizedException
(
'User not found'
);
}
if
(
error
instanceof
UnauthorizedException
)
{
throw
error
;
}
throw
new
UnauthorizedException
(
'Invalid token'
);
}
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment