@tula-tech/core 0.1.1 → 0.3.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.
Files changed (3) hide show
  1. package/dist/index.d.ts +1132 -92
  2. package/dist/index.js +684 -399
  3. package/package.json +3 -2
package/dist/index.d.ts CHANGED
@@ -4,6 +4,27 @@ import { DeviceKey as DeviceKey2 } from "@tula-tech/contract/device-binding";
4
4
  import { ErrorCode as ErrorCode2 } from "@tula-tech/contract/error-codes";
5
5
  /** Schemas of the client API (`/v1/client/*`), by their name in the OpenAPI document. */
6
6
  interface Schemas {
7
+ AcceptedInvitation: {
8
+ id: string;
9
+ name: string;
10
+ slug: string;
11
+ role: string;
12
+ permissions: string[];
13
+ createdAt: string;
14
+ };
15
+ ActiveOrganization: {
16
+ sessionId: string;
17
+ organizationId: string | null;
18
+ accessToken?: string;
19
+ accessTokenExpiresAt?: string;
20
+ };
21
+ AssignableRole: {
22
+ key: string;
23
+ name: string;
24
+ };
25
+ AssignableRoleList: {
26
+ data: Schemas["AssignableRole"][];
27
+ };
7
28
  BackupCodes: {
8
29
  codes: string[];
9
30
  };
@@ -31,6 +52,50 @@ interface Schemas {
31
52
  phone?: {
32
53
  enabled: boolean;
33
54
  };
55
+ organizations?: {
56
+ enabled: boolean;
57
+ allowUserCreated: boolean;
58
+ personalAccount: boolean;
59
+ };
60
+ };
61
+ CreateMySetupLinkRequest: {
62
+ name?: string;
63
+ expiresInDays?: number;
64
+ };
65
+ CreateMySsoConnectionRequest: {
66
+ protocol?: "oidc" | "saml";
67
+ name: string;
68
+ issuer?: string;
69
+ clientId?: string;
70
+ clientSecret?: string;
71
+ saml?: Schemas["SamlSettingsInput"];
72
+ attributeMapping?: Schemas["SsoAttributeMapping"];
73
+ defaultRole?: Schemas["OrganizationRoleKey"];
74
+ domains?: Schemas["OrganizationDomainName"][];
75
+ };
76
+ CreateOrganizationInvitationRequest: {
77
+ email: string;
78
+ role: Schemas["OrganizationRoleKey"];
79
+ redirectUrl: string;
80
+ };
81
+ CreateOrganizationRequest: {
82
+ name: string;
83
+ slug: Schemas["OrganizationSlug"];
84
+ };
85
+ CreatedSetupLink: {
86
+ id: string;
87
+ organizationId: string;
88
+ name: string;
89
+ /** The start of the token, to tell links apart in a list. */
90
+ prefix: string;
91
+ expiresAt: string;
92
+ revokedAt: string | null;
93
+ lastUsedAt: string | null;
94
+ createdAt: string;
95
+ /** The setup token. Shown once; only its hash is stored. */
96
+ token: string;
97
+ /** The address to send to the IT administrator. The token is in its fragment. */
98
+ url: string;
34
99
  };
35
100
  CurrentUser: {
36
101
  id: string;
@@ -64,7 +129,7 @@ interface Schemas {
64
129
  status: "verified";
65
130
  };
66
131
  EmailVerificationStrategy: "email_code" | "email_link";
67
- ErrorCode: "auth.invalid_credentials" | "auth.unauthenticated" | "auth.invalid_key" | "auth.forbidden" | "auth.user_banned" | "auth.method_disabled" | "auth.step_up_required" | "flow.not_found" | "flow.invalid_step" | "email.invalid" | "email.already_set" | "password.too_short" | "password.too_long" | "password.missing_lowercase" | "password.missing_uppercase" | "password.missing_number" | "password.missing_special" | "password.too_few_character_classes" | "password.contains_user_info" | "password.common" | "password.breached" | "password.repeated_characters" | "password.sequence" | "password.reused" | "password.not_set" | "verification.invalid_code" | "verification.expired" | "verification.too_many_attempts" | "verification.different_browser" | "mfa.invalid_code" | "mfa.already_enabled" | "mfa.not_enabled" | "mfa.enrolment_expired" | "mfa.not_available" | "mfa.enrolment_needs_other_sign_in" | "mfa.needs_other_sign_in" | "mfa.sms_not_allowed" | "mfa.phone_number_required" | "mfa.required_by_policy" | "oauth.access_denied" | "oauth.provider_error" | "oauth.state_invalid" | "oauth.ticket_invalid" | "oauth.different_browser" | "oauth.email_missing" | "oauth.email_unverified" | "oauth.account_exists" | "oauth.identity_in_use" | "oauth.already_linked" | "identity.last_sign_in_method" | "webhook.url_not_allowed" | "webhook.cannot_redeliver" | "webhook.rotation_refused" | "hook.denied" | "hook.unavailable" | "hook.url_not_allowed" | "passkey.registration_failed" | "passkey.already_registered" | "passkey.limit_reached" | "passkey.last_sign_in_method" | "phone.invalid" | "sms.disabled" | "sms.country_not_allowed" | "sms.unavailable" | "session.invalid_token" | "session.expired" | "session.revoked" | "session.reuse_detected" | "session.limit_reached" | "device.proof_invalid" | "device.nonce_required" | "device.binding_not_supported" | "device.binding_required" | "rate_limited" | "request.malformed" | "request.too_large" | "request.origin_not_allowed" | "request.redirect_not_allowed" | "validation.failed" | "resource.not_found" | "resource.conflict" | "precondition.required" | "precondition.failed" | "not_implemented" | "service.unavailable" | "internal";
132
+ ErrorCode: "auth.invalid_credentials" | "auth.unauthenticated" | "auth.invalid_key" | "auth.forbidden" | "auth.user_banned" | "auth.method_disabled" | "auth.step_up_required" | "flow.not_found" | "flow.invalid_step" | "email.invalid" | "email.already_set" | "password.too_short" | "password.too_long" | "password.missing_lowercase" | "password.missing_uppercase" | "password.missing_number" | "password.missing_special" | "password.too_few_character_classes" | "password.contains_user_info" | "password.common" | "password.breached" | "password.repeated_characters" | "password.sequence" | "password.reused" | "password.not_set" | "import.unsupported_algorithm" | "import.hash_malformed" | "import.hash_parameters_too_weak" | "import.hash_parameters_too_costly" | "verification.invalid_code" | "verification.expired" | "verification.too_many_attempts" | "verification.different_browser" | "mfa.invalid_code" | "mfa.already_enabled" | "mfa.not_enabled" | "mfa.enrolment_expired" | "mfa.not_available" | "mfa.enrolment_needs_other_sign_in" | "mfa.needs_other_sign_in" | "mfa.sms_not_allowed" | "mfa.phone_number_required" | "mfa.required_by_policy" | "oauth.access_denied" | "oauth.provider_error" | "oauth.state_invalid" | "oauth.ticket_invalid" | "oauth.different_browser" | "oauth.email_missing" | "oauth.email_unverified" | "oauth.account_exists" | "oauth.identity_in_use" | "oauth.already_linked" | "identity.last_sign_in_method" | "webhook.url_not_allowed" | "webhook.cannot_redeliver" | "webhook.rotation_refused" | "hook.denied" | "hook.unavailable" | "hook.url_not_allowed" | "passkey.registration_failed" | "passkey.already_registered" | "passkey.limit_reached" | "passkey.last_sign_in_method" | "phone.invalid" | "sms.disabled" | "sms.country_not_allowed" | "sms.unavailable" | "session.invalid_token" | "session.expired" | "session.revoked" | "session.reuse_detected" | "session.limit_reached" | "device.proof_invalid" | "device.nonce_required" | "device.binding_not_supported" | "device.binding_required" | "user.last_organization_manager" | "user.managed" | "organization.disabled" | "organization.slug_taken" | "organization.member_exists" | "organization.last_manager" | "organization.role_unknown" | "organization.not_a_member" | "organization.creation_disabled" | "organization.limit_reached" | "organization.personal_account_disabled" | "organization.role_exists" | "organization.role_in_use" | "organization.role_built_in" | "organization.role_limit" | "organization.permission_required" | "organization.role_not_assignable" | "organization.invitation_exists" | "organization.invitation_not_found" | "organization.invitation_expired" | "organization.invitation_revoked" | "organization.invitation_used" | "organization.invitation_wrong_account" | "organization.invitation_email_unverified" | "organization.invitation_role_gone" | "organization.domain_public" | "organization.domain_taken" | "organization.domain_address_mismatch" | "organization.domain_limit" | "organization.suggestion_unavailable" | "sso.disabled" | "sso.connection_not_found" | "sso.name_taken" | "sso.domain_taken" | "sso.domain_not_verified" | "sso.issuer_not_allowed" | "sso.saml_invalid" | "sso.connection_in_use" | "sso.invalid_transition" | "sso.url_not_https" | "sso.test_required" | "sso.test_not_found" | "sso.role_not_allowed" | "sso.unavailable" | "sso.provider_error" | "sso.user_not_provisioned" | "sso.email_domain_mismatch" | "rate_limited" | "request.malformed" | "request.too_large" | "request.origin_not_allowed" | "request.redirect_not_allowed" | "validation.failed" | "resource.not_found" | "resource.conflict" | "precondition.required" | "precondition.failed" | "not_implemented" | "service.unavailable" | "internal";
68
133
  ErrorEnvelope: {
69
134
  status: number;
70
135
  code: Schemas["ErrorCode"];
@@ -110,7 +175,7 @@ interface Schemas {
110
175
  strategy: Schemas["PreparedFirstFactorStrategy"];
111
176
  redirectUrl?: string;
112
177
  };
113
- FirstFactorStrategy: "password" | "email_code" | "email_link" | "passkey" | "sms_code" | "oauth_google" | "oauth_github" | "oauth_apple" | "oauth_microsoft" | "oauth_discord" | "oauth_linkedin" | "oauth_x" | "oauth_facebook";
178
+ FirstFactorStrategy: "password" | "email_code" | "email_link" | "passkey" | "sms_code" | "oauth_google" | "oauth_github" | "oauth_apple" | "oauth_microsoft" | "oauth_discord" | "oauth_linkedin" | "oauth_x" | "oauth_facebook" | "sso";
114
179
  FlowAttempt: {
115
180
  id: string;
116
181
  kind: Schemas["FlowKind"];
@@ -185,7 +250,26 @@ interface Schemas {
185
250
  IdentityList: {
186
251
  data: Schemas["Identity"][];
187
252
  };
253
+ InvitationPreview: {
254
+ organizationName: string;
255
+ role: string;
256
+ email: string;
257
+ expiresAt: string;
258
+ };
259
+ InvitationTokenRequest: {
260
+ token: string;
261
+ };
188
262
  MfaPolicy: "off" | "optional" | "required";
263
+ MyOrganizationInvitation: {
264
+ id: string;
265
+ organizationName: string;
266
+ role: string;
267
+ expiresAt: string;
268
+ };
269
+ MyOrganizationInvitationList: {
270
+ meta: Schemas["PaginationMeta"];
271
+ data: Schemas["MyOrganizationInvitation"][];
272
+ };
189
273
  NewPasswordReason: "expired";
190
274
  NewPasswordRequest: {
191
275
  password: string;
@@ -205,6 +289,64 @@ interface Schemas {
205
289
  provider: Schemas["OAuthProvider"];
206
290
  redirectUrl: string;
207
291
  };
292
+ OrganizationDomain: {
293
+ id: string;
294
+ domain: string;
295
+ mode: Schemas["OrganizationDomainMode"];
296
+ status: Schemas["OrganizationDomainStatus"];
297
+ verifiedAt: string | null;
298
+ expiresAt: string | null;
299
+ codeExpiresAt: string | null;
300
+ createdAt: string;
301
+ updatedAt: string;
302
+ };
303
+ OrganizationDomainList: {
304
+ data: Schemas["OrganizationDomain"][];
305
+ };
306
+ OrganizationDomainMode: "suggest" | "auto_join";
307
+ OrganizationDomainName: string;
308
+ OrganizationDomainStatus: "pending" | "verified" | "needs_reverification";
309
+ OrganizationInvitation: {
310
+ id: string;
311
+ organizationId: string;
312
+ email: string;
313
+ role: string;
314
+ status: "pending" | "accepted" | "declined" | "revoked" | "expired";
315
+ invitedBy: string | null;
316
+ expiresAt: string;
317
+ createdAt: string;
318
+ };
319
+ OrganizationInvitationList: {
320
+ meta: Schemas["PaginationMeta"];
321
+ data: Schemas["OrganizationInvitation"][];
322
+ };
323
+ OrganizationMember: {
324
+ userId: string;
325
+ name: string | null;
326
+ email: string | null;
327
+ role: string;
328
+ createdAt: string;
329
+ };
330
+ OrganizationMemberList: {
331
+ meta: Schemas["PaginationMeta"];
332
+ data: Schemas["OrganizationMember"][];
333
+ };
334
+ OrganizationRoleKey: string;
335
+ OrganizationSlug: string;
336
+ OrganizationSuggestion: {
337
+ id: string;
338
+ name: string;
339
+ slug: string;
340
+ };
341
+ OrganizationSuggestionList: {
342
+ data: Schemas["OrganizationSuggestion"][];
343
+ };
344
+ PaginationMeta: {
345
+ totalCount: number;
346
+ totalPages: number;
347
+ page: number;
348
+ perPage: number;
349
+ };
208
350
  Passkey: {
209
351
  id: string;
210
352
  name: string;
@@ -339,6 +481,26 @@ interface Schemas {
339
481
  RevokedSessions: {
340
482
  revoked: number;
341
483
  };
484
+ SamlConnection: {
485
+ idpEntityId: string;
486
+ ssoUrl: string;
487
+ metadataUrl: string | null;
488
+ certificates: {
489
+ fingerprint: string;
490
+ notAfter: string;
491
+ expired: boolean;
492
+ }[];
493
+ spEntityId: string;
494
+ acsUrl: string;
495
+ spMetadataUrl: string;
496
+ };
497
+ SamlSettingsInput: {
498
+ metadataXml?: string;
499
+ metadataUrl?: string;
500
+ idpEntityId?: string;
501
+ ssoUrl?: string;
502
+ certificates?: string[];
503
+ };
342
504
  SecondFactorMethod: "totp" | "passkey" | "backup_code" | "sms_code";
343
505
  SecondFactorPrepareRequest: {
344
506
  method: Schemas["PreparedSecondFactorMethod"];
@@ -377,6 +539,23 @@ interface Schemas {
377
539
  accessTokenExpiresAt?: string;
378
540
  refreshToken?: string;
379
541
  };
542
+ SetActiveOrganizationRequest: {
543
+ organizationId: string | null;
544
+ };
545
+ SetupLink: {
546
+ id: string;
547
+ organizationId: string;
548
+ name: string;
549
+ /** The start of the token, to tell links apart in a list. */
550
+ prefix: string;
551
+ expiresAt: string;
552
+ revokedAt: string | null;
553
+ lastUsedAt: string | null;
554
+ createdAt: string;
555
+ };
556
+ SetupLinkList: {
557
+ links: Schemas["SetupLink"][];
558
+ };
380
559
  SignInStartRequest: {
381
560
  identifier: string;
382
561
  };
@@ -395,6 +574,75 @@ interface Schemas {
395
574
  SmsFactorConfirmRequest: {
396
575
  code: string;
397
576
  };
577
+ SsoAttributeMapping: {
578
+ idp?: "okta" | "entra" | "google_workspace" | "jumpcloud" | "onelogin" | "ping" | "generic";
579
+ email?: string;
580
+ firstName?: string;
581
+ lastName?: string;
582
+ groups?: string;
583
+ };
584
+ SsoConnection: {
585
+ id: string;
586
+ organizationId: string;
587
+ protocol: "oidc" | "saml";
588
+ name: string;
589
+ status: "draft" | "active" | "disabled";
590
+ issuer: string;
591
+ clientId: string;
592
+ clientSecretSet: boolean;
593
+ saml: Schemas["SamlConnection"] | null;
594
+ redirectUri: string | null;
595
+ attributeMapping: Schemas["SsoAttributeMapping"] | null;
596
+ defaultRole: string;
597
+ enforced: boolean;
598
+ breakGlassRole: string | null;
599
+ trustIdpMfa: boolean;
600
+ domains: string[];
601
+ tested: boolean;
602
+ createdAt: string;
603
+ updatedAt: string;
604
+ };
605
+ SsoConnectionList: {
606
+ connections: Schemas["SsoConnection"][];
607
+ };
608
+ SsoGroupRoles: {
609
+ mappings: {
610
+ group: string;
611
+ role: Schemas["OrganizationRoleKey"];
612
+ }[];
613
+ };
614
+ SsoStartRequest: {
615
+ redirectUrl: string;
616
+ };
617
+ SsoTestResult: {
618
+ testId: string;
619
+ status: "pending" | "passed" | "failed";
620
+ reason: "wrong_audience" | "wrong_issuer" | "wrong_recipient" | "unsigned_assertion" | "signature_invalid" | "signature_algorithm" | "certificate_not_trusted" | "certificate_expired" | "assertion_expired" | "clock_skew" | "response_not_to_this_request" | "replayed" | "idp_refused" | "encrypted_assertion" | "malformed_response" | "discovery_failed" | "token_exchange_failed" | "no_id_token" | "id_token_invalid" | "email_missing" | "email_outside_domains" | null;
621
+ message: string | null;
622
+ attributes: {
623
+ name: string;
624
+ values: string[];
625
+ }[];
626
+ groups: string[];
627
+ mapped: {
628
+ email: string | null;
629
+ givenName: string | null;
630
+ familyName: string | null;
631
+ emailOnVerifiedDomain: boolean;
632
+ } | null;
633
+ };
634
+ SsoTestStart: {
635
+ testId: string;
636
+ authorizationUrl: string;
637
+ };
638
+ SsoTestStartRequest: {
639
+ redirectUrl: string;
640
+ };
641
+ StartOrganizationDomainRequest: {
642
+ domain: Schemas["OrganizationDomainName"];
643
+ email: string;
644
+ mode: Schemas["OrganizationDomainMode"];
645
+ };
398
646
  StepUpEmailCode: {
399
647
  method: "email_code";
400
648
  destination: string;
@@ -426,9 +674,49 @@ interface Schemas {
426
674
  secret: string;
427
675
  uri: string;
428
676
  };
677
+ UpdateOrganizationDomainRequest: {
678
+ mode: Schemas["OrganizationDomainMode"];
679
+ };
680
+ UpdateOrganizationMemberRequest: {
681
+ role: Schemas["OrganizationRoleKey"];
682
+ };
683
+ UpdateOrganizationRequest: {
684
+ name?: string;
685
+ slug?: Schemas["OrganizationSlug"];
686
+ };
687
+ UpdateSsoConnectionRequest: {
688
+ name?: string;
689
+ issuer?: string;
690
+ clientId?: string;
691
+ clientSecret?: string;
692
+ saml?: Schemas["SamlSettingsInput"];
693
+ attributeMapping?: Schemas["SsoAttributeMapping"];
694
+ defaultRole?: Schemas["OrganizationRoleKey"];
695
+ enforced?: boolean;
696
+ breakGlassRole?: Schemas["OrganizationRoleKey"] | null;
697
+ trustIdpMfa?: boolean;
698
+ domains?: Schemas["OrganizationDomainName"][];
699
+ status?: "draft" | "active" | "disabled";
700
+ };
701
+ UserOrganization: {
702
+ id: string;
703
+ name: string;
704
+ slug: string;
705
+ role: string;
706
+ permissions: string[];
707
+ createdAt: string;
708
+ };
709
+ UserOrganizationList: {
710
+ activeOrganizationId: string | null;
711
+ meta: Schemas["PaginationMeta"];
712
+ data: Schemas["UserOrganization"][];
713
+ };
429
714
  VerifyEmailRequest: {
430
715
  code: string;
431
716
  };
717
+ VerifyOrganizationDomainRequest: {
718
+ code: string;
719
+ };
432
720
  }
433
721
  /**
434
722
  * The next step of a sign-up, sign-in or password reset, decided by the server and
@@ -528,7 +816,11 @@ type ClientKind = Schemas["SessionClient"];
528
816
  * - `loading`: not known yet. Call `load()` (or `session.getToken()`) to find out.
529
817
  * - `signed-out`: nobody is signed in.
530
818
  * - `signed-in`: a session exists. `user` is `null` only until it has been fetched, or if
531
- * fetching it failed; `user.get()` fetches it again.
819
+ * fetching it failed; `user.get()` fetches it again. `organizationId` is the organization
820
+ * the session acts in, `null` for the personal account. For a session with an
821
+ * access token it is read from the token's `org_id` claim (unverified: for showing, never
822
+ * for authorizing) and so follows every refresh; a browser's `stateful` session has no
823
+ * token, so it is known after `organizations.list()` or `organizations.setActive()`.
532
824
  *
533
825
  * @example
534
826
  * ```ts
@@ -547,6 +839,7 @@ type AuthState = {
547
839
  readonly status: "signed-in";
548
840
  readonly sessionId: string;
549
841
  readonly user: User | null;
842
+ readonly organizationId: string | null;
550
843
  };
551
844
  /**
552
845
  * A `fetch` the client sends every request through. The global `fetch` fits; so does a
@@ -700,6 +993,106 @@ type EmailCodeSent2 = Schemas["EmailCodeSent"];
700
993
  */
701
994
  type StepUpMethod = StepUpProof["method"];
702
995
  /**
996
+ * An organization the signed-in user belongs to, with their role in it and its permission
997
+ * keys.
998
+ *
999
+ * @example
1000
+ * ```ts
1001
+ * const { data } = await tula.organizations.list()
1002
+ * const admin = data.find((org) => org.role === 'admin')
1003
+ * ```
1004
+ */
1005
+ type UserOrganization2 = Schemas["UserOrganization"];
1006
+ /** A page of the user's organizations, with the session's active organization's id. */
1007
+ type UserOrganizationList2 = Schemas["UserOrganizationList"];
1008
+ /** An organization the user may join because of a verified address (`organizations.suggestions()`). */
1009
+ type OrganizationSuggestion2 = Schemas["OrganizationSuggestion"];
1010
+ /** What `organizations.suggestions()` answers: the organizations the user is offered. */
1011
+ type OrganizationSuggestionList2 = Schemas["OrganizationSuggestionList"];
1012
+ /**
1013
+ * An invitation to an organization as the inviting side sees it. The token the emailed link
1014
+ * carries is never part of it.
1015
+ *
1016
+ * @example
1017
+ * ```ts
1018
+ * const { data } = await tula.organizations.invitations.list(org.id)
1019
+ * const open = data.filter((invitation) => invitation.status === 'pending')
1020
+ * ```
1021
+ */
1022
+ type OrganizationInvitation2 = Schemas["OrganizationInvitation"];
1023
+ /** A page of an organization's invitations. */
1024
+ type OrganizationInvitationList2 = Schemas["OrganizationInvitationList"];
1025
+ /**
1026
+ * An invitation addressed to the signed-in user, as `organizations.invitations.mine()` lists it:
1027
+ * its `id`, the organization's name, the role's key and the expiry; never a token.
1028
+ *
1029
+ * @example
1030
+ * ```ts
1031
+ * const { data } = await tula.organizations.invitations.mine()
1032
+ * await tula.organizations.invitations.acceptMine(data[0].id)
1033
+ * ```
1034
+ */
1035
+ type MyOrganizationInvitation2 = Schemas["MyOrganizationInvitation"];
1036
+ /** A page of the invitations addressed to the signed-in user. */
1037
+ type MyOrganizationInvitationList2 = Schemas["MyOrganizationInvitationList"];
1038
+ /** The body of `organizations.invitations.invite()`: the address, a role's key and the page the link leads to. */
1039
+ type CreateOrganizationInvitationInput = Schemas["CreateOrganizationInvitationRequest"];
1040
+ /** What the holder of an invitation's token may see before signing in. */
1041
+ type InvitationPreview2 = Schemas["InvitationPreview"];
1042
+ /** What accepting an invitation answers: the organization joined, with the role and its permissions. */
1043
+ type AcceptedInvitation2 = Schemas["AcceptedInvitation"];
1044
+ /**
1045
+ * A member of an organization as another member sees them: the user's id, name and address
1046
+ * (each `null` when they have none) and the key of their role.
1047
+ *
1048
+ * @example
1049
+ * ```ts
1050
+ * const { data } = await tula.organizations.members.list(org.id)
1051
+ * const admins = data.filter((member) => member.role === 'admin')
1052
+ * ```
1053
+ */
1054
+ type OrganizationMember2 = Schemas["OrganizationMember"];
1055
+ /** A page of an organization's members. */
1056
+ type OrganizationMemberList2 = Schemas["OrganizationMemberList"];
1057
+ /** A role the signed-in member may give: its key and its name. */
1058
+ type AssignableRole2 = Schemas["AssignableRole"];
1059
+ /** What `organizations.roles()` answers: the roles the member may offer. */
1060
+ type AssignableRoleList2 = Schemas["AssignableRoleList"];
1061
+ /** The body of `organizations.update()`: a new name, a new slug, or both. */
1062
+ type UpdateOrganizationInput = Schemas["UpdateOrganizationRequest"];
1063
+ /** A domain an organization claimed, pending a code or verified. */
1064
+ type OrganizationDomain2 = Schemas["OrganizationDomain"];
1065
+ /** An organization's domains. */
1066
+ type OrganizationDomainList2 = Schemas["OrganizationDomainList"];
1067
+ /** The body of `organizations.domains.start()`: the domain, an address on it and the mode. */
1068
+ type StartOrganizationDomainInput = Schemas["StartOrganizationDomainRequest"];
1069
+ /** An SSO connection of an organization, as its admins see it: never with the client secret. */
1070
+ type OrganizationSsoConnection = Schemas["SsoConnection"];
1071
+ /** An organization's SSO connections. */
1072
+ type OrganizationSsoConnectionList = Schemas["SsoConnectionList"];
1073
+ /** The body of `organizations.sso.create()`: the organization is the one of the call. */
1074
+ type CreateOrganizationSsoConnectionInput = Schemas["CreateMySsoConnectionRequest"];
1075
+ /** The fields `organizations.sso.update()` changes; an absent one is kept. */
1076
+ type UpdateOrganizationSsoConnectionInput = Schemas["UpdateSsoConnectionRequest"];
1077
+ /** An organization's IdP group to role mapping, in precedence order. */
1078
+ type OrganizationSsoGroupRoles = Schemas["SsoGroupRoles"];
1079
+ /** A started test sign-in: where to send the browser, and the id it returns with. */
1080
+ type OrganizationSsoTestStart = Schemas["SsoTestStart"];
1081
+ /** A test sign-in's result. */
1082
+ type OrganizationSsoTestResult = Schemas["SsoTestResult"];
1083
+ /** An SSO setup link of an organization, as listed (never its token). */
1084
+ type OrganizationSetupLink = Schemas["SetupLink"];
1085
+ /** The setup links of an organization. */
1086
+ type OrganizationSetupLinkList = Schemas["SetupLinkList"];
1087
+ /** A new setup link: the `token` and `url` are shown once. */
1088
+ type CreatedOrganizationSetupLink = Schemas["CreatedSetupLink"];
1089
+ /** The body of `organizations.sso.createSetupLink()`. */
1090
+ type CreateOrganizationSetupLinkInput = Schemas["CreateMySetupLinkRequest"];
1091
+ /** What `organizations.setActive()` answers: the session's active organization now. */
1092
+ type ActiveOrganization2 = Schemas["ActiveOrganization"];
1093
+ /** The body of `organizations.create()`: a name and a slug (lower-case letters, digits, hyphens). */
1094
+ type CreateOrganizationInput = Schemas["CreateOrganizationRequest"];
1095
+ /**
703
1096
  * Codes for failures that happen in the client, before or instead of an API answer. They sit
704
1097
  * next to the contract's codes so that callers have one thing to catch and one field to switch
705
1098
  * on. `status` is `0` for all of them.
@@ -844,7 +1237,8 @@ interface TulaErrorInit {
844
1237
  *
845
1238
  * `code` is a contract code (`auth.invalid_credentials`, `password.too_short`, `rate_limited`,
846
1239
  * …) or one of the client's own (`network.failed`, `network.timeout`, `response.invalid`,
847
- * `storage.failed`, `flow.busy`). An error the client raised itself has `status: 0`. `message` is ready to show, taken from the client's locale table. An error
1240
+ * `storage.failed`, `flow.busy`). An error the client raised itself has `status: 0`. `message` is
1241
+ * ready to show, taken from the client's locale table. An error
848
1242
  * never contains a token, an attempt's secret or a password.
849
1243
  *
850
1244
  * @example
@@ -1026,7 +1420,7 @@ import { DeviceKey } from "@tula-tech/contract/device-binding";
1026
1420
  /**
1027
1421
  * Where a client's device key comes from, for a runtime that cannot hand one over when the
1028
1422
  * client is created: a key in a phone's secure hardware is found or made by a native call,
1029
- * which takes time and can fail (ADR 0052). Asked before every request a key proves.
1423
+ * which takes time and can fail. Asked before every request a key proves.
1030
1424
  *
1031
1425
  * @param operation - The request about to be sent: the operation id of one of the starts,
1032
1426
  * or `refreshSession`.
@@ -1127,7 +1521,7 @@ interface Environment {
1127
1521
  linkStorage: LinkStorageLike | undefined;
1128
1522
  /**
1129
1523
  * Storage one tab keeps across a navigation and no other tab can read (`sessionStorage`).
1130
- * Holds the binding of an OAuth round trip while the tab is at the provider (ADR 0026): not a
1524
+ * Holds the binding of an OAuth round trip while the tab is at the provider: not a
1131
1525
  * token, and not an attempt's secret.
1132
1526
  */
1133
1527
  tabStorage: LinkStorageLike | undefined;
@@ -1135,7 +1529,7 @@ interface Environment {
1135
1529
  page: PageLike | undefined;
1136
1530
  /**
1137
1531
  * Where `navigator.credentials` and `PublicKeyCredential` are looked up when a passkey is
1138
- * asked for (ADR 0027). Read lazily: a runtime without WebAuthn simply has neither.
1532
+ * asked for. Read lazily: a runtime without WebAuthn simply has neither.
1139
1533
  */
1140
1534
  passkeys: PasskeyGlobals | undefined;
1141
1535
  /**
@@ -1145,7 +1539,7 @@ interface Environment {
1145
1539
  passkeyProvider?: PasskeyProvider;
1146
1540
  /**
1147
1541
  * Where the client's device key comes from, for a runtime that finds or makes it by a call
1148
- * that takes time (a key in a phone's secure hardware; ADR 0052). Asked before every
1542
+ * that takes time (a key in a phone's secure hardware). Asked before every
1149
1543
  * request a key proves, unless the client was given a `deviceKey` of its own. Never for a
1150
1544
  * `web` client.
1151
1545
  */
@@ -1196,8 +1590,9 @@ declare function runtimeEnvironment(globals?: RuntimeGlobals): Environment;
1196
1590
  * The `web` client kind never uses it: there the refresh token lives in an httpOnly cookie that
1197
1591
  * JavaScript cannot read. For `ios`, `android` and `server` the token is a long-lived
1198
1592
  * credential, so back this with the platform's secure store (Keychain, Keystore, an encrypted
1199
- * file), never with `localStorage` or a plain file. A React Native secure-store adapter ships in
1200
- * Phase 2.
1593
+ * file), never with `localStorage` or a plain file: script can read those, so any script on
1594
+ * the page (an XSS, a compromised dependency) could take the refresh token. The default
1595
+ * {@link memoryStorage} is not readable that way.
1201
1596
  *
1202
1597
  * @example
1203
1598
  * ```ts
@@ -1343,13 +1738,78 @@ interface EmailLinkOutcome {
1343
1738
  /** What happened. */
1344
1739
  readonly status: "none" | "signed_in" | "verified" | "different_browser" | "expired";
1345
1740
  }
1741
+ /** A provider `signIn.withOAuth` can be asked for. */
1742
+ type OAuthProvider2 = Schemas["OAuthProvider"];
1743
+ /** A provider account connected to the signed-in user. */
1744
+ type Identity2 = Schemas["Identity"];
1745
+ /**
1746
+ * Whether a failure thrown by `signIn.handleOAuthCallback()` left the round trip in place, so
1747
+ * that calling it again retries the exchange: the request got no answer (`network.failed`,
1748
+ * `network.timeout`) or was rate limited.
1749
+ *
1750
+ * @param error - What `handleOAuthCallback()` threw.
1751
+ * @returns `true` when calling it again can still succeed.
1752
+ *
1753
+ * @example
1754
+ * ```ts
1755
+ * try {
1756
+ * await tula.signIn.handleOAuthCallback()
1757
+ * } catch (error) {
1758
+ * if (isRetryableOAuthError(error)) showTryAgain()
1759
+ * }
1760
+ * ```
1761
+ */
1762
+ declare function isRetryableOAuthError(error: unknown): boolean;
1763
+ /**
1764
+ * What became of an OAuth round trip, as `signIn.handleOAuthCallback()` reports it.
1765
+ *
1766
+ * - `none`: the address holds no OAuth answer. Nothing was sent.
1767
+ * - `complete`: signed in. `flow.step.status` is `complete`.
1768
+ * - `needs_step`: the provider was accepted as a first factor and something still stands before
1769
+ * the session (`needs_second_factor`, `needs_factor_enrolment`). `flow` is positioned on that
1770
+ * step: `flow.submitSecondFactor(…)` and the enrolment actions work as for any sign-in.
1771
+ * - `linked`: a provider account was connected to the signed-in user (a link started with
1772
+ * `user.identities.link`).
1773
+ * - `different_browser`: this browser did not start the sign-in (or no longer has what it kept).
1774
+ * Nothing was completed. Start again here.
1775
+ * - `error`: it did not go through. `code` is a contract error code: `oauth.access_denied` (the
1776
+ * user cancelled), `oauth.account_exists` (sign in the usual way, then connect the provider
1777
+ * from the profile), `oauth.email_unverified`, `oauth.ticket_invalid`, …; `message` is its text.
1778
+ *
1779
+ * @example
1780
+ * ```ts
1781
+ * const outcome: OAuthCallbackOutcome = await tula.signIn.handleOAuthCallback()
1782
+ * if (outcome.status === 'needs_step') {
1783
+ * await outcome.flow.submitSecondFactor({ method: 'totp', code })
1784
+ * }
1785
+ * ```
1786
+ */
1787
+ type OAuthCallbackOutcome = {
1788
+ readonly status: "none";
1789
+ } | {
1790
+ readonly status: "complete";
1791
+ readonly flow: SignInFlow;
1792
+ } | {
1793
+ readonly status: "needs_step";
1794
+ readonly flow: SignInFlow;
1795
+ } | {
1796
+ readonly status: "linked";
1797
+ readonly identity: Identity2;
1798
+ } | {
1799
+ readonly status: "different_browser";
1800
+ } | {
1801
+ readonly status: "error";
1802
+ readonly code: string;
1803
+ readonly message: string;
1804
+ };
1346
1805
  /**
1347
1806
  * What a flow looks like when it is logged or serialised: where it stands, and nothing that
1348
1807
  * could continue it. The attempt's secret is never part of it.
1349
1808
  *
1350
1809
  * @example
1351
1810
  * ```ts
1352
- * JSON.stringify(flow) // {"id":"…","kind":"sign_in","step":{"status":"needs_password"},"expiresAt":"…"}
1811
+ * JSON.stringify(flow) //
1812
+ * {"id":"…","kind":"sign_in","step":{"status":"needs_password"},"expiresAt":"…"}
1353
1813
  * ```
1354
1814
  */
1355
1815
  interface FlowSnapshot<Kind extends FlowKind2 = FlowKind2> {
@@ -1367,7 +1827,8 @@ interface FlowSnapshot<Kind extends FlowKind2 = FlowKind2> {
1367
1827
  *
1368
1828
  * @example
1369
1829
  * ```ts
1370
- * const { step, backupCodesRemaining } = await flow.submitSecondFactor({ method: 'backup_code', code })
1830
+ * const { step, backupCodesRemaining } = await flow.submitSecondFactor({ method: 'backup_code', code
1831
+ * })
1371
1832
  * if (backupCodesRemaining !== undefined && backupCodesRemaining < 3) {
1372
1833
  * suggestNewBackupCodes()
1373
1834
  * }
@@ -1641,7 +2102,8 @@ interface SignInFlow extends Flow<"sign_in">, FactorEnrolmentActions, SecondFact
1641
2102
  * With `email_link` the email also carries a link to `redirectUrl`, which must be one of the
1642
2103
  * environment's allowed redirect URLs, exactly, **and on the same origin as the page that
1643
2104
  * asks** (scheme, host and port): what ties the link to this browser is kept in this origin's
1644
- * storage, and a page on another origin could not read it. The link works only in this browser: the
2105
+ * storage, and a page on another origin could not read it. The link works only in this browser:
2106
+ * the
1645
2107
  * flow keeps what ties the two together (in `localStorage`; it is not a token and authorizes
1646
2108
  * nothing by itself). Follow it with {@link SignInFlow.waitForEmailLink}.
1647
2109
  *
@@ -1701,6 +2163,28 @@ interface SignInFlow extends Flow<"sign_in">, FactorEnrolmentActions, SecondFact
1701
2163
  waitForEmailLink(options?: {
1702
2164
  signal?: AbortSignal;
1703
2165
  }): Promise<FlowStep2>;
2166
+ /**
2167
+ * Continue with single sign-on (step `needs_first_factor` offering `sso`: the address's
2168
+ * domain is bound to an active connection). Keeps the round trip's binding for this tab and
2169
+ * navigates to the identity provider, unless `navigate` is `false`. The provider returns to
2170
+ * `redirectUrl`; call `signIn.handleOAuthCallback()` on that page, as after a provider.
2171
+ *
2172
+ * @param input - The page to come back to (one of the allowed redirect URLs, same origin as
2173
+ * this page), and `navigate: false` to navigate yourself.
2174
+ * @returns The identity provider's URL.
2175
+ * @throws TulaError `request.redirect_not_allowed`, `sso.*` codes, `link.cross_origin` and
2176
+ * `storage.failed` (raised by the client, no request sent).
2177
+ * @example
2178
+ * ```ts
2179
+ * await flow.startSso({ redirectUrl: `${location.origin}/oauth/callback` })
2180
+ * ```
2181
+ */
2182
+ startSso(input: {
2183
+ redirectUrl: string;
2184
+ navigate?: boolean;
2185
+ }): Promise<{
2186
+ url: string;
2187
+ }>;
1704
2188
  }
1705
2189
  /**
1706
2190
  * A password reset in progress. The emailed code and the new password travel together, so a
@@ -1773,88 +2257,24 @@ interface IdTokenName {
1773
2257
  /** The family name, at most 100 characters. */
1774
2258
  familyName?: string;
1775
2259
  }
1776
- /** A provider `signIn.withOAuth` can be asked for. */
1777
- type OAuthProvider2 = Schemas["OAuthProvider"];
1778
- /** A provider account connected to the signed-in user. */
1779
- type Identity2 = Schemas["Identity"];
1780
2260
  /**
1781
- * Whether a failure thrown by `signIn.handleOAuthCallback()` left the round trip in place, so
1782
- * that calling it again retries the exchange: the request got no answer (`network.failed`,
1783
- * `network.timeout`) or was rate limited.
1784
- *
1785
- * @param error - What `handleOAuthCallback()` threw.
1786
- * @returns `true` when calling it again can still succeed.
2261
+ * How long one request may take before it fails with `network.timeout`, unless the client is
2262
+ * created with its own `timeoutMs`.
1787
2263
  *
1788
2264
  * @example
1789
2265
  * ```ts
1790
- * try {
1791
- * await tula.signIn.handleOAuthCallback()
1792
- * } catch (error) {
1793
- * if (isRetryableOAuthError(error)) showTryAgain()
1794
- * }
2266
+ * createTulaClient({ publishableKey, baseUrl, timeoutMs: DEFAULT_TIMEOUT_MS * 2 })
1795
2267
  * ```
1796
2268
  */
1797
- declare function isRetryableOAuthError(error: unknown): boolean;
2269
+ declare const DEFAULT_TIMEOUT_MS = 15e3;
1798
2270
  /**
1799
- * What became of an OAuth round trip, as `signIn.handleOAuthCallback()` reports it.
1800
- *
1801
- * - `none`: the address holds no OAuth answer. Nothing was sent.
1802
- * - `complete`: signed in. `flow.step.status` is `complete`.
1803
- * - `needs_step`: the provider was accepted as a first factor and something still stands before
1804
- * the session (`needs_second_factor`, `needs_factor_enrolment`). `flow` is positioned on that
1805
- * step: `flow.submitSecondFactor(…)` and the enrolment actions work as for any sign-in.
1806
- * - `linked`: a provider account was connected to the signed-in user (a link started with
1807
- * `user.identities.link`).
1808
- * - `different_browser`: this browser did not start the sign-in (or no longer has what it kept).
1809
- * Nothing was completed. Start again here.
1810
- * - `error`: it did not go through. `code` is a contract error code: `oauth.access_denied` (the
1811
- * user cancelled), `oauth.account_exists` (sign in the usual way, then connect the provider
1812
- * from the profile), `oauth.email_unverified`, `oauth.ticket_invalid`, …; `message` is its text.
2271
+ * Options of {@link createTulaClient}.
1813
2272
  *
1814
2273
  * @example
1815
2274
  * ```ts
1816
- * const outcome: OAuthCallbackOutcome = await tula.signIn.handleOAuthCallback()
1817
- * if (outcome.status === 'needs_step') {
1818
- * await outcome.flow.submitSecondFactor({ method: 'totp', code })
1819
- * }
1820
- * ```
1821
- */
1822
- type OAuthCallbackOutcome = {
1823
- readonly status: "none";
1824
- } | {
1825
- readonly status: "complete";
1826
- readonly flow: SignInFlow;
1827
- } | {
1828
- readonly status: "needs_step";
1829
- readonly flow: SignInFlow;
1830
- } | {
1831
- readonly status: "linked";
1832
- readonly identity: Identity2;
1833
- } | {
1834
- readonly status: "different_browser";
1835
- } | {
1836
- readonly status: "error";
1837
- readonly code: string;
1838
- readonly message: string;
1839
- };
1840
- /**
1841
- * How long one request may take before it fails with `network.timeout`, unless the client is
1842
- * created with its own `timeoutMs`.
1843
- *
1844
- * @example
1845
- * ```ts
1846
- * createTulaClient({ publishableKey, baseUrl, timeoutMs: DEFAULT_TIMEOUT_MS * 2 })
1847
- * ```
1848
- */
1849
- declare const DEFAULT_TIMEOUT_MS = 15e3;
1850
- /**
1851
- * Options of {@link createTulaClient}.
1852
- *
1853
- * @example
1854
- * ```ts
1855
- * const options: TulaClientOptions = {
1856
- * publishableKey: 'tula_pk_dev_…',
1857
- * baseUrl: 'https://auth.example.com',
2275
+ * const options: TulaClientOptions = {
2276
+ * publishableKey: 'tula_pk_dev_…',
2277
+ * baseUrl: 'https://auth.example.com',
1858
2278
  * }
1859
2279
  * ```
1860
2280
  */
@@ -1864,6 +2284,12 @@ interface TulaClientOptions {
1864
2284
  /** Where the Tula API is served, e.g. `https://auth.example.com`. */
1865
2285
  baseUrl: string;
1866
2286
  /**
2287
+ * Allow a plain `http:` `baseUrl` to a host that is not this machine. Off by default: the
2288
+ * publishable key and the bearer tokens would cross the network readable. Only for a private
2289
+ * network you trust.
2290
+ */
2291
+ allowInsecureHttp?: boolean;
2292
+ /**
1867
2293
  * The kind of client, which decides where the refresh token lives. `web` (the default in a
1868
2294
  * browser): an httpOnly cookie set by the API, which this SDK never sees. `ios`, `android`,
1869
2295
  * `server` (the default elsewhere): the `storage` adapter.
@@ -1875,7 +2301,7 @@ interface TulaClientOptions {
1875
2301
  */
1876
2302
  storage?: TokenStorage;
1877
2303
  /**
1878
- * The session profile to ask for, by name (ADR 0028). The session gets it only where the
2304
+ * The session profile to ask for, by name. The session gets it only where the
1879
2305
  * environment marks that profile `clientSelectable`; any other name is ignored by the API
1880
2306
  * and the session gets the profile of its client kind (`web`, or `mobile` for the others).
1881
2307
  * Leave it out to take that default. A name is lowercase letters, digits and single hyphens,
@@ -1888,7 +2314,7 @@ interface TulaClientOptions {
1888
2314
  */
1889
2315
  sessionProfile?: string;
1890
2316
  /**
1891
- * A key to bind this client's sessions to (ADR 0043). With one, every sign-in, sign-up and
2317
+ * A key to bind this client's sessions to. With one, every sign-in, sign-up and
1892
2318
  * password reset this client starts asks for a **device-bound** session, and every refresh
1893
2319
  * proves the key: a refresh token copied off the device is then of no use without it. The
1894
2320
  * proofs (`DPoP` headers) and the server's nonce are handled by the client.
@@ -1937,7 +2363,8 @@ interface TulaClient {
1937
2363
  readonly state: AuthState;
1938
2364
  /**
1939
2365
  * What a server rendered for this client, when a framework integration knows it
1940
- * (`@tula-tech/nextjs` sets it from the request's session). `@tula-tech/react` uses it as the state
2366
+ * (`@tula-tech/nextjs` sets it from the request's session). `@tula-tech/react` uses it as the
2367
+ * state
1941
2368
  * during server rendering and hydration instead of `loading`. `createTulaClient` never sets
1942
2369
  * it.
1943
2370
  */
@@ -2124,7 +2551,8 @@ interface TulaClient {
2124
2551
  provider: IdTokenProvider2;
2125
2552
  }): Promise<IdTokenSignIn>;
2126
2553
  /**
2127
- * "Continue with Google, GitHub, Apple, Microsoft, Discord, LinkedIn, X or Facebook": a sign-in that creates the account when the
2554
+ * "Continue with Google, GitHub, Apple, Microsoft, Discord, LinkedIn, X or Facebook": a sign-in
2555
+ * that creates the account when the
2128
2556
  * provider's verified address has none.
2129
2557
  *
2130
2558
  * Asks the API for the provider's URL, keeps the round trip's binding for this tab
@@ -2212,7 +2640,7 @@ interface TulaClient {
2212
2640
  * An access token to send to your own backend, refreshed first when it is missing or about
2213
2641
  * to expire. Any number of concurrent calls share one refresh.
2214
2642
  *
2215
- * **`null` while signed in on a `stateful` session profile** (ADR 0028). Such a session is
2643
+ * **`null` while signed in on a `stateful` session profile**. Such a session is
2216
2644
  * an httpOnly cookie the browser holds and this client never sees: there is no token, so
2217
2645
  * there is nothing to return and nothing is asked of the API. Use `state.status` to know
2218
2646
  * whether the user is signed in; a backend on the API's own host receives the cookie and
@@ -2337,6 +2765,591 @@ interface TulaClient {
2337
2765
  method: "email_code" | "sms_code";
2338
2766
  }): Promise<StepUpPrepared>;
2339
2767
  };
2768
+ /**
2769
+ * The signed-in user's organizations and the one the session acts in. While the
2770
+ * environment's `organizations.enabled` setting is off, each call answers
2771
+ * `organization.disabled`.
2772
+ */
2773
+ readonly organizations: {
2774
+ /**
2775
+ * The organizations the user belongs to, with their role and its permission keys in each,
2776
+ * and the session's active organization, one page at a time (the first, 20 of them, without
2777
+ * a query). `meta.totalPages` says whether there is a next page.
2778
+ *
2779
+ * @param query - The page to read (1-based, default 1) and its `size` (1 to 100, default 20).
2780
+ * @returns The page; `activeOrganizationId` is `null` for the personal account.
2781
+ * @throws TulaError `auth.unauthenticated` when signed out, `organization.disabled`.
2782
+ * @example
2783
+ * ```ts
2784
+ * const { data, meta, activeOrganizationId } = await tula.organizations.list({ page: 2 })
2785
+ * ```
2786
+ */
2787
+ list(query?: {
2788
+ page?: number;
2789
+ size?: number;
2790
+ }): Promise<UserOrganizationList2>;
2791
+ /**
2792
+ * Create an organization the user owns (it holds the environment's `creatorRole`). The
2793
+ * session does not switch to it: call `setActive`.
2794
+ *
2795
+ * @param input - The name and the slug.
2796
+ * @returns The organization.
2797
+ * @throws TulaError `organization.creation_disabled`, `organization.limit_reached`,
2798
+ * `organization.slug_taken`.
2799
+ * @example
2800
+ * ```ts
2801
+ * const org = await tula.organizations.create({ name: 'Acme', slug: 'acme' })
2802
+ * await tula.organizations.setActive(org.id)
2803
+ * ```
2804
+ */
2805
+ create(input: CreateOrganizationInput): Promise<UserOrganization2>;
2806
+ /**
2807
+ * Make an organization the one the session acts in (or `null`, the personal account). The
2808
+ * access token is replaced with one that carries `org_id`, `org_role` and `org_perms`;
2809
+ * `state.organizationId` follows.
2810
+ *
2811
+ * @param organizationId - The organization, or `null`.
2812
+ * @returns The API's answer.
2813
+ * @throws TulaError `organization.not_a_member`, `organization.personal_account_disabled`.
2814
+ * @example
2815
+ * ```ts
2816
+ * await tula.organizations.setActive(org.id)
2817
+ * ```
2818
+ */
2819
+ setActive(organizationId: string | null): Promise<ActiveOrganization2>;
2820
+ /**
2821
+ * The organizations the user may join because their **verified** email address is on a
2822
+ * domain the organization verified: its id, name and slug,
2823
+ * nothing of its members. Empty for a user with no verified address, for one already in
2824
+ * the organization and for a domain that is not exactly the address's.
2825
+ *
2826
+ * @returns The suggestions.
2827
+ * @throws TulaError `auth.unauthenticated` when signed out, `organization.disabled`.
2828
+ * @example
2829
+ * ```ts
2830
+ * const { data } = await tula.organizations.suggestions()
2831
+ * ```
2832
+ */
2833
+ suggestions(): Promise<OrganizationSuggestionList2>;
2834
+ /**
2835
+ * Join a suggested organization with the environment's default role. The server decides
2836
+ * again, so a suggestion that is gone (the domain was removed, the address changed) is
2837
+ * refused. The session does not switch to it: call `setActive`.
2838
+ *
2839
+ * @param organizationId - The organization, from {@link suggestions}.
2840
+ * @returns The organization, with the user's role and its permission keys.
2841
+ * @throws TulaError `organization.suggestion_unavailable`, `organization.member_exists`,
2842
+ * `organization.disabled`.
2843
+ * @example
2844
+ * ```ts
2845
+ * const [first] = (await tula.organizations.suggestions()).data
2846
+ * if (first) {
2847
+ * await tula.organizations.join(first.id)
2848
+ * }
2849
+ * ```
2850
+ */
2851
+ join(organizationId: string): Promise<UserOrganization2>;
2852
+ /**
2853
+ * Rename an organization, or change its slug. Needs `org:sys:organization:manage`, which
2854
+ * the API reads from the user's membership on every call.
2855
+ *
2856
+ * @param organizationId - The organization.
2857
+ * @param input - A new `name`, a new `slug`, or both.
2858
+ * @returns The organization with the user's role, as it is now.
2859
+ * @throws TulaError `organization.not_a_member`, `organization.permission_required`,
2860
+ * `organization.slug_taken`, `organization.disabled`.
2861
+ * @example
2862
+ * ```ts
2863
+ * await tula.organizations.update(org.id, { name: 'Acme Inc' })
2864
+ * ```
2865
+ */
2866
+ update(organizationId: string, input: UpdateOrganizationInput): Promise<UserOrganization2>;
2867
+ /**
2868
+ * Delete an organization for good, with its memberships, invitations and domains. Needs
2869
+ * `org:sys:organization:delete`. Members' sessions leave it at their next refresh; if it
2870
+ * was this session's active organization, call `setActive` for another one.
2871
+ *
2872
+ * @param organizationId - The organization.
2873
+ * @throws TulaError `organization.not_a_member`, `organization.permission_required`,
2874
+ * `organization.disabled`.
2875
+ * @example
2876
+ * ```ts
2877
+ * await tula.organizations.remove(org.id)
2878
+ * await tula.organizations.setActive(null)
2879
+ * ```
2880
+ */
2881
+ remove(organizationId: string): Promise<void>;
2882
+ /**
2883
+ * Leave an organization: remove the signed-in user's own membership, which needs no
2884
+ * permission. The last member who can manage members cannot leave.
2885
+ *
2886
+ * @param organizationId - The organization.
2887
+ * @throws TulaError `organization.not_a_member`, `organization.last_manager`,
2888
+ * `organization.disabled`.
2889
+ * @example
2890
+ * ```ts
2891
+ * await tula.organizations.leave(org.id)
2892
+ * ```
2893
+ */
2894
+ leave(organizationId: string): Promise<void>;
2895
+ /**
2896
+ * The roles the user may give to an invitation or a member: those whose permissions they
2897
+ * hold themselves. Needs `org:sys:invitations:manage` or `org:sys:memberships:manage`.
2898
+ *
2899
+ * @param organizationId - The organization.
2900
+ * @returns The roles' keys and names.
2901
+ * @throws TulaError `organization.not_a_member`, `organization.permission_required`,
2902
+ * `organization.disabled`.
2903
+ * @example
2904
+ * ```ts
2905
+ * const { data } = await tula.organizations.roles(org.id)
2906
+ * ```
2907
+ */
2908
+ roles(organizationId: string): Promise<AssignableRoleList2>;
2909
+ /**
2910
+ * An organization's members. Reading needs `org:sys:memberships:read` or `:manage`;
2911
+ * changing needs `org:sys:memberships:manage`, which the API reads from the user's
2912
+ * membership on every call.
2913
+ */
2914
+ readonly members: {
2915
+ /**
2916
+ * The organization's members, oldest first, with each one's name, address and role key.
2917
+ * Only the first page: the client sends no paging query yet.
2918
+ *
2919
+ * @param organizationId - The organization.
2920
+ * @returns The page.
2921
+ * @throws TulaError `organization.not_a_member`, `organization.permission_required`,
2922
+ * `organization.disabled`.
2923
+ * @example
2924
+ * ```ts
2925
+ * const { data } = await tula.organizations.members.list(org.id)
2926
+ * ```
2927
+ */
2928
+ list(organizationId: string): Promise<OrganizationMemberList2>;
2929
+ /**
2930
+ * Give a member another role, one whose permissions the user holds themselves. The
2931
+ * member's sessions carry it from their next refresh.
2932
+ *
2933
+ * @param organizationId - The organization.
2934
+ * @param userId - The member.
2935
+ * @param role - The key of the new role.
2936
+ * @returns The member, as they are now.
2937
+ * @throws TulaError `organization.permission_required`, `organization.role_unknown`,
2938
+ * `organization.role_not_assignable`, `organization.last_manager`.
2939
+ * @example
2940
+ * ```ts
2941
+ * await tula.organizations.members.changeRole(org.id, member.userId, 'admin')
2942
+ * ```
2943
+ */
2944
+ changeRole(organizationId: string, userId: string, role: string): Promise<OrganizationMember2>;
2945
+ /**
2946
+ * Remove a member. The user keeps their account; to remove yourself use `leave`.
2947
+ *
2948
+ * @param organizationId - The organization.
2949
+ * @param userId - The member.
2950
+ * @throws TulaError `organization.permission_required`, `organization.last_manager`.
2951
+ * @example
2952
+ * ```ts
2953
+ * await tula.organizations.members.remove(org.id, member.userId)
2954
+ * ```
2955
+ */
2956
+ remove(organizationId: string, userId: string): Promise<void>;
2957
+ };
2958
+ /**
2959
+ * An organization's domains: the organization claims one by
2960
+ * a code emailed to an address on it, and a verified one suggests or joins the people who
2961
+ * sign up with a verified address on it. Needs `org:sys:domains:manage`, which the API
2962
+ * reads from the user's membership on every call.
2963
+ */
2964
+ readonly domains: {
2965
+ /**
2966
+ * The organization's domains, oldest first: claims waiting for a code and verified ones.
2967
+ *
2968
+ * @param organizationId - The organization.
2969
+ * @returns The domains.
2970
+ * @throws TulaError `organization.not_a_member`, `auth.forbidden`, `organization.disabled`.
2971
+ * @example
2972
+ * ```ts
2973
+ * const { data } = await tula.organizations.domains.list(org.id)
2974
+ * ```
2975
+ */
2976
+ list(organizationId: string): Promise<OrganizationDomainList2>;
2977
+ /**
2978
+ * Claim a domain: email a code to an address on it. Sent again for a pending claim it
2979
+ * emails a new code.
2980
+ *
2981
+ * @param organizationId - The organization.
2982
+ * @param input - The domain, an address on it and what a verified domain does.
2983
+ * @returns The pending claim.
2984
+ * @throws TulaError `organization.domain_public`, `organization.domain_taken`,
2985
+ * `organization.domain_address_mismatch`, `organization.domain_limit`, `rate_limited`.
2986
+ * @example
2987
+ * ```ts
2988
+ * await tula.organizations.domains.start(org.id, {
2989
+ * domain: 'acme.com',
2990
+ * email: 'ada@acme.com',
2991
+ * mode: 'suggest',
2992
+ * })
2993
+ * ```
2994
+ */
2995
+ start(organizationId: string, input: StartOrganizationDomainInput): Promise<OrganizationDomain2>;
2996
+ /**
2997
+ * Enter the emailed code. Five wrong guesses use the code up.
2998
+ *
2999
+ * @param organizationId - The organization.
3000
+ * @param domainId - The claim.
3001
+ * @param code - The six digits.
3002
+ * @returns The verified domain.
3003
+ * @throws TulaError `verification.invalid_code` and its siblings, `organization.domain_taken`.
3004
+ * @example
3005
+ * ```ts
3006
+ * await tula.organizations.domains.verify(org.id, domain.id, '123456')
3007
+ * ```
3008
+ */
3009
+ verify(organizationId: string, domainId: string, code: string): Promise<OrganizationDomain2>;
3010
+ /**
3011
+ * Change what a domain does.
3012
+ *
3013
+ * @param organizationId - The organization.
3014
+ * @param domainId - The domain.
3015
+ * @param mode - `suggest` or `auto_join`.
3016
+ * @returns The domain.
3017
+ * @example
3018
+ * ```ts
3019
+ * await tula.organizations.domains.update(org.id, domain.id, 'auto_join')
3020
+ * ```
3021
+ */
3022
+ update(organizationId: string, domainId: string, mode: OrganizationDomain2["mode"]): Promise<OrganizationDomain2>;
3023
+ /**
3024
+ * Remove a domain. Later sign-ups are neither offered nor joined to the organization;
3025
+ * the members it brought stay.
3026
+ *
3027
+ * @param organizationId - The organization.
3028
+ * @param domainId - The domain.
3029
+ * @example
3030
+ * ```ts
3031
+ * await tula.organizations.domains.remove(org.id, domain.id)
3032
+ * ```
3033
+ */
3034
+ remove(organizationId: string, domainId: string): Promise<void>;
3035
+ };
3036
+ /**
3037
+ * An organization's single sign-on, for a member whose role holds
3038
+ * `org:sys:sso:manage`, which the API reads from the membership on every call. The client
3039
+ * secret is write-only: no answer returns it. Switching enforcement off, trusting the
3040
+ * identity provider for MFA and naming a break-glass role need a recent sign-in
3041
+ * (`auth.step_up_required`: step up, then repeat the call).
3042
+ */
3043
+ readonly sso: {
3044
+ /**
3045
+ * The organization's connections, with the service provider values to give the IdP.
3046
+ *
3047
+ * @param organizationId - The organization.
3048
+ * @returns The connections.
3049
+ * @throws TulaError `organization.not_a_member`, `auth.forbidden`, `sso.disabled`.
3050
+ * @example
3051
+ * ```ts
3052
+ * const { connections } = await tula.organizations.sso.list(org.id)
3053
+ * ```
3054
+ */
3055
+ list(organizationId: string): Promise<OrganizationSsoConnectionList>;
3056
+ /**
3057
+ * One connection.
3058
+ *
3059
+ * @param organizationId - The organization.
3060
+ * @param id - The connection.
3061
+ * @returns The connection.
3062
+ * @example
3063
+ * ```ts
3064
+ * const connection = await tula.organizations.sso.get(org.id, id)
3065
+ * ```
3066
+ */
3067
+ get(organizationId: string, id: string): Promise<OrganizationSsoConnection>;
3068
+ /**
3069
+ * Create a draft connection; a draft signs nobody in.
3070
+ *
3071
+ * @param organizationId - The organization.
3072
+ * @param input - The connection.
3073
+ * @returns The connection.
3074
+ * @example
3075
+ * ```ts
3076
+ * await tula.organizations.sso.create(org.id, { name: 'Okta', issuer, clientId, clientSecret
3077
+ * })
3078
+ * ```
3079
+ */
3080
+ create(organizationId: string, input: CreateOrganizationSsoConnectionInput): Promise<OrganizationSsoConnection>;
3081
+ /**
3082
+ * Change a connection; fields left out are kept.
3083
+ *
3084
+ * @param organizationId - The organization.
3085
+ * @param id - The connection.
3086
+ * @param input - The fields to change.
3087
+ * @returns The connection as it is now.
3088
+ * @throws TulaError `auth.step_up_required` for a weakening on a stale session.
3089
+ * @example
3090
+ * ```ts
3091
+ * await tula.organizations.sso.update(org.id, id, { status: 'active' })
3092
+ * ```
3093
+ */
3094
+ update(organizationId: string, id: string, input: UpdateOrganizationSsoConnectionInput): Promise<OrganizationSsoConnection>;
3095
+ /**
3096
+ * Read a SAML connection's metadata address again (a rotated certificate).
3097
+ *
3098
+ * @param organizationId - The organization.
3099
+ * @param id - The connection.
3100
+ * @returns The connection.
3101
+ * @example
3102
+ * ```ts
3103
+ * await tula.organizations.sso.refreshMetadata(org.id, id)
3104
+ * ```
3105
+ */
3106
+ refreshMetadata(organizationId: string, id: string): Promise<OrganizationSsoConnection>;
3107
+ /**
3108
+ * Delete a connection that is not active (`sso.connection_in_use` otherwise); its users keep
3109
+ * their accounts.
3110
+ *
3111
+ * @param organizationId - The organization.
3112
+ * @param id - The connection.
3113
+ * @example
3114
+ * ```ts
3115
+ * await tula.organizations.sso.remove(org.id, id)
3116
+ * ```
3117
+ */
3118
+ remove(organizationId: string, id: string): Promise<void>;
3119
+ /**
3120
+ * The IdP group to role mapping, in precedence order.
3121
+ *
3122
+ * @param organizationId - The organization.
3123
+ * @param id - A connection of the organization.
3124
+ * @returns The mapping.
3125
+ * @example
3126
+ * ```ts
3127
+ * const { mappings } = await tula.organizations.sso.groupRoles(org.id, id)
3128
+ * ```
3129
+ */
3130
+ groupRoles(organizationId: string, id: string): Promise<OrganizationSsoGroupRoles>;
3131
+ /**
3132
+ * Replace the whole mapping.
3133
+ *
3134
+ * @param organizationId - The organization.
3135
+ * @param id - A connection of the organization.
3136
+ * @param mapping - The new mapping.
3137
+ * @returns The mapping as saved.
3138
+ * @example
3139
+ * ```ts
3140
+ * await tula.organizations.sso.setGroupRoles(org.id, id, { mappings: [{ group: 'eng', role:
3141
+ * 'admin' }] })
3142
+ * ```
3143
+ */
3144
+ setGroupRoles(organizationId: string, id: string, mapping: OrganizationSsoGroupRoles): Promise<OrganizationSsoGroupRoles>;
3145
+ /**
3146
+ * Start a test sign-in of a connection. A passing test of its current settings is what
3147
+ * turns it on (`sso.test_required`). Send the browser to `authorizationUrl`; it returns to
3148
+ * `redirectUrl` with `#tula_test=<testId>`.
3149
+ *
3150
+ * @param organizationId - The organization.
3151
+ * @param id - A connection of the organization.
3152
+ * @param redirectUrl - The page to return to; the environment must allow it.
3153
+ * @returns The test's id and where to send the browser.
3154
+ * @throws TulaError `request.redirect_not_allowed`, and those of {@link get}.
3155
+ * @example
3156
+ * ```ts
3157
+ * const { authorizationUrl } = await tula.organizations.sso.startTest(org.id, id,
3158
+ * location.href)
3159
+ * ```
3160
+ */
3161
+ startTest(organizationId: string, id: string, redirectUrl: string): Promise<OrganizationSsoTestStart>;
3162
+ /**
3163
+ * Read a test's result: what arrived and how it mapped, or why it failed.
3164
+ *
3165
+ * @param organizationId - The organization.
3166
+ * @param id - The connection.
3167
+ * @param testId - The id the browser came back with.
3168
+ * @returns The result; `pending` until the browser has come back.
3169
+ * @throws TulaError `sso.test_not_found`, and those of {@link get}.
3170
+ * @example
3171
+ * ```ts
3172
+ * const result = await tula.organizations.sso.testResult(org.id, id, testId)
3173
+ * ```
3174
+ */
3175
+ testResult(organizationId: string, id: string, testId: string): Promise<OrganizationSsoTestResult>;
3176
+ /**
3177
+ * The organization's SSO setup links, newest first; never their tokens. A link lets an
3178
+ * IT administrator without an account set up an OpenID Connect connection (not SAML).
3179
+ *
3180
+ * @param organizationId - The organization.
3181
+ * @returns The links.
3182
+ * @throws TulaError `organization.not_a_member`, `organization.permission_required`,
3183
+ * `sso.disabled`.
3184
+ * @example
3185
+ * ```ts
3186
+ * const { links } = await tula.organizations.sso.setupLinks(org.id)
3187
+ * ```
3188
+ */
3189
+ setupLinks(organizationId: string): Promise<OrganizationSetupLinkList>;
3190
+ /**
3191
+ * Make a setup link. The answer holds the link's `url` once; it is not stored.
3192
+ *
3193
+ * @param organizationId - The organization.
3194
+ * @param input - The link's name and life in days.
3195
+ * @returns The link, with its token and address.
3196
+ * @throws TulaError those of {@link setupLinks}.
3197
+ * @example
3198
+ * ```ts
3199
+ * const { url } = await tula.organizations.sso.createSetupLink(org.id, { name: 'Acme IT' })
3200
+ * ```
3201
+ */
3202
+ createSetupLink(organizationId: string, input?: CreateOrganizationSetupLinkInput): Promise<CreatedOrganizationSetupLink>;
3203
+ /**
3204
+ * End a setup link now.
3205
+ *
3206
+ * @param organizationId - The organization.
3207
+ * @param linkId - The link.
3208
+ * @throws TulaError those of {@link setupLinks}.
3209
+ * @example
3210
+ * ```ts
3211
+ * await tula.organizations.sso.revokeSetupLink(org.id, link.id)
3212
+ * ```
3213
+ */
3214
+ revokeSetupLink(organizationId: string, linkId: string): Promise<void>;
3215
+ };
3216
+ /**
3217
+ * Invitations by email. The inviting calls need the permission
3218
+ * `org:sys:invitations:manage`, which the API reads from the user's membership on every
3219
+ * call. Answering needs the invited account: signed in, with the invited address, verified.
3220
+ */
3221
+ readonly invitations: {
3222
+ /**
3223
+ * An organization's pending invitations, newest first. Only the first page: the client
3224
+ * sends no paging or state query yet.
3225
+ *
3226
+ * @param organizationId - The organization.
3227
+ * @returns The page.
3228
+ * @throws TulaError `organization.not_a_member`, `organization.permission_required`,
3229
+ * `organization.disabled`.
3230
+ * @example
3231
+ * ```ts
3232
+ * const { data } = await tula.organizations.invitations.list(org.id)
3233
+ * ```
3234
+ */
3235
+ list(organizationId: string): Promise<OrganizationInvitationList2>;
3236
+ /**
3237
+ * Email an invitation. The link leads to `input.redirectUrl` (one of the environment's
3238
+ * allowed redirect URLs) with the token in its fragment; read it there with
3239
+ * `readInvitationLink`. The role must be within the permissions of the inviter's own.
3240
+ *
3241
+ * @param organizationId - The organization.
3242
+ * @param input - The address, the role's key and the redirect URL.
3243
+ * @returns The invitation; the token is only in the email.
3244
+ * @throws TulaError `organization.not_a_member`, `organization.permission_required`,
3245
+ * `organization.role_not_assignable`, `organization.role_unknown`,
3246
+ * `organization.member_exists`, `organization.invitation_exists`,
3247
+ * `request.redirect_not_allowed`, `rate_limited`.
3248
+ * @example
3249
+ * ```ts
3250
+ * await tula.organizations.invitations.invite(org.id, {
3251
+ * email: 'ada@example.com',
3252
+ * role: 'member',
3253
+ * redirectUrl: 'https://app.example.com/join',
3254
+ * })
3255
+ * ```
3256
+ */
3257
+ invite(organizationId: string, input: CreateOrganizationInvitationInput): Promise<OrganizationInvitation2>;
3258
+ /**
3259
+ * Withdraw a pending invitation: its link stops working.
3260
+ *
3261
+ * @param organizationId - The organization.
3262
+ * @param invitationId - The invitation.
3263
+ * @throws TulaError `organization.invitation_used`, `organization.invitation_revoked`,
3264
+ * `organization.invitation_expired` (410) for one that is no longer pending, and the
3265
+ * errors of `list`.
3266
+ * @example
3267
+ * ```ts
3268
+ * await tula.organizations.invitations.revoke(org.id, invitation.id)
3269
+ * ```
3270
+ */
3271
+ revoke(organizationId: string, invitationId: string): Promise<void>;
3272
+ /**
3273
+ * What an invitation's token shows before anyone has signed in: the organization's
3274
+ * name, the role's key and the address invited, to prefill a sign-up. Needs no session.
3275
+ *
3276
+ * @param token - The token from the link's fragment.
3277
+ * @returns The preview.
3278
+ * @throws TulaError `organization.invitation_not_found`, `organization.invitation_expired`,
3279
+ * `organization.invitation_revoked`, `organization.invitation_used`.
3280
+ * @example
3281
+ * ```ts
3282
+ * const { organizationName, email } = await tula.organizations.invitations.preview(token)
3283
+ * ```
3284
+ */
3285
+ preview(token: string): Promise<InvitationPreview2>;
3286
+ /**
3287
+ * Join the organization the token invites to, with the invitation's role. Only the
3288
+ * account the invitation was sent to may, and only with a verified address.
3289
+ *
3290
+ * @param token - The token from the link's fragment.
3291
+ * @returns The organization joined, with the role and its permission keys.
3292
+ * @throws TulaError `organization.invitation_wrong_account`,
3293
+ * `organization.invitation_email_unverified`, `organization.invitation_role_gone`,
3294
+ * `organization.member_exists`, and the errors of `preview`.
3295
+ * @example
3296
+ * ```ts
3297
+ * const org = await tula.organizations.invitations.accept(token)
3298
+ * await tula.organizations.setActive(org.id)
3299
+ * ```
3300
+ */
3301
+ accept(token: string): Promise<AcceptedInvitation2>;
3302
+ /**
3303
+ * Turn an invitation down: it is used up and nobody joins.
3304
+ *
3305
+ * @param token - The token from the link's fragment.
3306
+ * @throws TulaError As `accept`, but a deleted role does not matter.
3307
+ * @example
3308
+ * ```ts
3309
+ * await tula.organizations.invitations.decline(token)
3310
+ * ```
3311
+ */
3312
+ decline(token: string): Promise<void>;
3313
+ /**
3314
+ * The pending invitations addressed to the signed-in user's own verified address, newest
3315
+ * first: an `id`, the organization's name, the role's key and the expiry, never a token.
3316
+ * Only the first page: the client sends no paging query yet. An unverified address
3317
+ * has none.
3318
+ *
3319
+ * @returns The page.
3320
+ * @throws TulaError `organization.disabled`, `auth.unauthenticated`.
3321
+ * @example
3322
+ * ```ts
3323
+ * const { data } = await tula.organizations.invitations.mine()
3324
+ * ```
3325
+ */
3326
+ mine(): Promise<MyOrganizationInvitationList2>;
3327
+ /**
3328
+ * Join the organization of an invitation listed by `mine()`, by its id. The same checks as
3329
+ * `accept`: an id that is not the user's own answers `organization.invitation_not_found`.
3330
+ *
3331
+ * @param invitationId - The `id` from `mine()`.
3332
+ * @returns The organization joined, with the role and its permission keys.
3333
+ * @throws TulaError As `accept`.
3334
+ * @example
3335
+ * ```ts
3336
+ * const org = await tula.organizations.invitations.acceptMine(invitation.id)
3337
+ * ```
3338
+ */
3339
+ acceptMine(invitationId: string): Promise<AcceptedInvitation2>;
3340
+ /**
3341
+ * Turn down an invitation listed by `mine()`, by its id.
3342
+ *
3343
+ * @param invitationId - The `id` from `mine()`.
3344
+ * @throws TulaError As `decline`.
3345
+ * @example
3346
+ * ```ts
3347
+ * await tula.organizations.invitations.declineMine(invitation.id)
3348
+ * ```
3349
+ */
3350
+ declineMine(invitationId: string): Promise<void>;
3351
+ };
3352
+ };
2340
3353
  /** The signed-in user. */
2341
3354
  readonly user: {
2342
3355
  /**
@@ -2360,7 +3373,7 @@ interface TulaClient {
2360
3373
  newPassword: string;
2361
3374
  }): Promise<void>;
2362
3375
  /**
2363
- * The signed-in user's passkeys (ADR 0027). Adding, renaming and removing one may answer
3376
+ * The signed-in user's passkeys. Adding, renaming and removing one may answer
2364
3377
  * `auth.step_up_required`; see `session.stepUp`.
2365
3378
  */
2366
3379
  readonly passkeys: {
@@ -2756,4 +3769,31 @@ declare function createClient(options: TulaClientOptions, environment: Environme
2756
3769
  * ```
2757
3770
  */
2758
3771
  declare function createTulaClient(options: TulaClientOptions): TulaClient;
2759
- export { ACCESS_TOKEN_EXPIRY_SKEW_MS, AuthState, BackupCodes2 as BackupCodes, ChannelLike, ClientConfig2 as ClientConfig, ClientErrorCode, ClientKind, DEFAULT_TIMEOUT_MS, DeviceKey3 as DeviceKey, DeviceKeySource, DevicePublicJwk, EMAIL_LINK_POLL_INTERVAL_MS, EMAIL_LINK_SESSION_WAIT_MS, EN_MESSAGES, EmailCodeSent2 as EmailCodeSent, EmailLinkOutcome, Environment, ErrorParams2 as ErrorParams, FactorEnrolmentMethod2 as FactorEnrolmentMethod, FactorEnrolmentResult, Factors2 as Factors, FetchLike, FirstFactorStrategy2 as FirstFactorStrategy, FlowKind2 as FlowKind, FlowSnapshot, FlowStep2 as FlowStep, IdTokenName, IdTokenProvider2 as IdTokenProvider, IdTokenSignIn, Identity2 as Identity, LinkStorageLike, LockManagerLike, MAX_REFRESH_BACKOFF_MS, Messages, MfaPolicy2 as MfaPolicy, OAuthCallbackOutcome, OAuthProvider2 as OAuthProvider, PageLike, Passkey2 as Passkey, PasskeyCreationOptions2 as PasskeyCreationOptions, PasskeyGlobals, PasskeyProvider, PasskeyRequest, PasskeyRequestOptions2 as PasskeyRequestOptions, PasswordCheck, PasswordEvaluation, PasswordPolicy2 as PasswordPolicy, PasswordResetFlow, PasswordRule, PasswordUserInfo, PhoneCodeSent2 as PhoneCodeSent, REFRESH_RETRY_WINDOW_MS, REFRESH_TIMEOUT_MS, SecondFactorMethod2 as SecondFactorMethod, SecondFactorProof, SecondFactorResult, Session2 as Session, SignInFlow, SignUpFlow, SmsFactorCode2 as SmsFactorCode, StepUpMethod, StepUpPrepared, StepUpProof, TokenStorage, TotpEnrolment2 as TotpEnrolment, TulaClient, TulaClientOptions, TulaError, TulaErrorCode, TulaErrorInit, TulaFieldError, User, createTulaClient, createClient as createTulaClientWithEnvironment, evaluatePassword, formatMessage, generateSoftwareDeviceKey, isRetryableOAuthError, isStepUpRequired, isTulaError, memoryStorage, runtimeEnvironment, stepUpMethods };
3772
+ /** An invitation's token as read from the URL of an emailed link. */
3773
+ interface InvitationLink {
3774
+ /** The token to pass to `organizations.invitations.preview`, `accept` or `decline`. */
3775
+ token: string;
3776
+ /** The URL without the token, to put back in the address bar so the token is not kept. */
3777
+ cleanUrl: string;
3778
+ }
3779
+ /**
3780
+ * Read the invitation token from the URL an emailed invitation link leads to.
3781
+ *
3782
+ * The token travels in the URL fragment (`#tula_invitation=…`), which a browser never sends to
3783
+ * a server, so it is in no log and in no `Referer`. Read it, then replace the address bar with
3784
+ * `cleanUrl` (`history.replaceState`) so that the token is not left in the history.
3785
+ *
3786
+ * @param url - The page's URL, e.g. `window.location.href`.
3787
+ * @returns The token and the URL without it, or `null` when the URL carries no token.
3788
+ *
3789
+ * @example
3790
+ * ```ts
3791
+ * const link = readInvitationLink(window.location.href)
3792
+ * if (link) {
3793
+ * history.replaceState(null, '', link.cleanUrl)
3794
+ * const { organizationName } = await tula.organizations.invitations.preview(link.token)
3795
+ * }
3796
+ * ```
3797
+ */
3798
+ declare function readInvitationLink(url: string): InvitationLink | null;
3799
+ export { ACCESS_TOKEN_EXPIRY_SKEW_MS, AcceptedInvitation2 as AcceptedInvitation, ActiveOrganization2 as ActiveOrganization, AssignableRole2 as AssignableRole, AssignableRoleList2 as AssignableRoleList, AuthState, BackupCodes2 as BackupCodes, ChannelLike, ClientConfig2 as ClientConfig, ClientErrorCode, ClientKind, CreateOrganizationInput, CreateOrganizationInvitationInput, CreateOrganizationSetupLinkInput, CreateOrganizationSsoConnectionInput, CreatedOrganizationSetupLink, DEFAULT_TIMEOUT_MS, DeviceKey3 as DeviceKey, DeviceKeySource, DevicePublicJwk, EMAIL_LINK_POLL_INTERVAL_MS, EMAIL_LINK_SESSION_WAIT_MS, EN_MESSAGES, EmailCodeSent2 as EmailCodeSent, EmailLinkOutcome, Environment, ErrorParams2 as ErrorParams, FactorEnrolmentMethod2 as FactorEnrolmentMethod, FactorEnrolmentResult, Factors2 as Factors, FetchLike, FirstFactorStrategy2 as FirstFactorStrategy, FlowKind2 as FlowKind, FlowSnapshot, FlowStep2 as FlowStep, IdTokenName, IdTokenProvider2 as IdTokenProvider, IdTokenSignIn, Identity2 as Identity, InvitationLink, InvitationPreview2 as InvitationPreview, LinkStorageLike, LockManagerLike, MAX_REFRESH_BACKOFF_MS, Messages, MfaPolicy2 as MfaPolicy, MyOrganizationInvitation2 as MyOrganizationInvitation, MyOrganizationInvitationList2 as MyOrganizationInvitationList, OAuthCallbackOutcome, OAuthProvider2 as OAuthProvider, OrganizationDomain2 as OrganizationDomain, OrganizationDomainList2 as OrganizationDomainList, OrganizationInvitation2 as OrganizationInvitation, OrganizationInvitationList2 as OrganizationInvitationList, OrganizationMember2 as OrganizationMember, OrganizationMemberList2 as OrganizationMemberList, OrganizationSetupLink, OrganizationSetupLinkList, OrganizationSsoConnection, OrganizationSsoConnectionList, OrganizationSsoGroupRoles, OrganizationSsoTestResult, OrganizationSsoTestStart, OrganizationSuggestion2 as OrganizationSuggestion, OrganizationSuggestionList2 as OrganizationSuggestionList, PageLike, Passkey2 as Passkey, PasskeyCreationOptions2 as PasskeyCreationOptions, PasskeyGlobals, PasskeyProvider, PasskeyRequest, PasskeyRequestOptions2 as PasskeyRequestOptions, PasswordCheck, PasswordEvaluation, PasswordPolicy2 as PasswordPolicy, PasswordResetFlow, PasswordRule, PasswordUserInfo, PhoneCodeSent2 as PhoneCodeSent, REFRESH_RETRY_WINDOW_MS, REFRESH_TIMEOUT_MS, SecondFactorMethod2 as SecondFactorMethod, SecondFactorProof, SecondFactorResult, Session2 as Session, SignInFlow, SignUpFlow, SmsFactorCode2 as SmsFactorCode, StartOrganizationDomainInput, StepUpMethod, StepUpPrepared, StepUpProof, TokenStorage, TotpEnrolment2 as TotpEnrolment, TulaClient, TulaClientOptions, TulaError, TulaErrorCode, TulaErrorInit, TulaFieldError, UpdateOrganizationInput, UpdateOrganizationSsoConnectionInput, User, UserOrganization2 as UserOrganization, UserOrganizationList2 as UserOrganizationList, createTulaClient, createClient as createTulaClientWithEnvironment, evaluatePassword, formatMessage, generateSoftwareDeviceKey, isRetryableOAuthError, isStepUpRequired, isTulaError, memoryStorage, readInvitationLink, runtimeEnvironment, stepUpMethods };