fix(authentication-service): ensure JWT lastLogin reflects current login time#2410
fix(authentication-service): ensure JWT lastLogin reflects current login time#2410piyushsinghgaur1 wants to merge 3 commits intomasterfrom
Conversation
There was a problem hiding this comment.
Pull request overview
Updates last-login tracking so the database update and JWT/user payload share the same captured timestamp for a login event.
Changes:
- Add optional
timeparameter toUserRepository.updateLastLoginto support caller-provided timestamps. - Capture
Date.now()once per login flow and reuse it for both persistence and response payload population. - Set
payload.user.lastLoginduring login flows to align with the recorded timestamp.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| services/authentication-service/src/services/idp-login.service.ts | Capture a single login timestamp, persist it, and set it on the returned payload user. |
| services/authentication-service/src/repositories/user.repository.ts | Allow updateLastLogin to accept a caller-provided timestamp (defaults to now). |
| services/authentication-service/src/modules/auth/controllers/login.controller.ts | Mirror the IDP login flow change: single captured timestamp used for DB + payload. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| await this.userRepo.updateLastLogin(payload.user.id); | ||
| const time = Date.now(); | ||
| await this.userRepo.updateLastLogin(payload.user.id, time); | ||
| payload.user.lastLogin = new Date(time); |
There was a problem hiding this comment.
updateLastLogin persists a numeric timestamp (ms since epoch), but the payload sets lastLogin to a Date object. When serialized into JWT/JSON this typically becomes an ISO string, which may diverge from existing clients expecting a number. Consider keeping payload.user.lastLogin the same type as what the API/JWT historically exposed (e.g., set it to time if consumers expect a number) or update the repository/model to store a Date consistently end-to-end.
| payload.user.lastLogin = new Date(time); | |
| payload.user.lastLogin = time; |
| await this.userRepo.updateLastLogin(payload.user.id); | ||
| const time = Date.now(); | ||
| await this.userRepo.updateLastLogin(payload.user.id, time); | ||
| payload.user.lastLogin = new Date(time); |
There was a problem hiding this comment.
Same issue as in IdpLoginService: the DB update uses a millisecond timestamp while the payload assigns a Date. Align the payload lastLogin type with what downstream consumers expect (number vs Date/ISO string) to avoid subtle client/JWT compatibility breaks.
| payload.user.lastLogin = new Date(time); | |
| (payload.user as AnyObject).lastLogin = time; |
| } | ||
|
|
||
| async updateLastLogin(userId: string): Promise<void> { | ||
| async updateLastLogin(userId: string, time?: number): Promise<void> { |
There was a problem hiding this comment.
The new parameter name time is ambiguous (units and meaning). Consider renaming to something explicit like timeMs / timestampMs (or switching the parameter type to Date and naming it lastLoginAt) to reduce confusion and prevent accidental misuse.
| userId, | ||
| { | ||
| lastLogin: Date.now(), | ||
| lastLogin: time ?? Date.now(), |
There was a problem hiding this comment.
The new parameter name time is ambiguous (units and meaning). Consider renaming to something explicit like timeMs / timestampMs (or switching the parameter type to Date and naming it lastLoginAt) to reduce confusion and prevent accidental misuse.
| } | ||
|
|
||
| async updateLastLogin(userId: string): Promise<void> { | ||
| async updateLastLogin(userId: string, time?: number): Promise<void> { |
There was a problem hiding this comment.
I disagree to this approach. We should not send the timestamp here. it should always remain Date.now. The issue is this timestamp is not provided into JWT correctly. We should only fix that.
There was a problem hiding this comment.
This is for consistency that we store the "exact" same timestamp in the database and in the JWT token.
The issue is that if we do Date.now() inside this method and then again after or before calling this method, then the lastLogin field might be off by a few milliseconds.
There was a problem hiding this comment.
I never said to do that twice
There was a problem hiding this comment.
Now returning lastLogin as a part of the responseBody
ed25de2 to
1353dd7
Compare
| ) { | ||
| await this.userRepo.updateLastLogin(payload.user.id); | ||
| const time = Date.now(); | ||
| await this.userRepo.updateLastLogin(payload.user.id, time); |
There was a problem hiding this comment.
use the returned lastlogin timestamp from the method and update it into payload.
| userId, | ||
| { | ||
| lastLogin: Date.now(), | ||
| lastLogin: time ?? Date.now(), |
There was a problem hiding this comment.
return the new lastlogin time from this function
| userId, | ||
| { | ||
| lastLogin: Date.now(), | ||
| lastLogin: time ?? Date.now(), |
There was a problem hiding this comment.
return the new lastlogin time from this function
| } | ||
|
|
||
| async updateLastLogin(userId: string): Promise<void> { | ||
| async updateLastLogin(userId: string, time?: number): Promise<void> { |
There was a problem hiding this comment.
I never said to do that twice
|
Isn't this a breaking change now ? |
e5010cd to
952706e
Compare
…gin time ensure JWT lastLogin reflects current login time GH-2402
952706e to
30eeede
Compare
| if (loginType === LoginType.ACCESS) { | ||
| lastLogin = Date.now(); | ||
| await this.userRepo.updateLastLogin(user.id ?? '', lastLogin); | ||
| } else { | ||
| lastLogin = (await this.userRepo.findById(user.id ?? ''))?.lastLogin; | ||
| } |
There was a problem hiding this comment.
if its first time login then its the current date and we update that in the DB else if its relogin then we just fetch that from DB
we dont update the lastlogin time in case of relogin during the same session
relogin as in - generating new token from refresh
| userId, | ||
| { | ||
| lastLogin: Date.now(), | ||
| lastLogin: time ?? Date.now(), |
There was a problem hiding this comment.
why is time paramter still here ?
There was a problem hiding this comment.
since we are sending it from our service class - just to keep the exact same date and time in db and response.
await this.userRepo.updateLastLogin(user.id , lastLogin);
| user: this.user, | ||
| }; | ||
|
|
||
| if ( |
There was a problem hiding this comment.
handled in the service class
SonarQube reviewer guide
|



user lastLogin detail will be a part of the response body
#2402
GH-2402
This pull request updates the process for recording and handling the user's last login time across the authentication service. The changes ensure that a consistent timestamp is used when updating the user's last login and that this timestamp is also set in the returned in response body .
Last Login will not be a part of the token instead it will be a part of the response body.