@@ -10,7 +10,8 @@ import jwt_decode from 'jwt-decode';
1010import APIResponse from 'src/common/responses/response' ;
1111import { KeycloakService } from 'src/common/utils/keycloak.service' ;
1212import { APIID } from 'src/common/utils/api-id.config' ;
13- import { Response } from 'express' ;
13+ import { Request , Response } from 'express' ;
14+ import { normalizeIpForForwarding } from 'src/common/utils/client-ip.util' ;
1415
1516type LoginResponse = {
1617 access_token : string ;
@@ -25,9 +26,38 @@ export class AuthService {
2526 private readonly keycloakService : KeycloakService
2627 ) { }
2728
28- async login ( authDto , response : Response ) {
29+ private getClientIpForKeycloak ( request : Request ) : string | undefined {
30+ const ipFromExpress = normalizeIpForForwarding ( request ?. ip ) ;
31+ if ( ipFromExpress ) return ipFromExpress ;
32+
33+ // Fallback: only trust X-Forwarded-For if the direct connection is internal (proxy hop).
34+ const remoteAddress = normalizeIpForForwarding ( request ?. socket ?. remoteAddress ) ;
35+ const isInternal =
36+ ! ! remoteAddress &&
37+ ( remoteAddress === '127.0.0.1' ||
38+ remoteAddress === '::1' ||
39+ remoteAddress . startsWith ( '10.' ) ||
40+ remoteAddress . startsWith ( '192.168.' ) ||
41+ ( remoteAddress . startsWith ( '172.' ) &&
42+ ( ( ) => {
43+ const second = parseInt ( remoteAddress . slice ( 4 , 7 ) , 10 ) ;
44+ return second >= 16 && second <= 31 ;
45+ } ) ( ) ) ) ;
46+
47+ if ( ! isInternal ) return undefined ;
48+
49+ const xff = request ?. headers ?. [ 'x-forwarded-for' ] ;
50+ const raw =
51+ typeof xff === 'string' ? xff : Array . isArray ( xff ) ? xff . join ( ',' ) : undefined ;
52+ if ( ! raw ) return undefined ;
53+ const first = raw . split ( ',' ) [ 0 ] ?. trim ( ) ;
54+ return normalizeIpForForwarding ( first ) ;
55+ }
56+
57+ async login ( request : Request , authDto , response : Response ) {
2958 const apiId = APIID . LOGIN ;
3059 const { username, password } = authDto ;
60+ const clientIp = this . getClientIpForKeycloak ( request ) ;
3161
3262 try {
3363 // Optimized: Only check user status (no tenant/role data needed for login)
@@ -57,7 +87,7 @@ export class AuthService {
5787 refresh_token,
5888 refresh_expires_in,
5989 token_type,
60- } = await this . keycloakService . login ( username , password ) ;
90+ } = await this . keycloakService . login ( username , password , clientIp ) ;
6191
6292 const res = {
6393 access_token,
@@ -119,12 +149,14 @@ export class AuthService {
119149 }
120150
121151 async refreshToken (
152+ request : Request ,
122153 refreshToken : string ,
123154 response : Response
124155 ) : Promise < LoginResponse > {
125156 const apiId = APIID . REFRESH ;
157+ const clientIp = this . getClientIpForKeycloak ( request ) ;
126158 const { access_token, expires_in, refresh_token, refresh_expires_in } =
127- await this . keycloakService . refreshToken ( refreshToken ) . catch ( ( ) => {
159+ await this . keycloakService . refreshToken ( refreshToken , clientIp ) . catch ( ( ) => {
128160 throw new UnauthorizedException ( ) ;
129161 } ) ;
130162
@@ -143,10 +175,11 @@ export class AuthService {
143175 ) ;
144176 }
145177
146- async logout ( refreshToken : string , response : Response ) {
178+ async logout ( request : Request , refreshToken : string , response : Response ) {
147179 const apiId = APIID . LOGOUT ;
180+ const clientIp = this . getClientIpForKeycloak ( request ) ;
148181 try {
149- const logout = await this . keycloakService . logout ( refreshToken ) ;
182+ const logout = await this . keycloakService . logout ( refreshToken , clientIp ) ;
150183 return APIResponse . success (
151184 response ,
152185 apiId ,
0 commit comments