Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
F
finwise-miniapp-be
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
ThinhNC
finwise-miniapp-be
Commits
a1e1b9d4
Commit
a1e1b9d4
authored
Jul 01, 2026
by
ThinhNC
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: get ip & userAgent + validation
parent
8d3bead5
Changes
8
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
79 additions
and
10 deletions
+79
-10
.env.example
.env.example
+1
-1
migration.sql
...123327_optimize_indexes_and_add_device_info/migration.sql
+42
-0
schema.prisma
prisma/schema.prisma
+15
-0
app.ts
src/app.ts
+2
-0
auth.controller.ts
src/modules/auth/auth.controller.ts
+6
-2
auth.repository.ts
src/modules/auth/auth.repository.ts
+3
-1
auth.service.ts
src/modules/auth/auth.service.ts
+9
-5
auth.validation.ts
src/modules/auth/auth.validation.ts
+1
-1
No files found.
.env.example
View file @
a1e1b9d4
NODE_ENV=development
PORT=3000
DATABASE_URL=postgresql://postgres.[project-ref]:[password]@aws-1-[region].pooler.supabase.com:6543/postgres
DATABASE_URL=postgresql://postgres.[project-ref]:[password]@aws-1-[region].pooler.supabase.com:6543/postgres
?pgbouncer=true
DIRECT_URL=postgresql://postgres:[password]@db.[project-ref].supabase.co:5432/postgres
DB_HOST=aws-1-[region].pooler.supabase.com
DB_PORT=6543
...
...
prisma/migrations/20260701123327_optimize_indexes_and_add_device_info/migration.sql
0 → 100644
View file @
a1e1b9d4
-- AlterTable
ALTER
TABLE
"refresh_tokens"
ADD
COLUMN
"ip_address"
TEXT
,
ADD
COLUMN
"user_agent"
TEXT
;
-- CreateIndex
CREATE
INDEX
"applications_approved_by_idx"
ON
"applications"
(
"approved_by"
);
-- CreateIndex
CREATE
INDEX
"daily_reports_intern_id_idx"
ON
"daily_reports"
(
"intern_id"
);
-- CreateIndex
CREATE
INDEX
"intern_profiles_leader_id_idx"
ON
"intern_profiles"
(
"leader_id"
);
-- CreateIndex
CREATE
INDEX
"notification_logs_notification_id_idx"
ON
"notification_logs"
(
"notification_id"
);
-- CreateIndex
CREATE
INDEX
"notifications_user_id_idx"
ON
"notifications"
(
"user_id"
);
-- CreateIndex
CREATE
INDEX
"refresh_tokens_user_id_idx"
ON
"refresh_tokens"
(
"user_id"
);
-- CreateIndex
CREATE
INDEX
"task_assignments_intern_id_idx"
ON
"task_assignments"
(
"intern_id"
);
-- CreateIndex
CREATE
INDEX
"task_assignments_assigned_by_idx"
ON
"task_assignments"
(
"assigned_by"
);
-- CreateIndex
CREATE
INDEX
"task_submissions_assignment_id_idx"
ON
"task_submissions"
(
"assignment_id"
);
-- CreateIndex
CREATE
INDEX
"task_submissions_reviewed_by_idx"
ON
"task_submissions"
(
"reviewed_by"
);
-- CreateIndex
CREATE
INDEX
"tasks_created_by_idx"
ON
"tasks"
(
"created_by"
);
-- CreateIndex
CREATE
INDEX
"users_role_id_idx"
ON
"users"
(
"role_id"
);
-- CreateIndex
CREATE
INDEX
"weekly_evaluations_leader_id_idx"
ON
"weekly_evaluations"
(
"leader_id"
);
prisma/schema.prisma
View file @
a1e1b9d4
...
...
@@ -47,6 +47,7 @@ model User {
notifications
Notification
[]
refreshTokens
RefreshToken
[]
@@
index
([
roleId
])
@@
map
(
"users"
)
}
...
...
@@ -74,6 +75,7 @@ model Application {
approver
User
?
@
relation
(
"ApprovedApplications"
,
fields
:
[
approvedBy
],
references
:
[
id
],
onDelete
:
SetNull
)
@@
index
([
approvedBy
])
@@
map
(
"applications"
)
}
...
...
@@ -108,6 +110,7 @@ model InternProfile {
dailyReports
DailyReport
[]
weeklyEvaluations
WeeklyEvaluation
[]
@@
index
([
leaderId
])
@@
map
(
"intern_profiles"
)
}
...
...
@@ -146,6 +149,7 @@ model Task {
creator
User
@
relation
(
"CreatedTasks"
,
fields
:
[
createdBy
],
references
:
[
id
],
onDelete
:
Cascade
)
assignment
TaskAssignment
?
@@
index
([
createdBy
])
@@
map
(
"tasks"
)
}
...
...
@@ -171,6 +175,8 @@ model TaskAssignment {
assigner
User
@
relation
(
"AssignedTasks"
,
fields
:
[
assignedBy
],
references
:
[
id
],
onDelete
:
Cascade
)
submissions
TaskSubmission
[]
@@
index
([
internId
])
@@
index
([
assignedBy
])
@@
map
(
"task_assignments"
)
}
...
...
@@ -198,6 +204,8 @@ model TaskSubmission {
assignment
TaskAssignment
@
relation
(
fields
:
[
assignmentId
],
references
:
[
id
],
onDelete
:
Cascade
)
reviewer
User
?
@
relation
(
"ReviewedSubmissions"
,
fields
:
[
reviewedBy
],
references
:
[
id
],
onDelete
:
SetNull
)
@@
index
([
assignmentId
])
@@
index
([
reviewedBy
])
@@
map
(
"task_submissions"
)
}
...
...
@@ -213,6 +221,7 @@ model DailyReport {
intern
InternProfile
@
relation
(
fields
:
[
internId
],
references
:
[
id
],
onDelete
:
Cascade
)
@@
index
([
internId
])
@@
map
(
"daily_reports"
)
}
...
...
@@ -235,6 +244,7 @@ model WeeklyEvaluation {
leader
User
@
relation
(
"LeaderEvaluations"
,
fields
:
[
leaderId
],
references
:
[
id
],
onDelete
:
Cascade
)
@@
unique
([
internId
,
week
])
@@
index
([
leaderId
])
@@
map
(
"weekly_evaluations"
)
}
...
...
@@ -252,6 +262,7 @@ model Notification {
user
User
@
relation
(
fields
:
[
userId
],
references
:
[
id
],
onDelete
:
Cascade
)
logs
NotificationLog
[]
@@
index
([
userId
])
@@
map
(
"notifications"
)
}
...
...
@@ -276,6 +287,7 @@ model NotificationLog {
notification
Notification
@
relation
(
fields
:
[
notificationId
],
references
:
[
id
],
onDelete
:
Cascade
)
@@
index
([
notificationId
])
@@
map
(
"notification_logs"
)
}
...
...
@@ -284,10 +296,13 @@ model RefreshToken {
id
String
@
id
@
default
(
uuid
())
@
db
.
Uuid
token
String
@
unique
userId
String
@
db
.
Uuid
@
map
(
"user_id"
)
userAgent
String
?
@
map
(
"user_agent"
)
ipAddress
String
?
@
map
(
"ip_address"
)
expiresAt
DateTime
@
map
(
"expires_at"
)
createdAt
DateTime
@
default
(
now
())
@
map
(
"created_at"
)
user
User
@
relation
(
fields
:
[
userId
],
references
:
[
id
],
onDelete
:
Cascade
)
@@
index
([
userId
])
@@
map
(
"refresh_tokens"
)
}
src/app.ts
View file @
a1e1b9d4
...
...
@@ -7,6 +7,8 @@ import routes from './routes';
const
app
=
express
();
app
.
set
(
'trust proxy'
,
true
);
app
.
use
(
helmet
());
app
.
use
(
cors
());
app
.
use
(
morgan
(
'dev'
));
...
...
src/modules/auth/auth.controller.ts
View file @
a1e1b9d4
...
...
@@ -7,7 +7,9 @@ export class AuthController {
login
=
async
(
req
:
Request
,
res
:
Response
,
next
:
NextFunction
)
=>
{
try
{
const
{
email
,
password
}
=
req
.
body
;
const
result
=
await
this
.
service
.
login
(
email
,
password
);
const
userAgent
=
req
.
headers
[
'user-agent'
];
const
ipAddress
=
req
.
ip
;
const
result
=
await
this
.
service
.
login
(
email
,
password
,
{
userAgent
,
ipAddress
});
res
.
json
({
success
:
true
,
...
...
@@ -34,7 +36,9 @@ export class AuthController {
refresh
=
async
(
req
:
Request
,
res
:
Response
,
next
:
NextFunction
)
=>
{
try
{
const
{
refreshToken
}
=
req
.
body
;
const
result
=
await
this
.
service
.
refresh
(
refreshToken
);
const
userAgent
=
req
.
headers
[
'user-agent'
];
const
ipAddress
=
req
.
ip
;
const
result
=
await
this
.
service
.
refresh
(
refreshToken
,
{
userAgent
,
ipAddress
});
res
.
json
({
success
:
true
,
...
...
src/modules/auth/auth.repository.ts
View file @
a1e1b9d4
...
...
@@ -15,12 +15,14 @@ export class AuthRepository {
});
}
async
saveRefreshToken
(
userId
:
string
,
token
:
string
,
expiresAt
:
Date
)
{
async
saveRefreshToken
(
userId
:
string
,
token
:
string
,
expiresAt
:
Date
,
userAgent
?:
string
,
ipAddress
?:
string
)
{
return
prisma
.
refreshToken
.
create
({
data
:
{
userId
,
token
,
expiresAt
,
userAgent
,
ipAddress
,
},
});
}
...
...
src/modules/auth/auth.service.ts
View file @
a1e1b9d4
...
...
@@ -8,7 +8,7 @@ import { jwtConfig } from '../../config/jwt.config';
export
class
AuthService
{
private
readonly
repository
=
new
AuthRepository
();
async
login
(
email
:
string
,
password
:
string
)
{
async
login
(
email
:
string
,
password
:
string
,
metadata
?:
{
userAgent
?:
string
;
ipAddress
?:
string
}
)
{
const
user
=
await
this
.
repository
.
findByEmail
(
email
);
if
(
!
user
)
{
...
...
@@ -37,7 +37,7 @@ export class AuthService {
const
decoded
=
jwt
.
decode
(
refreshToken
)
as
{
exp
:
number
};
const
expiresAt
=
new
Date
(
decoded
.
exp
*
1000
);
await
this
.
repository
.
saveRefreshToken
(
user
.
id
,
refreshToken
,
expiresAt
);
await
this
.
repository
.
saveRefreshToken
(
user
.
id
,
refreshToken
,
expiresAt
,
metadata
?.
userAgent
,
metadata
?.
ipAddress
);
return
{
accessToken
,
...
...
@@ -50,7 +50,7 @@ export class AuthService {
};
}
async
refresh
(
token
:
string
)
{
async
refresh
(
token
:
string
,
metadata
?:
{
userAgent
?:
string
;
ipAddress
?:
string
}
)
{
let
payload
:
any
;
try
{
payload
=
jwt
.
verify
(
token
,
jwtConfig
.
refreshSecret
);
...
...
@@ -87,7 +87,7 @@ export class AuthService {
const
decoded
=
jwt
.
decode
(
newRefreshToken
)
as
{
exp
:
number
};
const
expiresAt
=
new
Date
(
decoded
.
exp
*
1000
);
await
this
.
repository
.
saveRefreshToken
(
user
.
id
,
newRefreshToken
,
expiresAt
);
await
this
.
repository
.
saveRefreshToken
(
user
.
id
,
newRefreshToken
,
expiresAt
,
metadata
?.
userAgent
,
metadata
?.
ipAddress
);
return
{
accessToken
:
newAccessToken
,
...
...
@@ -96,7 +96,11 @@ export class AuthService {
}
async
logout
(
token
:
string
)
{
await
this
.
repository
.
deleteRefreshToken
(
token
);
const
result
=
await
this
.
repository
.
deleteRefreshToken
(
token
);
if
(
result
.
count
===
0
)
{
throw
new
AppError
(
'Refresh token not found or already invalidated'
,
400
,
ERROR_CODE
.
TOKEN_INVALID
);
}
}
async
getMe
(
userId
:
string
)
{
...
...
src/modules/auth/auth.validation.ts
View file @
a1e1b9d4
import
{
z
}
from
'zod'
;
export
const
loginSchema
=
z
.
object
({
email
:
z
.
string
().
email
(
'Invalid email format'
),
email
:
z
.
string
().
min
(
1
,
'Email is required'
).
email
(
'Invalid email format'
),
password
:
z
.
string
().
min
(
1
,
'Password is required'
),
});
...
...
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