@technomoron/api-server-base 1.1.3 → 1.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.txt CHANGED
@@ -127,7 +127,9 @@ Request Lifecycle
127
127
 
128
128
  Client IP Helpers
129
129
  -----------------
130
- Use getClientIp(req) to obtain the most likely client address, skipping loopback entries collected from proxy headers. Call getClientIpChain(req) when you need the de-duplicated sequence gathered from the standard Forwarded/X-Forwarded-For/X-Real-IP headers as well as Express' req.ip/req.ips and the underlying socket.
130
+ Call `apiReq.getClientInfo()` when you need the entire client fingerprint captured during request hydration. It returns the raw user-agent string plus derived browser/OS/device labels along with the computed `ip` and `ipchain`.
131
+
132
+ Call `apiReq.getClientIp()` to obtain the most likely client address, skipping loopback entries collected from proxy headers. Use `apiReq.getClientIpChain()` when you need the de-duplicated sequence gathered from the standard Forwarded/X-Forwarded-For/X-Real-IP headers as well as Express' `req.ip`/`req.ips` and the underlying socket. Both helpers reuse the cached payload returned by `apiReq.getClientInfo()`.
131
133
 
132
134
  Extending the Base Classes
133
135
  --------------------------
@@ -110,6 +110,111 @@ function extractForwardedHeader(header) {
110
110
  }
111
111
  return ips;
112
112
  }
113
+ function detectBrowser(userAgent) {
114
+ const browserMatchers = [
115
+ { label: 'Edge', pattern: /(Edg|Edge|EdgiOS|EdgA)\/([\d.]+)/i, versionGroup: 2 },
116
+ { label: 'Chrome', pattern: /(Chrome|CriOS)\/([\d.]+)/i, versionGroup: 2 },
117
+ { label: 'Firefox', pattern: /(Firefox|FxiOS)\/([\d.]+)/i, versionGroup: 2 },
118
+ { label: 'Safari', pattern: /Version\/([\d.]+).*Safari/i, versionGroup: 1 },
119
+ { label: 'Opera', pattern: /(OPR|Opera)\/([\d.]+)/i, versionGroup: 2 },
120
+ { label: 'Brave', pattern: /Brave\/([\d.]+)/i, versionGroup: 1 },
121
+ { label: 'Vivaldi', pattern: /Vivaldi\/([\d.]+)/i, versionGroup: 1 },
122
+ { label: 'Electron', pattern: /Electron\/([\d.]+)/i, versionGroup: 1 },
123
+ { label: 'Node', pattern: /Node\.js\/([\d.]+)/i, versionGroup: 1 },
124
+ { label: 'IE', pattern: /MSIE ([\d.]+)/i, versionGroup: 1 },
125
+ { label: 'IE', pattern: /Trident\/.*rv:([\d.]+)/i, versionGroup: 1 }
126
+ ];
127
+ for (const matcher of browserMatchers) {
128
+ const m = userAgent.match(matcher.pattern);
129
+ if (m) {
130
+ const version = matcher.versionGroup ? m[matcher.versionGroup] : '';
131
+ return version ? `${matcher.label} ${version}` : matcher.label;
132
+ }
133
+ }
134
+ return '';
135
+ }
136
+ function detectOs(userAgent) {
137
+ const osMatchers = [
138
+ {
139
+ label: 'Windows',
140
+ pattern: /Windows NT ([\d.]+)/i,
141
+ transform: (match) => `Windows ${match[1]}`
142
+ },
143
+ {
144
+ label: 'iOS',
145
+ pattern: /iPhone OS ([\d_]+)/i,
146
+ transform: (match) => `iOS ${match[1].replace(/_/g, '.')}`
147
+ },
148
+ {
149
+ label: 'iPadOS',
150
+ pattern: /iPad; CPU OS ([\d_]+)/i,
151
+ transform: (match) => `iPadOS ${match[1].replace(/_/g, '.')}`
152
+ },
153
+ {
154
+ label: 'macOS',
155
+ pattern: /Mac OS X ([\d_]+)/i,
156
+ transform: (match) => `macOS ${match[1].replace(/_/g, '.')}`
157
+ },
158
+ {
159
+ label: 'Android',
160
+ pattern: /Android ([\d.]+)/i,
161
+ transform: (match) => `Android ${match[1]}`
162
+ },
163
+ {
164
+ label: 'ChromeOS',
165
+ pattern: /CrOS [^ ]+ ([\d.]+)/i,
166
+ transform: (match) => `ChromeOS ${match[1]}`
167
+ },
168
+ { label: 'Linux', pattern: /Linux/i },
169
+ { label: 'Unix', pattern: /X11/i }
170
+ ];
171
+ for (const matcher of osMatchers) {
172
+ const m = userAgent.match(matcher.pattern);
173
+ if (m) {
174
+ return matcher.transform ? matcher.transform(m) : matcher.label;
175
+ }
176
+ }
177
+ return '';
178
+ }
179
+ function detectDevice(userAgent, osLabel) {
180
+ if (/iPhone/i.test(userAgent)) {
181
+ return 'iPhone';
182
+ }
183
+ if (/iPad/i.test(userAgent)) {
184
+ return 'iPad';
185
+ }
186
+ if (/iPod/i.test(userAgent)) {
187
+ return 'iPod';
188
+ }
189
+ if (/Android/i.test(userAgent)) {
190
+ const match = userAgent.match(/;\s*([^;]+)\s+Build/i);
191
+ if (match) {
192
+ return match[1];
193
+ }
194
+ return 'Android Device';
195
+ }
196
+ if (/Macintosh/i.test(userAgent)) {
197
+ return 'Mac';
198
+ }
199
+ if (/Windows/i.test(userAgent)) {
200
+ return 'PC';
201
+ }
202
+ if (/CrOS/i.test(userAgent)) {
203
+ return 'Chromebook';
204
+ }
205
+ return osLabel;
206
+ }
207
+ function parseClientAgent(userAgentHeader) {
208
+ const raw = Array.isArray(userAgentHeader) ? userAgentHeader[0] : userAgentHeader;
209
+ const ua = typeof raw === 'string' ? raw.trim() : '';
210
+ if (!ua) {
211
+ return { ua: '', browser: '', os: '', device: '' };
212
+ }
213
+ const os = detectOs(ua);
214
+ const browser = detectBrowser(ua);
215
+ const device = detectDevice(ua, os);
216
+ return { ua, browser, os, device };
217
+ }
113
218
  function isLoopbackAddress(ip) {
114
219
  if (ip === '::1' || ip === '0:0:0:0:0:0:0:1') {
115
220
  return true;
@@ -122,6 +227,71 @@ function isLoopbackAddress(ip) {
122
227
  }
123
228
  return false;
124
229
  }
230
+ function collectClientIpChain(req) {
231
+ const seen = new Set();
232
+ const result = [];
233
+ const pushNormalized = (ip) => {
234
+ if (!ip || seen.has(ip)) {
235
+ return;
236
+ }
237
+ seen.add(ip);
238
+ result.push(ip);
239
+ };
240
+ for (const ip of extractForwardedFor(req.headers['x-forwarded-for'])) {
241
+ pushNormalized(ip);
242
+ }
243
+ for (const ip of extractForwardedHeader(req.headers['forwarded'])) {
244
+ pushNormalized(ip);
245
+ }
246
+ const realIp = req.headers['x-real-ip'];
247
+ if (Array.isArray(realIp)) {
248
+ realIp.forEach((value) => pushNormalized(normalizeIpAddress(value)));
249
+ }
250
+ else if (typeof realIp === 'string') {
251
+ pushNormalized(normalizeIpAddress(realIp));
252
+ }
253
+ if (Array.isArray(req.ips)) {
254
+ for (const ip of req.ips) {
255
+ pushNormalized(normalizeIpAddress(ip));
256
+ }
257
+ }
258
+ if (typeof req.ip === 'string') {
259
+ pushNormalized(normalizeIpAddress(req.ip));
260
+ }
261
+ const socketAddress = req.socket?.remoteAddress;
262
+ if (typeof socketAddress === 'string') {
263
+ pushNormalized(normalizeIpAddress(socketAddress));
264
+ }
265
+ const connectionAddress = req.connection?.remoteAddress;
266
+ if (typeof connectionAddress === 'string') {
267
+ pushNormalized(normalizeIpAddress(connectionAddress));
268
+ }
269
+ return result;
270
+ }
271
+ function selectClientIp(chain) {
272
+ for (const ip of chain) {
273
+ if (!isLoopbackAddress(ip)) {
274
+ return ip;
275
+ }
276
+ }
277
+ return chain[0] ?? null;
278
+ }
279
+ function buildClientInfo(req) {
280
+ const agent = parseClientAgent(req.headers['user-agent']);
281
+ const ipchain = collectClientIpChain(req);
282
+ const ip = selectClientIp(ipchain);
283
+ return {
284
+ ...agent,
285
+ ip,
286
+ ipchain
287
+ };
288
+ }
289
+ function ensureClientInfo(apiReq) {
290
+ if (!apiReq.clientInfo) {
291
+ apiReq.clientInfo = buildClientInfo(apiReq.req);
292
+ }
293
+ return apiReq.clientInfo;
294
+ }
125
295
  class ApiError extends Error {
126
296
  constructor({ code, message, data, errors }) {
127
297
  const msg = guess_exception_text(message, '[Unknown error (null/undefined)]');
@@ -420,13 +590,17 @@ class ApiServer {
420
590
  return async (req, res, next) => {
421
591
  void next;
422
592
  try {
423
- const apiReq = (this.currReq = {
593
+ const apiReq = {
424
594
  server: this,
425
595
  req,
426
596
  res,
427
597
  token: '',
428
- tokenData: null
429
- });
598
+ tokenData: null,
599
+ getClientInfo: () => ensureClientInfo(apiReq),
600
+ getClientIp: () => ensureClientInfo(apiReq).ip,
601
+ getClientIpChain: () => ensureClientInfo(apiReq).ipchain
602
+ };
603
+ this.currReq = apiReq;
430
604
  if (this.config.hydrateGetBody) {
431
605
  hydrateGetBody(apiReq.req);
432
606
  }
@@ -473,56 +647,6 @@ class ApiServer {
473
647
  }
474
648
  };
475
649
  }
476
- getClientIp(req) {
477
- const chain = this.getClientIpChain(req);
478
- for (const ip of chain) {
479
- if (!isLoopbackAddress(ip)) {
480
- return ip;
481
- }
482
- }
483
- return chain[0] ?? null;
484
- }
485
- getClientIpChain(req) {
486
- const seen = new Set();
487
- const result = [];
488
- const pushNormalized = (ip) => {
489
- if (!ip || seen.has(ip)) {
490
- return;
491
- }
492
- seen.add(ip);
493
- result.push(ip);
494
- };
495
- for (const ip of extractForwardedFor(req.headers['x-forwarded-for'])) {
496
- pushNormalized(ip);
497
- }
498
- for (const ip of extractForwardedHeader(req.headers['forwarded'])) {
499
- pushNormalized(ip);
500
- }
501
- const realIp = req.headers['x-real-ip'];
502
- if (Array.isArray(realIp)) {
503
- realIp.forEach((value) => pushNormalized(normalizeIpAddress(value)));
504
- }
505
- else if (typeof realIp === 'string') {
506
- pushNormalized(normalizeIpAddress(realIp));
507
- }
508
- if (Array.isArray(req.ips)) {
509
- for (const ip of req.ips) {
510
- pushNormalized(normalizeIpAddress(ip));
511
- }
512
- }
513
- if (typeof req.ip === 'string') {
514
- pushNormalized(normalizeIpAddress(req.ip));
515
- }
516
- const socketAddress = req.socket?.remoteAddress;
517
- if (typeof socketAddress === 'string') {
518
- pushNormalized(normalizeIpAddress(socketAddress));
519
- }
520
- const connectionAddress = req.connection?.remoteAddress;
521
- if (typeof connectionAddress === 'string') {
522
- pushNormalized(normalizeIpAddress(connectionAddress));
523
- }
524
- return result;
525
- }
526
650
  api(module) {
527
651
  const router = express_1.default.Router();
528
652
  module.server = this;
@@ -46,6 +46,20 @@ export interface ApiRequest {
46
46
  res: Response;
47
47
  tokenData?: ApiTokenData | null;
48
48
  token?: string;
49
+ clientInfo?: ClientInfo;
50
+ getClientInfo: () => ClientInfo;
51
+ getClientIp: () => string | null;
52
+ getClientIpChain: () => string[];
53
+ }
54
+ export interface ClientAgentProfile {
55
+ ua: string;
56
+ browser: string;
57
+ os: string;
58
+ device: string;
59
+ }
60
+ export interface ClientInfo extends ClientAgentProfile {
61
+ ip: string | null;
62
+ ipchain: string[];
49
63
  }
50
64
  export { ApiModule } from './api-module.js';
51
65
  export type { ApiHandler, ApiAuthType, ApiAuthClass, ApiRoute, ApiKey } from './api-module.js';
@@ -121,8 +135,6 @@ export declare class ApiServer {
121
135
  private verifyJWT;
122
136
  private authenticate;
123
137
  private handle_request;
124
- getClientIp(req: RequestWithStuff): string | null;
125
- getClientIpChain(req: RequestWithStuff): string[];
126
138
  api<T extends ApiModule<any>>(module: T): this;
127
139
  dumpRequest(apiReq: ApiRequest): void;
128
140
  }
@@ -4,12 +4,18 @@ export interface AuthTokenMetadata {
4
4
  domain?: string;
5
5
  fingerprint?: string;
6
6
  label?: string;
7
+ browser?: string;
8
+ device?: string;
9
+ ip?: string;
10
+ os?: string;
7
11
  scope?: string | string[];
8
12
  revokeSessions?: 'device' | 'domain' | 'client' | 'user';
9
13
  }
10
14
  export interface AuthTokenData extends AuthTokenMetadata {
11
15
  access: string;
12
16
  expires?: Date;
17
+ issuedAt?: Date;
18
+ lastSeenAt?: Date;
13
19
  refresh: string;
14
20
  userId: AuthIdentifier;
15
21
  }
@@ -46,6 +46,20 @@ export interface ApiRequest {
46
46
  res: Response;
47
47
  tokenData?: ApiTokenData | null;
48
48
  token?: string;
49
+ clientInfo?: ClientInfo;
50
+ getClientInfo: () => ClientInfo;
51
+ getClientIp: () => string | null;
52
+ getClientIpChain: () => string[];
53
+ }
54
+ export interface ClientAgentProfile {
55
+ ua: string;
56
+ browser: string;
57
+ os: string;
58
+ device: string;
59
+ }
60
+ export interface ClientInfo extends ClientAgentProfile {
61
+ ip: string | null;
62
+ ipchain: string[];
49
63
  }
50
64
  export { ApiModule } from './api-module.js';
51
65
  export type { ApiHandler, ApiAuthType, ApiAuthClass, ApiRoute, ApiKey } from './api-module.js';
@@ -121,8 +135,6 @@ export declare class ApiServer {
121
135
  private verifyJWT;
122
136
  private authenticate;
123
137
  private handle_request;
124
- getClientIp(req: RequestWithStuff): string | null;
125
- getClientIpChain(req: RequestWithStuff): string[];
126
138
  api<T extends ApiModule<any>>(module: T): this;
127
139
  dumpRequest(apiReq: ApiRequest): void;
128
140
  }
@@ -103,6 +103,111 @@ function extractForwardedHeader(header) {
103
103
  }
104
104
  return ips;
105
105
  }
106
+ function detectBrowser(userAgent) {
107
+ const browserMatchers = [
108
+ { label: 'Edge', pattern: /(Edg|Edge|EdgiOS|EdgA)\/([\d.]+)/i, versionGroup: 2 },
109
+ { label: 'Chrome', pattern: /(Chrome|CriOS)\/([\d.]+)/i, versionGroup: 2 },
110
+ { label: 'Firefox', pattern: /(Firefox|FxiOS)\/([\d.]+)/i, versionGroup: 2 },
111
+ { label: 'Safari', pattern: /Version\/([\d.]+).*Safari/i, versionGroup: 1 },
112
+ { label: 'Opera', pattern: /(OPR|Opera)\/([\d.]+)/i, versionGroup: 2 },
113
+ { label: 'Brave', pattern: /Brave\/([\d.]+)/i, versionGroup: 1 },
114
+ { label: 'Vivaldi', pattern: /Vivaldi\/([\d.]+)/i, versionGroup: 1 },
115
+ { label: 'Electron', pattern: /Electron\/([\d.]+)/i, versionGroup: 1 },
116
+ { label: 'Node', pattern: /Node\.js\/([\d.]+)/i, versionGroup: 1 },
117
+ { label: 'IE', pattern: /MSIE ([\d.]+)/i, versionGroup: 1 },
118
+ { label: 'IE', pattern: /Trident\/.*rv:([\d.]+)/i, versionGroup: 1 }
119
+ ];
120
+ for (const matcher of browserMatchers) {
121
+ const m = userAgent.match(matcher.pattern);
122
+ if (m) {
123
+ const version = matcher.versionGroup ? m[matcher.versionGroup] : '';
124
+ return version ? `${matcher.label} ${version}` : matcher.label;
125
+ }
126
+ }
127
+ return '';
128
+ }
129
+ function detectOs(userAgent) {
130
+ const osMatchers = [
131
+ {
132
+ label: 'Windows',
133
+ pattern: /Windows NT ([\d.]+)/i,
134
+ transform: (match) => `Windows ${match[1]}`
135
+ },
136
+ {
137
+ label: 'iOS',
138
+ pattern: /iPhone OS ([\d_]+)/i,
139
+ transform: (match) => `iOS ${match[1].replace(/_/g, '.')}`
140
+ },
141
+ {
142
+ label: 'iPadOS',
143
+ pattern: /iPad; CPU OS ([\d_]+)/i,
144
+ transform: (match) => `iPadOS ${match[1].replace(/_/g, '.')}`
145
+ },
146
+ {
147
+ label: 'macOS',
148
+ pattern: /Mac OS X ([\d_]+)/i,
149
+ transform: (match) => `macOS ${match[1].replace(/_/g, '.')}`
150
+ },
151
+ {
152
+ label: 'Android',
153
+ pattern: /Android ([\d.]+)/i,
154
+ transform: (match) => `Android ${match[1]}`
155
+ },
156
+ {
157
+ label: 'ChromeOS',
158
+ pattern: /CrOS [^ ]+ ([\d.]+)/i,
159
+ transform: (match) => `ChromeOS ${match[1]}`
160
+ },
161
+ { label: 'Linux', pattern: /Linux/i },
162
+ { label: 'Unix', pattern: /X11/i }
163
+ ];
164
+ for (const matcher of osMatchers) {
165
+ const m = userAgent.match(matcher.pattern);
166
+ if (m) {
167
+ return matcher.transform ? matcher.transform(m) : matcher.label;
168
+ }
169
+ }
170
+ return '';
171
+ }
172
+ function detectDevice(userAgent, osLabel) {
173
+ if (/iPhone/i.test(userAgent)) {
174
+ return 'iPhone';
175
+ }
176
+ if (/iPad/i.test(userAgent)) {
177
+ return 'iPad';
178
+ }
179
+ if (/iPod/i.test(userAgent)) {
180
+ return 'iPod';
181
+ }
182
+ if (/Android/i.test(userAgent)) {
183
+ const match = userAgent.match(/;\s*([^;]+)\s+Build/i);
184
+ if (match) {
185
+ return match[1];
186
+ }
187
+ return 'Android Device';
188
+ }
189
+ if (/Macintosh/i.test(userAgent)) {
190
+ return 'Mac';
191
+ }
192
+ if (/Windows/i.test(userAgent)) {
193
+ return 'PC';
194
+ }
195
+ if (/CrOS/i.test(userAgent)) {
196
+ return 'Chromebook';
197
+ }
198
+ return osLabel;
199
+ }
200
+ function parseClientAgent(userAgentHeader) {
201
+ const raw = Array.isArray(userAgentHeader) ? userAgentHeader[0] : userAgentHeader;
202
+ const ua = typeof raw === 'string' ? raw.trim() : '';
203
+ if (!ua) {
204
+ return { ua: '', browser: '', os: '', device: '' };
205
+ }
206
+ const os = detectOs(ua);
207
+ const browser = detectBrowser(ua);
208
+ const device = detectDevice(ua, os);
209
+ return { ua, browser, os, device };
210
+ }
106
211
  function isLoopbackAddress(ip) {
107
212
  if (ip === '::1' || ip === '0:0:0:0:0:0:0:1') {
108
213
  return true;
@@ -115,6 +220,71 @@ function isLoopbackAddress(ip) {
115
220
  }
116
221
  return false;
117
222
  }
223
+ function collectClientIpChain(req) {
224
+ const seen = new Set();
225
+ const result = [];
226
+ const pushNormalized = (ip) => {
227
+ if (!ip || seen.has(ip)) {
228
+ return;
229
+ }
230
+ seen.add(ip);
231
+ result.push(ip);
232
+ };
233
+ for (const ip of extractForwardedFor(req.headers['x-forwarded-for'])) {
234
+ pushNormalized(ip);
235
+ }
236
+ for (const ip of extractForwardedHeader(req.headers['forwarded'])) {
237
+ pushNormalized(ip);
238
+ }
239
+ const realIp = req.headers['x-real-ip'];
240
+ if (Array.isArray(realIp)) {
241
+ realIp.forEach((value) => pushNormalized(normalizeIpAddress(value)));
242
+ }
243
+ else if (typeof realIp === 'string') {
244
+ pushNormalized(normalizeIpAddress(realIp));
245
+ }
246
+ if (Array.isArray(req.ips)) {
247
+ for (const ip of req.ips) {
248
+ pushNormalized(normalizeIpAddress(ip));
249
+ }
250
+ }
251
+ if (typeof req.ip === 'string') {
252
+ pushNormalized(normalizeIpAddress(req.ip));
253
+ }
254
+ const socketAddress = req.socket?.remoteAddress;
255
+ if (typeof socketAddress === 'string') {
256
+ pushNormalized(normalizeIpAddress(socketAddress));
257
+ }
258
+ const connectionAddress = req.connection?.remoteAddress;
259
+ if (typeof connectionAddress === 'string') {
260
+ pushNormalized(normalizeIpAddress(connectionAddress));
261
+ }
262
+ return result;
263
+ }
264
+ function selectClientIp(chain) {
265
+ for (const ip of chain) {
266
+ if (!isLoopbackAddress(ip)) {
267
+ return ip;
268
+ }
269
+ }
270
+ return chain[0] ?? null;
271
+ }
272
+ function buildClientInfo(req) {
273
+ const agent = parseClientAgent(req.headers['user-agent']);
274
+ const ipchain = collectClientIpChain(req);
275
+ const ip = selectClientIp(ipchain);
276
+ return {
277
+ ...agent,
278
+ ip,
279
+ ipchain
280
+ };
281
+ }
282
+ function ensureClientInfo(apiReq) {
283
+ if (!apiReq.clientInfo) {
284
+ apiReq.clientInfo = buildClientInfo(apiReq.req);
285
+ }
286
+ return apiReq.clientInfo;
287
+ }
118
288
  export class ApiError extends Error {
119
289
  constructor({ code, message, data, errors }) {
120
290
  const msg = guess_exception_text(message, '[Unknown error (null/undefined)]');
@@ -412,13 +582,17 @@ export class ApiServer {
412
582
  return async (req, res, next) => {
413
583
  void next;
414
584
  try {
415
- const apiReq = (this.currReq = {
585
+ const apiReq = {
416
586
  server: this,
417
587
  req,
418
588
  res,
419
589
  token: '',
420
- tokenData: null
421
- });
590
+ tokenData: null,
591
+ getClientInfo: () => ensureClientInfo(apiReq),
592
+ getClientIp: () => ensureClientInfo(apiReq).ip,
593
+ getClientIpChain: () => ensureClientInfo(apiReq).ipchain
594
+ };
595
+ this.currReq = apiReq;
422
596
  if (this.config.hydrateGetBody) {
423
597
  hydrateGetBody(apiReq.req);
424
598
  }
@@ -465,56 +639,6 @@ export class ApiServer {
465
639
  }
466
640
  };
467
641
  }
468
- getClientIp(req) {
469
- const chain = this.getClientIpChain(req);
470
- for (const ip of chain) {
471
- if (!isLoopbackAddress(ip)) {
472
- return ip;
473
- }
474
- }
475
- return chain[0] ?? null;
476
- }
477
- getClientIpChain(req) {
478
- const seen = new Set();
479
- const result = [];
480
- const pushNormalized = (ip) => {
481
- if (!ip || seen.has(ip)) {
482
- return;
483
- }
484
- seen.add(ip);
485
- result.push(ip);
486
- };
487
- for (const ip of extractForwardedFor(req.headers['x-forwarded-for'])) {
488
- pushNormalized(ip);
489
- }
490
- for (const ip of extractForwardedHeader(req.headers['forwarded'])) {
491
- pushNormalized(ip);
492
- }
493
- const realIp = req.headers['x-real-ip'];
494
- if (Array.isArray(realIp)) {
495
- realIp.forEach((value) => pushNormalized(normalizeIpAddress(value)));
496
- }
497
- else if (typeof realIp === 'string') {
498
- pushNormalized(normalizeIpAddress(realIp));
499
- }
500
- if (Array.isArray(req.ips)) {
501
- for (const ip of req.ips) {
502
- pushNormalized(normalizeIpAddress(ip));
503
- }
504
- }
505
- if (typeof req.ip === 'string') {
506
- pushNormalized(normalizeIpAddress(req.ip));
507
- }
508
- const socketAddress = req.socket?.remoteAddress;
509
- if (typeof socketAddress === 'string') {
510
- pushNormalized(normalizeIpAddress(socketAddress));
511
- }
512
- const connectionAddress = req.connection?.remoteAddress;
513
- if (typeof connectionAddress === 'string') {
514
- pushNormalized(normalizeIpAddress(connectionAddress));
515
- }
516
- return result;
517
- }
518
642
  api(module) {
519
643
  const router = express.Router();
520
644
  module.server = this;
@@ -4,12 +4,18 @@ export interface AuthTokenMetadata {
4
4
  domain?: string;
5
5
  fingerprint?: string;
6
6
  label?: string;
7
+ browser?: string;
8
+ device?: string;
9
+ ip?: string;
10
+ os?: string;
7
11
  scope?: string | string[];
8
12
  revokeSessions?: 'device' | 'domain' | 'client' | 'user';
9
13
  }
10
14
  export interface AuthTokenData extends AuthTokenMetadata {
11
15
  access: string;
12
16
  expires?: Date;
17
+ issuedAt?: Date;
18
+ lastSeenAt?: Date;
13
19
  refresh: string;
14
20
  userId: AuthIdentifier;
15
21
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@technomoron/api-server-base",
3
- "version": "1.1.3",
3
+ "version": "1.1.4",
4
4
  "description": "Api Server Skeleton / Base Class",
5
5
  "type": "module",
6
6
  "main": "./dist/cjs/index.cjs",