@faable/auth-sdk 2.8.20 → 2.8.22

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.
@@ -108,6 +108,7 @@ export type UserAppCreate = components["schemas"]["UserAppCreate"];
108
108
  export type UserAppMetadata = components["schemas"]["UserAppMetadata"];
109
109
  export type UserAppMetadataUpdate = components["schemas"]["UserAppMetadataUpdate"];
110
110
  export type UserCreate = components["schemas"]["UserCreate"];
111
+ export type UserIpInsight = components["schemas"]["UserIpInsight"];
111
112
  export type UserMetadata = components["schemas"]["UserMetadata"];
112
113
  export type UserUpdate = components["schemas"]["UserUpdate"];
113
114
  export type UserUserCreate = components["schemas"]["UserUserCreate"];
@@ -463,22 +463,7 @@ export declare abstract class GeneratedFaableAuthApi extends FaableApi {
463
463
  slug: string;
464
464
  logo_src?: string | null | undefined;
465
465
  icon_src?: string | null | undefined;
466
- callback_hostnames: string[]; /**
467
- * `POST /user/import` — operationId: `user/import`
468
- *
469
- * Import users, with their password hashes
470
- *
471
- * Creates up to 500 users per request, for migrations from another provider. Each row can carry a `password_hash` exported from that provider (bcrypt, scrypt, Firebase scrypt, PBKDF2, argon2): the user signs in with their existing password and the hash is upgraded to argon2id on that first login. Fast digests (MD5, SHA-1, SHA-2) and low costs are refused per row with `weak_password_hash`. Rows are matched by email, so the same file can be sent twice: existing users are `skipped` unless `update_existing`. A bad row never fails the batch: each row gets its own `status` and `error`. Imported users get none of the built-in new-user emails. The response never contains a hash, and neither does the audit log.
472
- *
473
- * @throws {FaableApiError} `validation_error` (400) — The body, query or path failed schema validation. `details.issues` lists each failing field.
474
- * @throws {FaableApiError} `unauthorized` (401) — The request carries no valid credentials.
475
- * @throws {FaableApiError} `forbidden` (403) — The credentials are valid but do not allow this operation.
476
- * @throws {FaableApiError} `insufficient_scope` (403) — The token lacks a scope this operation requires. `message` names it.
477
- * @throws {FaableApiError} `user_suspended` (403) — The user is suspended and cannot sign in or be modified.
478
- * @throws {FaableApiError} `account_not_found` (404) — No Auth Account matches the request (domain, header or token).
479
- * @throws {FaableApiError} `too_many_requests` (429) — Rate limit exceeded. Honour the `Retry-After` header.
480
- * @throws {FaableApiError} `internal_error` (500) — Unexpected server error. Retry later; the request id is logged.
481
- */
466
+ callback_hostnames: string[];
482
467
  token_signature: string;
483
468
  token_signing_alg: "RS256";
484
469
  default_connection?: unknown;
@@ -548,22 +533,7 @@ export declare abstract class GeneratedFaableAuthApi extends FaableApi {
548
533
  slug: string;
549
534
  logo_src?: string | null | undefined;
550
535
  icon_src?: string | null | undefined;
551
- callback_hostnames: string[]; /**
552
- * `POST /user/import` — operationId: `user/import`
553
- *
554
- * Import users, with their password hashes
555
- *
556
- * Creates up to 500 users per request, for migrations from another provider. Each row can carry a `password_hash` exported from that provider (bcrypt, scrypt, Firebase scrypt, PBKDF2, argon2): the user signs in with their existing password and the hash is upgraded to argon2id on that first login. Fast digests (MD5, SHA-1, SHA-2) and low costs are refused per row with `weak_password_hash`. Rows are matched by email, so the same file can be sent twice: existing users are `skipped` unless `update_existing`. A bad row never fails the batch: each row gets its own `status` and `error`. Imported users get none of the built-in new-user emails. The response never contains a hash, and neither does the audit log.
557
- *
558
- * @throws {FaableApiError} `validation_error` (400) — The body, query or path failed schema validation. `details.issues` lists each failing field.
559
- * @throws {FaableApiError} `unauthorized` (401) — The request carries no valid credentials.
560
- * @throws {FaableApiError} `forbidden` (403) — The credentials are valid but do not allow this operation.
561
- * @throws {FaableApiError} `insufficient_scope` (403) — The token lacks a scope this operation requires. `message` names it.
562
- * @throws {FaableApiError} `user_suspended` (403) — The user is suspended and cannot sign in or be modified.
563
- * @throws {FaableApiError} `account_not_found` (404) — No Auth Account matches the request (domain, header or token).
564
- * @throws {FaableApiError} `too_many_requests` (429) — Rate limit exceeded. Honour the `Retry-After` header.
565
- * @throws {FaableApiError} `internal_error` (500) — Unexpected server error. Retry later; the request id is logged.
566
- */
536
+ callback_hostnames: string[];
567
537
  token_signature: string;
568
538
  token_signing_alg: "RS256";
569
539
  default_connection?: unknown;
@@ -636,22 +606,7 @@ export declare abstract class GeneratedFaableAuthApi extends FaableApi {
636
606
  slug: string;
637
607
  logo_src?: string | null | undefined;
638
608
  icon_src?: string | null | undefined;
639
- callback_hostnames: string[]; /**
640
- * `POST /user/import` — operationId: `user/import`
641
- *
642
- * Import users, with their password hashes
643
- *
644
- * Creates up to 500 users per request, for migrations from another provider. Each row can carry a `password_hash` exported from that provider (bcrypt, scrypt, Firebase scrypt, PBKDF2, argon2): the user signs in with their existing password and the hash is upgraded to argon2id on that first login. Fast digests (MD5, SHA-1, SHA-2) and low costs are refused per row with `weak_password_hash`. Rows are matched by email, so the same file can be sent twice: existing users are `skipped` unless `update_existing`. A bad row never fails the batch: each row gets its own `status` and `error`. Imported users get none of the built-in new-user emails. The response never contains a hash, and neither does the audit log.
645
- *
646
- * @throws {FaableApiError} `validation_error` (400) — The body, query or path failed schema validation. `details.issues` lists each failing field.
647
- * @throws {FaableApiError} `unauthorized` (401) — The request carries no valid credentials.
648
- * @throws {FaableApiError} `forbidden` (403) — The credentials are valid but do not allow this operation.
649
- * @throws {FaableApiError} `insufficient_scope` (403) — The token lacks a scope this operation requires. `message` names it.
650
- * @throws {FaableApiError} `user_suspended` (403) — The user is suspended and cannot sign in or be modified.
651
- * @throws {FaableApiError} `account_not_found` (404) — No Auth Account matches the request (domain, header or token).
652
- * @throws {FaableApiError} `too_many_requests` (429) — Rate limit exceeded. Honour the `Retry-After` header.
653
- * @throws {FaableApiError} `internal_error` (500) — Unexpected server error. Retry later; the request id is logged.
654
- */
609
+ callback_hostnames: string[];
655
610
  token_signature: string;
656
611
  token_signing_alg: "RS256";
657
612
  default_connection?: unknown;
@@ -5769,6 +5724,7 @@ export declare abstract class GeneratedFaableAuthApi extends FaableApi {
5769
5724
  status: "active" | "revoked";
5770
5725
  ip?: string | undefined;
5771
5726
  user_agent?: string | undefined;
5727
+ language?: string | undefined;
5772
5728
  device_name?: string | undefined;
5773
5729
  client?: unknown;
5774
5730
  connection?: unknown;
@@ -5809,6 +5765,7 @@ export declare abstract class GeneratedFaableAuthApi extends FaableApi {
5809
5765
  status: "active" | "revoked";
5810
5766
  ip?: string | undefined;
5811
5767
  user_agent?: string | undefined;
5768
+ language?: string | undefined;
5812
5769
  device_name?: string | undefined;
5813
5770
  client?: unknown;
5814
5771
  connection?: unknown;
@@ -5831,6 +5788,7 @@ export declare abstract class GeneratedFaableAuthApi extends FaableApi {
5831
5788
  status: "active" | "revoked";
5832
5789
  ip?: string | undefined;
5833
5790
  user_agent?: string | undefined;
5791
+ language?: string | undefined;
5834
5792
  device_name?: string | undefined;
5835
5793
  client?: unknown;
5836
5794
  connection?: unknown;
@@ -5856,6 +5814,7 @@ export declare abstract class GeneratedFaableAuthApi extends FaableApi {
5856
5814
  status: "active" | "revoked";
5857
5815
  ip?: string | undefined;
5858
5816
  user_agent?: string | undefined;
5817
+ language?: string | undefined;
5859
5818
  device_name?: string | undefined;
5860
5819
  client?: unknown;
5861
5820
  connection?: unknown;
@@ -5897,6 +5856,7 @@ export declare abstract class GeneratedFaableAuthApi extends FaableApi {
5897
5856
  status: "active" | "revoked";
5898
5857
  ip?: string | undefined;
5899
5858
  user_agent?: string | undefined;
5859
+ language?: string | undefined;
5900
5860
  device_name?: string | undefined;
5901
5861
  client?: unknown;
5902
5862
  connection?: unknown;
@@ -6919,6 +6879,52 @@ export declare abstract class GeneratedFaableAuthApi extends FaableApi {
6919
6879
  } | undefined;
6920
6880
  }[];
6921
6881
  }>;
6882
+ /**
6883
+ * `GET /user/{user_id}/ip` — operationId: `user/ip`
6884
+ *
6885
+ * Insight into a user's last IP
6886
+ *
6887
+ * Location and network (country, ASN), reputation signals and usage of the user's `last_ip`. Empty when the user has never logged in.
6888
+ *
6889
+ * @throws {FaableApiError} `validation_error` (400) — The body, query or path failed schema validation. `details.issues` lists each failing field.
6890
+ * @throws {FaableApiError} `unauthorized` (401) — The request carries no valid credentials.
6891
+ * @throws {FaableApiError} `forbidden` (403) — The credentials are valid but do not allow this operation.
6892
+ * @throws {FaableApiError} `insufficient_scope` (403) — The token lacks a scope this operation requires. `message` names it.
6893
+ * @throws {FaableApiError} `user_suspended` (403) — The user is suspended and cannot sign in or be modified.
6894
+ * @throws {FaableApiError} `account_not_found` (404) — No Auth Account matches the request (domain, header or token).
6895
+ * @throws {FaableApiError} `too_many_requests` (429) — Rate limit exceeded. Honour the `Retry-After` header.
6896
+ * @throws {FaableApiError} `internal_error` (500) — Unexpected server error. Retry later; the request id is logged.
6897
+ */
6898
+ userIp(user_id: string): Promise<{
6899
+ ip?: string | undefined;
6900
+ geo?: {
6901
+ country_code?: string | undefined;
6902
+ country_name?: string | undefined;
6903
+ continent?: string | undefined;
6904
+ asn?: number | undefined;
6905
+ as_org?: string | undefined;
6906
+ source: {
6907
+ name: string;
6908
+ url: string;
6909
+ license: string;
6910
+ edition: string;
6911
+ };
6912
+ } | undefined;
6913
+ signals?: {
6914
+ datacenter: boolean;
6915
+ apple_private_relay: boolean;
6916
+ active_blocks: number;
6917
+ network_active_blocks: number;
6918
+ suspended_denies: number;
6919
+ network_suspended_denies: number;
6920
+ } | undefined;
6921
+ activity?: {
6922
+ logins: number;
6923
+ first_login_at?: string | undefined;
6924
+ active_sessions: number;
6925
+ other_users: number;
6926
+ } | undefined;
6927
+ }>;
6922
6928
  /**
6923
6929
  * `GET /user` — operationId: `user/list`
6924
6930
  *
@@ -2623,6 +2623,26 @@ export class GeneratedFaableAuthApi extends FaableApi {
2623
2623
  userImport(data) {
2624
2624
  return this.fetcher.post(`/user/import`, data);
2625
2625
  }
2626
+ /**
2627
+ * `GET /user/{user_id}/ip` — operationId: `user/ip`
2628
+ *
2629
+ * Insight into a user's last IP
2630
+ *
2631
+ * Location and network (country, ASN), reputation signals and usage of the user's `last_ip`. Empty when the user has never logged in.
2632
+ *
2633
+ * @throws {FaableApiError} `validation_error` (400) — The body, query or path failed schema validation. `details.issues` lists each failing field.
2634
+ * @throws {FaableApiError} `unauthorized` (401) — The request carries no valid credentials.
2635
+ * @throws {FaableApiError} `forbidden` (403) — The credentials are valid but do not allow this operation.
2636
+ * @throws {FaableApiError} `insufficient_scope` (403) — The token lacks a scope this operation requires. `message` names it.
2637
+ * @throws {FaableApiError} `user_suspended` (403) — The user is suspended and cannot sign in or be modified.
2638
+ * @throws {FaableApiError} `account_not_found` (404) — No Auth Account matches the request (domain, header or token).
2639
+ * @throws {FaableApiError} `too_many_requests` (429) — Rate limit exceeded. Honour the `Retry-After` header.
2640
+ * @throws {FaableApiError} `internal_error` (500) — Unexpected server error. Retry later; the request id is logged.
2641
+ */
2642
+ userIp(user_id) {
2643
+ requireId("user_id", user_id);
2644
+ return this.fetcher.get(`/user/${user_id}/ip`);
2645
+ }
2626
2646
  /**
2627
2647
  * `GET /user` — operationId: `user/list`
2628
2648
  *
@@ -880,6 +880,26 @@ export interface paths {
880
880
  patch?: never;
881
881
  trace?: never;
882
882
  };
883
+ "/user/{user_id}/ip": {
884
+ parameters: {
885
+ query?: never;
886
+ header?: never;
887
+ path?: never;
888
+ cookie?: never;
889
+ };
890
+ /**
891
+ * Insight into a user's last IP
892
+ * @description Location and network (country, ASN), reputation signals and usage of the user's `last_ip`. Empty when the user has never logged in.
893
+ */
894
+ get: operations["user/ip"];
895
+ put?: never;
896
+ post?: never;
897
+ delete?: never;
898
+ options?: never;
899
+ head?: never;
900
+ patch?: never;
901
+ trace?: never;
902
+ };
883
903
  "/user/import": {
884
904
  parameters: {
885
905
  query?: never;
@@ -5928,6 +5948,17 @@ export interface components {
5928
5948
  label: string;
5929
5949
  url: string;
5930
5950
  };
5951
+ /** @description How a passwordless type behaves. Present only on types of the `passwordless` category. */
5952
+ passwordless?: {
5953
+ /** @description Digits in an emailed code. */
5954
+ code_length: number;
5955
+ /** @description Seconds an emailed code stays valid. */
5956
+ code_ttl_seconds: number;
5957
+ /** @description Seconds a magic link stays valid. */
5958
+ link_ttl_seconds: number;
5959
+ /** @description One email per address in this window: a start inside it sends nothing new. */
5960
+ resend_interval_seconds: number;
5961
+ };
5931
5962
  /** @description The credentials a tenant fills in for its own app, when they are more than a client id and a secret (Apple). Absent means client id + secret. */
5932
5963
  credential_fields?: {
5933
5964
  /** @description Connection field this input writes, as a dotted path (`client_id`, `provider_options.team_id`). */
@@ -6486,6 +6517,42 @@ export interface components {
6486
6517
  last_used_at?: string;
6487
6518
  remaining?: number;
6488
6519
  };
6520
+ UserIpInsight: {
6521
+ ip?: string;
6522
+ geo?: {
6523
+ country_code?: string;
6524
+ country_name?: string;
6525
+ continent?: string;
6526
+ asn?: number;
6527
+ as_org?: string;
6528
+ /** @description Where the location comes from. The data is CC BY 4.0: anything that shows it must credit this source. */
6529
+ source: {
6530
+ name: string;
6531
+ url: string;
6532
+ license: string;
6533
+ edition: string;
6534
+ };
6535
+ };
6536
+ /** @description The same values a tenant Action reads as `event.request.ip_*` for a login from this IP now. */
6537
+ signals?: {
6538
+ datacenter: boolean;
6539
+ apple_private_relay: boolean;
6540
+ active_blocks: number;
6541
+ network_active_blocks: number;
6542
+ suspended_denies: number;
6543
+ network_suspended_denies: number;
6544
+ };
6545
+ activity?: {
6546
+ /** @description This user's logins from this IP in the last year. */
6547
+ logins: number;
6548
+ /** Format: date-time */
6549
+ first_login_at?: string;
6550
+ /** @description This user's active sessions last seen from this IP. */
6551
+ active_sessions: number;
6552
+ /** @description Other users of this tenant whose last login came from this IP. */
6553
+ other_users: number;
6554
+ };
6555
+ };
6489
6556
  /** @description Identity */
6490
6557
  Identity: {
6491
6558
  /** @description Identity ID */
@@ -7981,6 +8048,8 @@ export interface components {
7981
8048
  ip?: string;
7982
8049
  /** @description Raw `User-Agent` header at the last authentication. */
7983
8050
  user_agent?: string;
8051
+ /** @description First language tag of the browser's `Accept-Language` at the last authentication, e.g. `es-ES`. */
8052
+ language?: string;
7984
8053
  /** @description The name a device gave itself when it asked for a code (device authorization grant), e.g. the hostname `faable login` runs on. Absent for browser sessions: a browser does not expose one. */
7985
8054
  device_name?: string;
7986
8055
  client?: components["schemas"]["Client"] | string | unknown;
@@ -16524,6 +16593,106 @@ export interface operations {
16524
16593
  };
16525
16594
  };
16526
16595
  };
16596
+ "user/ip": {
16597
+ parameters: {
16598
+ query?: never;
16599
+ header?: never;
16600
+ path: {
16601
+ user_id: string;
16602
+ };
16603
+ cookie?: never;
16604
+ };
16605
+ requestBody?: never;
16606
+ responses: {
16607
+ /** @description Default Response */
16608
+ 200: {
16609
+ headers: {
16610
+ [name: string]: unknown;
16611
+ };
16612
+ content: {
16613
+ "application/json": components["schemas"]["UserIpInsight"];
16614
+ };
16615
+ };
16616
+ /** @description `validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field. */
16617
+ 400: {
16618
+ headers: {
16619
+ [name: string]: unknown;
16620
+ };
16621
+ content: {
16622
+ "application/json": components["schemas"]["ErrorResponse"] & {
16623
+ /** @enum {string} */
16624
+ error_code?: "validation_error";
16625
+ };
16626
+ };
16627
+ };
16628
+ /** @description `unauthorized` — The request carries no valid credentials. */
16629
+ 401: {
16630
+ headers: {
16631
+ [name: string]: unknown;
16632
+ };
16633
+ content: {
16634
+ "application/json": components["schemas"]["ErrorResponse"] & {
16635
+ /** @enum {string} */
16636
+ error_code?: "unauthorized";
16637
+ };
16638
+ };
16639
+ };
16640
+ /**
16641
+ * @description `forbidden` — The credentials are valid but do not allow this operation.
16642
+ *
16643
+ * `insufficient_scope` — The token lacks a scope this operation requires. `message` names it.
16644
+ *
16645
+ * `user_suspended` — The user is suspended and cannot sign in or be modified.
16646
+ */
16647
+ 403: {
16648
+ headers: {
16649
+ [name: string]: unknown;
16650
+ };
16651
+ content: {
16652
+ "application/json": components["schemas"]["ErrorResponse"] & {
16653
+ /** @enum {string} */
16654
+ error_code?: "forbidden" | "insufficient_scope" | "user_suspended";
16655
+ };
16656
+ };
16657
+ };
16658
+ /** @description `account_not_found` — No Auth Account matches the request (domain, header or token). */
16659
+ 404: {
16660
+ headers: {
16661
+ [name: string]: unknown;
16662
+ };
16663
+ content: {
16664
+ "application/json": components["schemas"]["ErrorResponse"] & {
16665
+ /** @enum {string} */
16666
+ error_code?: "account_not_found";
16667
+ };
16668
+ };
16669
+ };
16670
+ /** @description `too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header. */
16671
+ 429: {
16672
+ headers: {
16673
+ [name: string]: unknown;
16674
+ };
16675
+ content: {
16676
+ "application/json": components["schemas"]["ErrorResponse"] & {
16677
+ /** @enum {string} */
16678
+ error_code?: "too_many_requests";
16679
+ };
16680
+ };
16681
+ };
16682
+ /** @description `internal_error` — Unexpected server error. Retry later; the request id is logged. */
16683
+ 500: {
16684
+ headers: {
16685
+ [name: string]: unknown;
16686
+ };
16687
+ content: {
16688
+ "application/json": components["schemas"]["ErrorResponse"] & {
16689
+ /** @enum {string} */
16690
+ error_code?: "internal_error";
16691
+ };
16692
+ };
16693
+ };
16694
+ };
16695
+ };
16527
16696
  "user/import": {
16528
16697
  parameters: {
16529
16698
  query?: never;
@@ -30584,6 +30753,8 @@ export interface operations {
30584
30753
  ip?: string;
30585
30754
  /** @description Raw `User-Agent` header at the last authentication. */
30586
30755
  user_agent?: string;
30756
+ /** @description First language tag of the browser's `Accept-Language` at the last authentication, e.g. `es-ES`. */
30757
+ language?: string;
30587
30758
  /** @description The name a device gave itself when it asked for a code (device authorization grant), e.g. the hostname `faable login` runs on. Absent for browser sessions: a browser does not expose one. */
30588
30759
  device_name?: string;
30589
30760
  client?: components["schemas"]["Client"] | string | unknown;
@@ -30739,6 +30910,8 @@ export interface operations {
30739
30910
  ip?: string;
30740
30911
  /** @description Raw `User-Agent` header at the last authentication. */
30741
30912
  user_agent?: string;
30913
+ /** @description First language tag of the browser's `Accept-Language` at the last authentication, e.g. `es-ES`. */
30914
+ language?: string;
30742
30915
  /** @description The name a device gave itself when it asked for a code (device authorization grant), e.g. the hostname `faable login` runs on. Absent for browser sessions: a browser does not expose one. */
30743
30916
  device_name?: string;
30744
30917
  client?: components["schemas"]["Client"] | string | unknown;
package/dist/version.js CHANGED
@@ -9,13 +9,13 @@
9
9
  // login pages could not import the SDK at all. Same pattern as auth-js.
10
10
  //
11
11
  // The sentinels MUST stay byte-identical to the `from` values in `.releaserc`.
12
- export const version = "2.8.20";
12
+ export const version = "2.8.22";
13
13
  // Short git SHA of the released commit. The version dates a build; this names
14
14
  // the exact tree, so a canonical log line or an audit entry leads straight to
15
15
  // `git show <sha>`. Deliberately NOT hex: an unreleased build (dev, a local
16
16
  // link) cannot be mistaken for a real commit — auth only records values that
17
17
  // look like a SHA, and this one never will.
18
- export const commit = "f843227";
18
+ export const commit = "9419803";
19
19
  // What this SDK writes in `x-faable-client`. Exported for a consumer that
20
20
  // builds a strategy on its own (outside `FaableAuthApi`) and still wants the
21
21
  // token request attributed — a bare `authClientCredentials` stamps only what
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@faable/auth-sdk",
3
- "version": "2.8.20",
3
+ "version": "2.8.22",
4
4
  "author": "Marc Pomar <marc@faable.com>",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/spec/openapi.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "info": {
4
4
  "title": "@faablecloud/auth",
5
5
  "description": "Auth Platform made by Faable. Manage Users and Roles",
6
- "version": "2.86.4",
6
+ "version": "2.88.0",
7
7
  "license": {
8
8
  "name": "private",
9
9
  "url": "https://faable.com/docs/platform/privacy-policy"
@@ -1424,6 +1424,35 @@
1424
1424
  },
1425
1425
  "description": "Provider documentation for tenants registering their own OAuth app."
1426
1426
  },
1427
+ "passwordless": {
1428
+ "type": "object",
1429
+ "required": [
1430
+ "code_length",
1431
+ "code_ttl_seconds",
1432
+ "link_ttl_seconds",
1433
+ "resend_interval_seconds"
1434
+ ],
1435
+ "properties": {
1436
+ "code_length": {
1437
+ "type": "integer",
1438
+ "description": "Digits in an emailed code."
1439
+ },
1440
+ "code_ttl_seconds": {
1441
+ "type": "integer",
1442
+ "description": "Seconds an emailed code stays valid."
1443
+ },
1444
+ "link_ttl_seconds": {
1445
+ "type": "integer",
1446
+ "description": "Seconds a magic link stays valid."
1447
+ },
1448
+ "resend_interval_seconds": {
1449
+ "type": "integer",
1450
+ "description": "One email per address in this window: a start inside it sends nothing new."
1451
+ }
1452
+ },
1453
+ "additionalProperties": false,
1454
+ "description": "How a passwordless type behaves. Present only on types of the `passwordless` category."
1455
+ },
1427
1456
  "credential_fields": {
1428
1457
  "type": "array",
1429
1458
  "items": {
@@ -3675,6 +3704,119 @@
3675
3704
  }
3676
3705
  }
3677
3706
  },
3707
+ "UserIpInsight": {
3708
+ "type": "object",
3709
+ "properties": {
3710
+ "ip": {
3711
+ "type": "string"
3712
+ },
3713
+ "geo": {
3714
+ "type": "object",
3715
+ "required": [
3716
+ "source"
3717
+ ],
3718
+ "properties": {
3719
+ "country_code": {
3720
+ "type": "string"
3721
+ },
3722
+ "country_name": {
3723
+ "type": "string"
3724
+ },
3725
+ "continent": {
3726
+ "type": "string"
3727
+ },
3728
+ "asn": {
3729
+ "type": "integer"
3730
+ },
3731
+ "as_org": {
3732
+ "type": "string"
3733
+ },
3734
+ "source": {
3735
+ "type": "object",
3736
+ "required": [
3737
+ "name",
3738
+ "url",
3739
+ "license",
3740
+ "edition"
3741
+ ],
3742
+ "properties": {
3743
+ "name": {
3744
+ "type": "string"
3745
+ },
3746
+ "url": {
3747
+ "type": "string"
3748
+ },
3749
+ "license": {
3750
+ "type": "string"
3751
+ },
3752
+ "edition": {
3753
+ "type": "string"
3754
+ }
3755
+ },
3756
+ "description": "Where the location comes from. The data is CC BY 4.0: anything that shows it must credit this source."
3757
+ }
3758
+ }
3759
+ },
3760
+ "signals": {
3761
+ "type": "object",
3762
+ "required": [
3763
+ "datacenter",
3764
+ "apple_private_relay",
3765
+ "active_blocks",
3766
+ "network_active_blocks",
3767
+ "suspended_denies",
3768
+ "network_suspended_denies"
3769
+ ],
3770
+ "properties": {
3771
+ "datacenter": {
3772
+ "type": "boolean"
3773
+ },
3774
+ "apple_private_relay": {
3775
+ "type": "boolean"
3776
+ },
3777
+ "active_blocks": {
3778
+ "type": "integer"
3779
+ },
3780
+ "network_active_blocks": {
3781
+ "type": "integer"
3782
+ },
3783
+ "suspended_denies": {
3784
+ "type": "integer"
3785
+ },
3786
+ "network_suspended_denies": {
3787
+ "type": "integer"
3788
+ }
3789
+ },
3790
+ "description": "The same values a tenant Action reads as `event.request.ip_*` for a login from this IP now."
3791
+ },
3792
+ "activity": {
3793
+ "type": "object",
3794
+ "required": [
3795
+ "logins",
3796
+ "active_sessions",
3797
+ "other_users"
3798
+ ],
3799
+ "properties": {
3800
+ "logins": {
3801
+ "type": "integer",
3802
+ "description": "This user's logins from this IP in the last year."
3803
+ },
3804
+ "first_login_at": {
3805
+ "type": "string",
3806
+ "format": "date-time"
3807
+ },
3808
+ "active_sessions": {
3809
+ "type": "integer",
3810
+ "description": "This user's active sessions last seen from this IP."
3811
+ },
3812
+ "other_users": {
3813
+ "type": "integer",
3814
+ "description": "Other users of this tenant whose last login came from this IP."
3815
+ }
3816
+ }
3817
+ }
3818
+ }
3819
+ },
3678
3820
  "Identity": {
3679
3821
  "type": "object",
3680
3822
  "required": [
@@ -7902,6 +8044,10 @@
7902
8044
  "type": "string",
7903
8045
  "description": "Raw `User-Agent` header at the last authentication."
7904
8046
  },
8047
+ "language": {
8048
+ "type": "string",
8049
+ "description": "First language tag of the browser's `Accept-Language` at the last authentication, e.g. `es-ES`."
8050
+ },
7905
8051
  "device_name": {
7906
8052
  "type": "string",
7907
8053
  "description": "The name a device gave itself when it asked for a code (device authorization grant), e.g. the hostname `faable login` runs on. Absent for browser sessions: a browser does not expose one."
@@ -30892,6 +31038,195 @@
30892
31038
  }
30893
31039
  }
30894
31040
  },
31041
+ "/user/{user_id}/ip": {
31042
+ "get": {
31043
+ "operationId": "user/ip",
31044
+ "summary": "Insight into a user's last IP",
31045
+ "tags": [
31046
+ "user"
31047
+ ],
31048
+ "description": "Location and network (country, ASN), reputation signals and usage of the user's `last_ip`. Empty when the user has never logged in.",
31049
+ "parameters": [
31050
+ {
31051
+ "schema": {
31052
+ "type": "string"
31053
+ },
31054
+ "in": "path",
31055
+ "name": "user_id",
31056
+ "required": true
31057
+ }
31058
+ ],
31059
+ "security": [
31060
+ {
31061
+ "bearerAuth": []
31062
+ }
31063
+ ],
31064
+ "responses": {
31065
+ "200": {
31066
+ "description": "Default Response",
31067
+ "content": {
31068
+ "application/json": {
31069
+ "schema": {
31070
+ "$ref": "#/components/schemas/UserIpInsight"
31071
+ }
31072
+ }
31073
+ }
31074
+ },
31075
+ "400": {
31076
+ "description": "`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.",
31077
+ "content": {
31078
+ "application/json": {
31079
+ "schema": {
31080
+ "allOf": [
31081
+ {
31082
+ "$ref": "#/components/schemas/ErrorResponse"
31083
+ },
31084
+ {
31085
+ "type": "object",
31086
+ "properties": {
31087
+ "error_code": {
31088
+ "type": "string",
31089
+ "enum": [
31090
+ "validation_error"
31091
+ ]
31092
+ }
31093
+ }
31094
+ }
31095
+ ]
31096
+ }
31097
+ }
31098
+ }
31099
+ },
31100
+ "401": {
31101
+ "description": "`unauthorized` — The request carries no valid credentials.",
31102
+ "content": {
31103
+ "application/json": {
31104
+ "schema": {
31105
+ "allOf": [
31106
+ {
31107
+ "$ref": "#/components/schemas/ErrorResponse"
31108
+ },
31109
+ {
31110
+ "type": "object",
31111
+ "properties": {
31112
+ "error_code": {
31113
+ "type": "string",
31114
+ "enum": [
31115
+ "unauthorized"
31116
+ ]
31117
+ }
31118
+ }
31119
+ }
31120
+ ]
31121
+ }
31122
+ }
31123
+ }
31124
+ },
31125
+ "403": {
31126
+ "description": "`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.",
31127
+ "content": {
31128
+ "application/json": {
31129
+ "schema": {
31130
+ "allOf": [
31131
+ {
31132
+ "$ref": "#/components/schemas/ErrorResponse"
31133
+ },
31134
+ {
31135
+ "type": "object",
31136
+ "properties": {
31137
+ "error_code": {
31138
+ "type": "string",
31139
+ "enum": [
31140
+ "forbidden",
31141
+ "insufficient_scope",
31142
+ "user_suspended"
31143
+ ]
31144
+ }
31145
+ }
31146
+ }
31147
+ ]
31148
+ }
31149
+ }
31150
+ }
31151
+ },
31152
+ "404": {
31153
+ "description": "`account_not_found` — No Auth Account matches the request (domain, header or token).",
31154
+ "content": {
31155
+ "application/json": {
31156
+ "schema": {
31157
+ "allOf": [
31158
+ {
31159
+ "$ref": "#/components/schemas/ErrorResponse"
31160
+ },
31161
+ {
31162
+ "type": "object",
31163
+ "properties": {
31164
+ "error_code": {
31165
+ "type": "string",
31166
+ "enum": [
31167
+ "account_not_found"
31168
+ ]
31169
+ }
31170
+ }
31171
+ }
31172
+ ]
31173
+ }
31174
+ }
31175
+ }
31176
+ },
31177
+ "429": {
31178
+ "description": "`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.",
31179
+ "content": {
31180
+ "application/json": {
31181
+ "schema": {
31182
+ "allOf": [
31183
+ {
31184
+ "$ref": "#/components/schemas/ErrorResponse"
31185
+ },
31186
+ {
31187
+ "type": "object",
31188
+ "properties": {
31189
+ "error_code": {
31190
+ "type": "string",
31191
+ "enum": [
31192
+ "too_many_requests"
31193
+ ]
31194
+ }
31195
+ }
31196
+ }
31197
+ ]
31198
+ }
31199
+ }
31200
+ }
31201
+ },
31202
+ "500": {
31203
+ "description": "`internal_error` — Unexpected server error. Retry later; the request id is logged.",
31204
+ "content": {
31205
+ "application/json": {
31206
+ "schema": {
31207
+ "allOf": [
31208
+ {
31209
+ "$ref": "#/components/schemas/ErrorResponse"
31210
+ },
31211
+ {
31212
+ "type": "object",
31213
+ "properties": {
31214
+ "error_code": {
31215
+ "type": "string",
31216
+ "enum": [
31217
+ "internal_error"
31218
+ ]
31219
+ }
31220
+ }
31221
+ }
31222
+ ]
31223
+ }
31224
+ }
31225
+ }
31226
+ }
31227
+ }
31228
+ }
31229
+ },
30895
31230
  "/user/import": {
30896
31231
  "post": {
30897
31232
  "operationId": "user/import",
@@ -62600,6 +62935,10 @@
62600
62935
  "type": "string",
62601
62936
  "description": "Raw `User-Agent` header at the last authentication."
62602
62937
  },
62938
+ "language": {
62939
+ "type": "string",
62940
+ "description": "First language tag of the browser's `Accept-Language` at the last authentication, e.g. `es-ES`."
62941
+ },
62603
62942
  "device_name": {
62604
62943
  "type": "string",
62605
62944
  "description": "The name a device gave itself when it asked for a code (device authorization grant), e.g. the hostname `faable login` runs on. Absent for browser sessions: a browser does not expose one."
@@ -62941,6 +63280,10 @@
62941
63280
  "type": "string",
62942
63281
  "description": "Raw `User-Agent` header at the last authentication."
62943
63282
  },
63283
+ "language": {
63284
+ "type": "string",
63285
+ "description": "First language tag of the browser's `Accept-Language` at the last authentication, e.g. `es-ES`."
63286
+ },
62944
63287
  "device_name": {
62945
63288
  "type": "string",
62946
63289
  "description": "The name a device gave itself when it asked for a code (device authorization grant), e.g. the hostname `faable login` runs on. Absent for browser sessions: a browser does not expose one."