@authhero/cloudflare-adapter 2.8.0 → 2.10.0

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.md CHANGED
@@ -4,10 +4,11 @@ Cloudflare-specific adapters for AuthHero, providing integrations with Cloudflar
4
4
 
5
5
  ## Features
6
6
 
7
- This package provides three adapters:
7
+ This package provides four adapters:
8
8
 
9
9
  - **Custom Domains** - Manage custom domains via Cloudflare API
10
10
  - **Cache** - Caching using Cloudflare's Cache API
11
+ - **Geo** (optional) - Extract geographic information from Cloudflare request headers
11
12
  - **Logs** (optional) - Write authentication logs to Cloudflare R2 using Pipelines and query with R2 SQL
12
13
 
13
14
  ## Installation
@@ -35,6 +36,13 @@ const adapters = createAdapters({
35
36
  defaultTtlSeconds: 3600,
36
37
  keyPrefix: "authhero:",
37
38
 
39
+ // Geo adapter configuration (optional) - automatically included when getHeaders is provided
40
+ getHeaders: () => {
41
+ // In Cloudflare Workers, you'd typically pass request headers
42
+ // Cloudflare automatically adds cf-ipcountry, cf-ipcity, etc.
43
+ return request.headers;
44
+ },
45
+
38
46
  // R2 SQL logs configuration (optional) - HTTP mode
39
47
  r2SqlLogs: {
40
48
  pipelineEndpoint: "https://your-stream-id.ingest.cloudflare.com",
@@ -46,7 +54,7 @@ const adapters = createAdapters({
46
54
  });
47
55
 
48
56
  // Use the adapters
49
- const { customDomains, cache, logs } = adapters;
57
+ const { customDomains, cache, geo, logs } = adapters;
50
58
  ```
51
59
 
52
60
  ### Service Binding Mode (Cloudflare Workers)
@@ -66,6 +74,9 @@ export default {
66
74
  authEmail: "your-cloudflare-email",
67
75
  customDomainAdapter: yourDatabaseCustomDomainsAdapter,
68
76
 
77
+ // Geo adapter - extract location from Cloudflare headers
78
+ getHeaders: () => Object.fromEntries(request.headers),
79
+
69
80
  // R2 SQL logs with service binding
70
81
  r2SqlLogs: {
71
82
  pipelineBinding: env.PIPELINE_SERVICE,
@@ -74,7 +85,7 @@ export default {
74
85
  },
75
86
  });
76
87
 
77
- // Use adapters.logs
88
+ // Use adapters.logs and adapters.geo
78
89
  },
79
90
  };
80
91
  ```
@@ -225,7 +236,15 @@ In the Cloudflare Dashboard:
225
236
  { "name": "strategy_type", "type": "string", "required": false },
226
237
  { "name": "hostname", "type": "string", "required": false },
227
238
  { "name": "auth0_client", "type": "string", "required": false },
228
- { "name": "log_id", "type": "string", "required": true }
239
+ { "name": "log_id", "type": "string", "required": true },
240
+ { "name": "country_code", "type": "string", "required": false },
241
+ { "name": "country_code3", "type": "string", "required": false },
242
+ { "name": "country_name", "type": "string", "required": false },
243
+ { "name": "city_name", "type": "string", "required": false },
244
+ { "name": "latitude", "type": "string", "required": false },
245
+ { "name": "longitude", "type": "string", "required": false },
246
+ { "name": "time_zone", "type": "string", "required": false },
247
+ { "name": "continent_code", "type": "string", "required": false }
229
248
  ]
230
249
  }
231
250
  ```
@@ -283,24 +302,24 @@ Use this mode when running inside a Cloudflare Worker with a service binding to
283
302
  ```typescript
284
303
  // wrangler.toml
285
304
  [[pipelines]];
286
- binding = "PIPELINE_SERVICE";
305
+ binding = "AUTHHERO_LOGS_STREAM";
287
306
  pipeline = "my-pipeline";
288
307
 
289
308
  // TypeScript
290
309
  interface Env {
291
- PIPELINE_SERVICE: { fetch: typeof fetch };
310
+ AUTHHERO_LOGS_STREAM: { fetch: typeof fetch };
292
311
  }
293
312
 
294
313
  const { logs } = createAdapters({
295
314
  r2SqlLogs: {
296
- pipelineBinding: env.PIPELINE_SERVICE,
315
+ pipelineBinding: env.AUTHHERO_LOGS_STREAM,
297
316
  authToken: env.R2_SQL_AUTH_TOKEN,
298
317
  warehouseName: env.R2_WAREHOUSE_NAME,
299
318
  },
300
319
  });
301
320
  ```
302
321
 
303
- This mode is more efficient as it avoids HTTP overhead for Worker-to-Worker communication.
322
+ This mode is more efficient as it avoids HTTP overhead for Worker-to-Worker communication. The `AUTHHERO_LOGS_STREAM` binding name is recommended for consistency across workers.
304
323
 
305
324
  ##### 3. Passthrough Mode (Wrap Another Adapter)
306
325
 
@@ -385,6 +404,150 @@ npx wrangler r2 sql query "your_warehouse" "
385
404
  "
386
405
  ```
387
406
 
407
+ ## Geo Adapter
408
+
409
+ The Cloudflare Geo adapter extracts geographic location information from Cloudflare's automatic request headers. This is used to enrich authentication logs with location data.
410
+
411
+ ### Features
412
+
413
+ - **Zero Latency**: Uses headers already provided by Cloudflare Workers
414
+ - **No API Calls**: No external services or databases required
415
+ - **Comprehensive Data**: Includes country, city, coordinates, timezone, and continent
416
+ - **Automatic**: Cloudflare populates headers automatically for every request
417
+
418
+ ### Configuration
419
+
420
+ The geo adapter is automatically created when you provide the `getHeaders` function:
421
+
422
+ ```typescript
423
+ const adapters = createAdapters({
424
+ // ... other config
425
+ getHeaders: () => Object.fromEntries(request.headers),
426
+ });
427
+
428
+ // Access the geo adapter
429
+ const geoInfo = await adapters.geo?.getGeoInfo();
430
+ ```
431
+
432
+ ### Cloudflare Headers Used
433
+
434
+ The adapter reads these Cloudflare-provided headers:
435
+
436
+ | Header | Description | Example |
437
+ | ---------------- | ------------------------- | --------------------- |
438
+ | `cf-ipcountry` | 2-letter ISO country code | `US` |
439
+ | `cf-ipcity` | City name | `San Francisco` |
440
+ | `cf-iplatitude` | Latitude coordinate | `37.7749` |
441
+ | `cf-iplongitude` | Longitude coordinate | `-122.4194` |
442
+ | `cf-timezone` | IANA timezone identifier | `America/Los_Angeles` |
443
+ | `cf-ipcontinent` | 2-letter continent code | `NA` |
444
+
445
+ ### Response Format
446
+
447
+ ```typescript
448
+ interface GeoInfo {
449
+ country_code: string; // "US"
450
+ country_code3: string; // "USA"
451
+ country_name: string; // "United States"
452
+ city_name: string; // "San Francisco"
453
+ latitude: string; // "37.7749"
454
+ longitude: string; // "-122.4194"
455
+ time_zone: string; // "America/Los_Angeles"
456
+ continent_code: string; // "NA"
457
+ }
458
+ ```
459
+
460
+ ### Integration with AuthHero
461
+
462
+ When configured in AuthHero, the geo adapter automatically enriches authentication logs:
463
+
464
+ ```typescript
465
+ import { init } from "@authhero/authhero";
466
+ import createAdapters from "@authhero/cloudflare-adapter";
467
+
468
+ const cloudflareAdapters = createAdapters({
469
+ getHeaders: () => Object.fromEntries(request.headers),
470
+ // ... other config
471
+ });
472
+
473
+ const authhero = init({
474
+ data: yourDatabaseAdapter,
475
+ geo: cloudflareAdapters.geo, // Add geo adapter
476
+ // ... other config
477
+ });
478
+ ```
479
+
480
+ Logs will automatically include `location_info`:
481
+
482
+ ```json
483
+ {
484
+ "type": "s",
485
+ "date": "2025-11-28T12:00:00.000Z",
486
+ "location_info": {
487
+ "country_code": "US",
488
+ "country_code3": "USA",
489
+ "country_name": "United States",
490
+ "city_name": "San Francisco",
491
+ "latitude": "37.7749",
492
+ "longitude": "-122.4194",
493
+ "time_zone": "America/Los_Angeles",
494
+ "continent_code": "NA"
495
+ }
496
+ }
497
+ ```
498
+
499
+ ### Alternative: IP Geolocation Databases
500
+
501
+ If you're not using Cloudflare Workers or need more detailed location data, you can implement a custom `GeoAdapter` using IP geolocation databases like MaxMind GeoIP2:
502
+
503
+ ```typescript
504
+ import maxmind from "maxmind";
505
+ import { GeoAdapter, GeoInfo } from "@authhero/adapter-interfaces";
506
+
507
+ class MaxMindGeoAdapter implements GeoAdapter {
508
+ private reader: maxmind.Reader<maxmind.CityResponse>;
509
+
510
+ private constructor(reader: maxmind.Reader<maxmind.CityResponse>) {
511
+ this.reader = reader;
512
+ }
513
+
514
+ static async create(databasePath: string): Promise<MaxMindGeoAdapter> {
515
+ const reader = await maxmind.open<maxmind.CityResponse>(databasePath);
516
+ return new MaxMindGeoAdapter(reader);
517
+ }
518
+
519
+ async getGeoInfo(): Promise<GeoInfo | null> {
520
+ const ip = this.getClientIP();
521
+ const lookup = this.reader.get(ip);
522
+
523
+ if (!lookup) return null;
524
+
525
+ return {
526
+ country_code: lookup.country?.iso_code || "",
527
+ country_code3: lookup.country?.iso_code3 || "",
528
+ country_name: lookup.country?.names?.en || "",
529
+ city_name: lookup.city?.names?.en || "",
530
+ latitude: lookup.location?.latitude?.toString() || "",
531
+ longitude: lookup.location?.longitude?.toString() || "",
532
+ time_zone: lookup.location?.time_zone || "",
533
+ continent_code: lookup.continent?.code || "",
534
+ };
535
+ }
536
+ }
537
+
538
+ // Usage:
539
+ const geoAdapter = await MaxMindGeoAdapter.create(
540
+ "/path/to/GeoLite2-City.mmdb",
541
+ );
542
+ ```
543
+
544
+ **Considerations for IP Databases**:
545
+
546
+ - Requires database downloads and regular updates
547
+ - Additional latency for lookups (1-5ms typically)
548
+ - May require licensing fees
549
+ - Works in any environment, not just edge platforms
550
+
388
551
  ## Environment Variables
389
552
 
390
553
  Recommended environment variables: