@fonderie/auth 7.9.3 → 7.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.
@@ -0,0 +1,6 @@
1
+ -- Optional location for each login attempt (and registration), filled only when the app supplies
2
+ -- IAuthConfig.location. JSONB rather than columns: resolvers differ in
3
+ -- what they know (edge headers give country/region/city; an IP-intelligence
4
+ -- API adds ISP/ASN/proxy), and auth sanitizes the shape before writing.
5
+ -- Nullable, no default, no backfill: existing rows simply have no location.
6
+ ALTER TABLE fonderie_login_events ADD COLUMN IF NOT EXISTS location JSONB;
@@ -0,0 +1,5 @@
1
+ -- Where each session was opened from, filled only when the app supplies
2
+ -- IAuthConfig.location — the Active Sessions screen shows it next to the IP.
3
+ -- Same shape and sanitizing as fonderie_login_events.location. Nullable, no
4
+ -- backfill.
5
+ ALTER TABLE fonderie_sessions ADD COLUMN IF NOT EXISTS location JSONB;
@@ -2,6 +2,41 @@ import { Middleware } from '@fonderie/core';
2
2
  import { IStoreAdapter } from '@fonderie/store';
3
3
  import { IRateLimitStore, IRateLimitRule } from '@fonderie/rate-limit';
4
4
 
5
+ /** Where a login attempt came from. Every field is optional; unknown is null. */
6
+ interface IRequestLocation {
7
+ country?: string | null;
8
+ countryName?: string | null;
9
+ subdivision?: string | null;
10
+ subdivisionName?: string | null;
11
+ city?: string | null;
12
+ /** Postal / ZIP code as the provider reports it (94043, K1A 0B1, SW1A). Approximate. */
13
+ postalCode?: string | null;
14
+ continent?: string | null;
15
+ timeZone?: string | null;
16
+ latitude?: number | null;
17
+ longitude?: number | null;
18
+ /** How far off the point may be, in kilometres (MaxMind's accuracy_radius).
19
+ * Lets a UI say "near Mountain View (±20 km)" instead of implying precision. */
20
+ accuracyRadius?: number | null;
21
+ /** Network facts — only resolvers backed by an IP-intelligence API know these. */
22
+ isp?: string | null;
23
+ org?: string | null;
24
+ asn?: string | null;
25
+ mobile?: boolean | null;
26
+ proxy?: boolean | null;
27
+ hosting?: boolean | null;
28
+ }
29
+ interface ILocationRequest {
30
+ /** The resolved client IP (adapter-resolved, proxy-aware), or null. */
31
+ ip: string | null;
32
+ /** The incoming request's headers — for platform geolocation headers. */
33
+ headers: Headers;
34
+ }
35
+ type LocationResolver = (req: ILocationRequest) => IRequestLocation | null | undefined | Promise<IRequestLocation | null | undefined>;
36
+ /** Keep only well-formed, bounded fields. Returns null when nothing survives. */
37
+ declare function sanitizeLocation(input: unknown): IRequestLocation | null;
38
+ declare function resolveLocation(resolver: LocationResolver | undefined, req: ILocationRequest, timeoutMs?: number): Promise<IRequestLocation | null>;
39
+
5
40
  interface IAuthRateLimitConfig {
6
41
  store?: IRateLimitStore;
7
42
  rules?: Partial<Record<AuthLimitedRoute, IRateLimitRule | false>>;
@@ -51,6 +86,7 @@ declare const MESSAGE_KEYS: {
51
86
  };
52
87
  type AuthMessageKey = (typeof MESSAGE_KEYS)[keyof typeof MESSAGE_KEYS];
53
88
  interface IAuthConfig extends IAuthSecrets, IAuthRuntimeConfig {
89
+ location?: LocationResolver;
54
90
  secureCookies?: boolean;
55
91
  rateLimit?: IAuthRateLimitConfig | false;
56
92
  accessTokenDuration?: string;
@@ -76,4 +112,4 @@ type AuthRouteOverride = string | {
76
112
 
77
113
  declare function withSession(store: IStoreAdapter, config: IAuthConfig): Middleware;
78
114
 
79
- export { AUTH_CONFIG_KEYS as A, type IAuthConfig as I, MESSAGE_KEYS as M, type AuthLimitedRoute as a, type AuthMessageKey as b, type IAuthRateLimitConfig as c, type IAuthRuntimeConfig as d, type IAuthSecrets as e, type IDataExportContributor as f, buildAuthAccountLimiter as g, buildAuthIpLimiter as h, withSession as w };
115
+ export { AUTH_CONFIG_KEYS as A, type IAuthConfig as I, type LocationResolver as L, MESSAGE_KEYS as M, type IRequestLocation as a, type AuthLimitedRoute as b, type AuthMessageKey as c, type IAuthRateLimitConfig as d, type IAuthRuntimeConfig as e, type IAuthSecrets as f, type IDataExportContributor as g, type ILocationRequest as h, buildAuthAccountLimiter as i, buildAuthIpLimiter as j, resolveLocation as r, sanitizeLocation as s, withSession as w };
@@ -2,6 +2,41 @@ import { Middleware } from '@fonderie/core';
2
2
  import { IStoreAdapter } from '@fonderie/store';
3
3
  import { IRateLimitStore, IRateLimitRule } from '@fonderie/rate-limit';
4
4
 
5
+ /** Where a login attempt came from. Every field is optional; unknown is null. */
6
+ interface IRequestLocation {
7
+ country?: string | null;
8
+ countryName?: string | null;
9
+ subdivision?: string | null;
10
+ subdivisionName?: string | null;
11
+ city?: string | null;
12
+ /** Postal / ZIP code as the provider reports it (94043, K1A 0B1, SW1A). Approximate. */
13
+ postalCode?: string | null;
14
+ continent?: string | null;
15
+ timeZone?: string | null;
16
+ latitude?: number | null;
17
+ longitude?: number | null;
18
+ /** How far off the point may be, in kilometres (MaxMind's accuracy_radius).
19
+ * Lets a UI say "near Mountain View (±20 km)" instead of implying precision. */
20
+ accuracyRadius?: number | null;
21
+ /** Network facts — only resolvers backed by an IP-intelligence API know these. */
22
+ isp?: string | null;
23
+ org?: string | null;
24
+ asn?: string | null;
25
+ mobile?: boolean | null;
26
+ proxy?: boolean | null;
27
+ hosting?: boolean | null;
28
+ }
29
+ interface ILocationRequest {
30
+ /** The resolved client IP (adapter-resolved, proxy-aware), or null. */
31
+ ip: string | null;
32
+ /** The incoming request's headers — for platform geolocation headers. */
33
+ headers: Headers;
34
+ }
35
+ type LocationResolver = (req: ILocationRequest) => IRequestLocation | null | undefined | Promise<IRequestLocation | null | undefined>;
36
+ /** Keep only well-formed, bounded fields. Returns null when nothing survives. */
37
+ declare function sanitizeLocation(input: unknown): IRequestLocation | null;
38
+ declare function resolveLocation(resolver: LocationResolver | undefined, req: ILocationRequest, timeoutMs?: number): Promise<IRequestLocation | null>;
39
+
5
40
  interface IAuthRateLimitConfig {
6
41
  store?: IRateLimitStore;
7
42
  rules?: Partial<Record<AuthLimitedRoute, IRateLimitRule | false>>;
@@ -51,6 +86,7 @@ declare const MESSAGE_KEYS: {
51
86
  };
52
87
  type AuthMessageKey = (typeof MESSAGE_KEYS)[keyof typeof MESSAGE_KEYS];
53
88
  interface IAuthConfig extends IAuthSecrets, IAuthRuntimeConfig {
89
+ location?: LocationResolver;
54
90
  secureCookies?: boolean;
55
91
  rateLimit?: IAuthRateLimitConfig | false;
56
92
  accessTokenDuration?: string;
@@ -76,4 +112,4 @@ type AuthRouteOverride = string | {
76
112
 
77
113
  declare function withSession(store: IStoreAdapter, config: IAuthConfig): Middleware;
78
114
 
79
- export { AUTH_CONFIG_KEYS as A, type IAuthConfig as I, MESSAGE_KEYS as M, type AuthLimitedRoute as a, type AuthMessageKey as b, type IAuthRateLimitConfig as c, type IAuthRuntimeConfig as d, type IAuthSecrets as e, type IDataExportContributor as f, buildAuthAccountLimiter as g, buildAuthIpLimiter as h, withSession as w };
115
+ export { AUTH_CONFIG_KEYS as A, type IAuthConfig as I, type LocationResolver as L, MESSAGE_KEYS as M, type IRequestLocation as a, type AuthLimitedRoute as b, type AuthMessageKey as c, type IAuthRateLimitConfig as d, type IAuthRuntimeConfig as e, type IAuthSecrets as f, type IDataExportContributor as g, type ILocationRequest as h, buildAuthAccountLimiter as i, buildAuthIpLimiter as j, resolveLocation as r, sanitizeLocation as s, withSession as w };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fonderie/auth",
3
- "version": "7.9.3",
3
+ "version": "7.10.0",
4
4
  "fonderie": { "stability": "stable" },
5
5
  "description": "Drop-in auth for SaaS — email/password, phone OTP, Google OAuth, stateless JWT sessions, TOTP-based MFA with backup codes, and self-service password recovery.",
6
6
  "keywords": [