@@ -7,7 +7,7 @@ import type {
77import dayjs from 'dayjs'
88import utc from 'dayjs/plugin/utc.js' ;
99
10- import { AdminForthPlugin , AllowedActionsEnum , AdminForthSortDirections , AdminForthDataTypes , HttpExtra , ActionCheckSource , } from "adminforth" ;
10+ import { AdminForthPlugin , AllowedActionsEnum , AdminForthSortDirections , AdminForthDataTypes , HttpExtra , ActionCheckSource , Filters , } from "adminforth" ;
1111import { PluginOptions } from "./types.js" ;
1212
1313dayjs . extend ( utc ) ;
@@ -29,6 +29,64 @@ export default class AuditLogPlugin extends AdminForthPlugin {
2929
3030 static defaultError = 'Sorry, you do not have access to this resource.'
3131
32+ async getIpAndCountry ( headers : Record < string , any > ) : Promise < { country : string | null , clientIp : string | null } > {
33+ let clientIp : string | null = null ;
34+ if ( this . options . resourceColumns . resourceIpColumnName ) {
35+ clientIp = this . adminforth . auth . getClientIp ( headers ) ;
36+ }
37+ let country : string | null = null ;
38+ if ( this . options . resourceColumns . resourceCountryColumnName && clientIp ) {
39+ country = await this . getClientIpCountry ( headers , clientIp ) ;
40+ }
41+ return { country, clientIp } ;
42+ }
43+
44+ async getClientIpCountry ( headers : Record < string , any > , clientIp : string | null ) : Promise < string | null > {
45+ const headersLower = Object . keys ( headers ) . reduce ( ( acc : Record < string , any > , key : string ) => {
46+ acc [ key . toLowerCase ( ) ] = headers [ key ] ;
47+ return acc ;
48+ } , { } ) ;
49+
50+ if ( this . options . isoCountryCodeRequestHeader ) {
51+ const cfCountry = headersLower [ this . options . isoCountryCodeRequestHeader . toLowerCase ( ) ] ;
52+ if ( cfCountry && cfCountry !== 'XX' ) {
53+ return cfCountry . toUpperCase ( ) ;
54+ }
55+ }
56+
57+ // DB CHECK
58+ const ipCol = this . options . resourceColumns . resourceIpColumnName ;
59+ const countryCol = this . options . resourceColumns . resourceCountryColumnName ;
60+
61+ //TODO fix ts-ignore after release new adminforth version with proper types
62+ //@ts -ignore
63+ const existingLog = await this . adminforth . resource ( this . auditLogResource ) . get ( Filters . AND ( Filters . EQ ( ipCol , clientIp ) , Filters . IS_NOT_EMPTY ( countryCol ) ) ) ;
64+ if ( existingLog ) {
65+ return existingLog [ countryCol ] ;
66+ }
67+
68+ // API Request
69+ try {
70+ const apiUrl = `https://ipinfo.io/${ clientIp } /json` ;
71+
72+ const response = await fetch ( apiUrl ) ;
73+ if ( response . status !== 200 ) {
74+ return null ;
75+ }
76+
77+ const data : any = await response . json ( ) ;
78+ const country = data . country ;
79+
80+ if ( country && typeof country === 'string' && country . length === 2 ) {
81+ return country . toUpperCase ( ) ;
82+ }
83+
84+ } catch ( e ) {
85+ console . error ( 'Error fetching IP country' , e ) ;
86+ }
87+
88+ return null ;
89+ }
3290 createLogRecord = async ( resource : AdminForthResource , action : AllowedActionsEnum | string , data : Object , user : AdminUser , oldRecord ?: Object , extra ?: HttpExtra ) => {
3391 const recordIdFieldName = resource . columns . find ( ( c ) => c . primaryKey === true ) ?. name ;
3492 const recordId = data ?. [ recordIdFieldName ] || oldRecord ?. [ recordIdFieldName ] ;
@@ -85,6 +143,8 @@ export default class AuditLogPlugin extends AdminForthPlugin {
85143 }
86144 } ) ;
87145
146+ const { country, clientIp } = await this . getIpAndCountry ( extra ?. headers || { } ) ;
147+
88148 const record = {
89149 [ this . options . resourceColumns . resourceIdColumnName ] : resource . resourceId ,
90150 [ this . options . resourceColumns . resourceActionColumnName ] : action ,
@@ -93,7 +153,8 @@ export default class AuditLogPlugin extends AdminForthPlugin {
93153 [ this . options . resourceColumns . resourceRecordIdColumnName ] : recordId ,
94154 // utc iso string
95155 [ this . options . resourceColumns . resourceCreatedColumnName ] : dayjs . utc ( ) . format ( ) ,
96- ...( this . options . resourceColumns . resourceIpColumnName && extra ?. headers ? { [ this . options . resourceColumns . resourceIpColumnName ] : this . adminforth . auth . getClientIp ( extra . headers ) } : { } ) ,
156+ ...( clientIp ? { [ this . options . resourceColumns . resourceIpColumnName ] : clientIp } : { } ) ,
157+ ...( country ? { [ this . options . resourceColumns . resourceCountryColumnName ] : country } : { } ) ,
97158 }
98159 const auditLogResource = this . adminforth . config . resources . find ( ( r ) => r . resourceId === this . auditLogResource ) ;
99160 await this . adminforth . createResourceRecord ( { resource : auditLogResource , record, adminUser : user } ) ;
@@ -132,15 +193,19 @@ export default class AuditLogPlugin extends AdminForthPlugin {
132193 }
133194 }
134195
196+ const { country, clientIp } = await this . getIpAndCountry ( headers || { } ) ;
197+
135198 const record = {
136199 [ this . options . resourceColumns . resourceIdColumnName ] : resourceId ,
137200 [ this . options . resourceColumns . resourceActionColumnName ] : actionId ,
138201 [ this . options . resourceColumns . resourceDataColumnName ] : { 'oldRecord' : oldData || { } , 'newRecord' : data } ,
139202 [ this . options . resourceColumns . resourceUserIdColumnName ] : user . pk ,
140203 [ this . options . resourceColumns . resourceRecordIdColumnName ] : recordId ,
141204 [ this . options . resourceColumns . resourceCreatedColumnName ] : dayjs . utc ( ) . format ( ) ,
142- ...( this . options . resourceColumns . resourceIpColumnName && headers ? { [ this . options . resourceColumns . resourceIpColumnName ] : this . adminforth . auth . getClientIp ( headers ) } : { } ) ,
205+ ...( clientIp ? { [ this . options . resourceColumns . resourceIpColumnName ] : clientIp } : { } ) ,
206+ ...( country ? { [ this . options . resourceColumns . resourceCountryColumnName ] : country } : { } ) ,
143207 }
208+
144209 const auditLogResource = this . adminforth . config . resources . find ( ( r ) => r . resourceId === this . auditLogResource ) ;
145210 await this . adminforth . createResourceRecord ( { resource : auditLogResource , record, adminUser : user } ) ;
146211 }
0 commit comments