@finueva/auth 0.11.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  Use `@finueva/auth` to add Finueva sign-in, organization access, invitations, and Drive credentials to a browser, server, CLI, or native application.
4
4
 
5
- The current published version is `0.10.1`. The package is ESM-only. Node.js consumers require Node.js 22 or later. Other server runtimes must provide compatible Fetch, Web Crypto, URL, and Web Streams APIs. Its interface is Experimental until version `1.0.0`.
5
+ The current published version is `0.11.1`. The package is ESM-only. Node.js consumers require Node.js 22 or later. Other server runtimes must provide compatible Fetch, Web Crypto, URL, and Web Streams APIs. Its interface is Experimental until version `1.0.0`.
6
6
 
7
- The passkey and recent-authentication methods below describe this repository's next package candidate. Published version `0.10.1` does not include them.
7
+ The platform-status, organization-authority, and invitation-administration methods below describe this repository's next package candidate. Published version `0.11.1` does not include them.
8
8
 
9
9
  ## Install
10
10
 
@@ -78,23 +78,27 @@ The browser owns the session cookie. The SDK does not expose or persist it.
78
78
 
79
79
  ## Common Tasks
80
80
 
81
- | Task | Browser | Server | Device |
82
- | ----------------------------- | ------------------------------------------- | -------------------------------------- | -------------------------------------- |
83
- | Create an account | `signUp()` | Not available | `signUpWithEmail()` |
84
- | Read the current session | `getSession()` | `getSession()` | `getSession()` |
85
- | Observe session state | `onAuthStateChange()` | Not available | Not available |
86
- | Update a profile | `updateProfile()` | Not available | `updateProfile()` |
87
- | Change a password | `changePassword()` | Not available | `changePassword()` |
88
- | Reset a password | `requestPasswordReset()`, `resetPassword()` | Not available | Not available |
89
- | Verify an email | `sendVerificationEmail()` | Not available | Not available |
90
- | List organizations | `listOrganizations()` | `listOrganizations()` | `listOrganizations()` |
91
- | Create an ordinary team | `createOrganizationTeam()` | `createOrganizationTeam()` | `createOrganizationTeam()` |
92
- | Invite an ordinary member | `createOrganizationMemberInvitation()` | `createOrganizationMemberInvitation()` | `createOrganizationMemberInvitation()` |
93
- | Accept an invitation | `acceptInvitation()` | `acceptInvitation()` | Not available |
94
- | Enroll a passkey | `addPasskey()` | Not available | Not available |
95
- | Perform recent authentication | Three `*RecentAuthentication*()` methods | Not available | Not available |
96
- | Request Drive access | `getDriveCredential()` | `getDriveCredential()` | `getDriveCredential()` |
97
- | End the current session | `signOut()` | `signOut()` | `revokeCurrentDevice()` |
81
+ | Task | Browser | Server | Device |
82
+ | ----------------------------- | ------------------------------------------- | ----------------------------------------- | -------------------------------------- |
83
+ | Create an account | `signUp()` | Not available | `signUpWithEmail()` |
84
+ | Read the current session | `getSession()` | `getSession()` | `getSession()` |
85
+ | Observe session state | `onAuthStateChange()` | Not available | Not available |
86
+ | Update a profile | `updateProfile()` | Not available | `updateProfile()` |
87
+ | Change a password | `changePassword()` | Not available | `changePassword()` |
88
+ | Reset a password | `requestPasswordReset()`, `resetPassword()` | Not available | Not available |
89
+ | Verify an email | `sendVerificationEmail()` | Not available | Not available |
90
+ | List organizations | `listOrganizations()` | `listOrganizations()` | `listOrganizations()` |
91
+ | Create an ordinary team | `createOrganizationTeam()` | `createOrganizationTeam()` | `createOrganizationTeam()` |
92
+ | Invite an ordinary member | `createOrganizationMemberInvitation()` | `createOrganizationMemberInvitation()` | `createOrganizationMemberInvitation()` |
93
+ | Resend a member invitation | `resendOrganizationInvitation()` | `resendOrganizationInvitation()` (cookie) | Not available |
94
+ | Cancel a member invitation | `cancelOrganizationInvitation()` | `cancelOrganizationInvitation()` (cookie) | Not available |
95
+ | Accept an invitation | `acceptInvitation()` | `acceptInvitation()` | Not available |
96
+ | Enroll a passkey | `addPasskey()` | Not available | Not available |
97
+ | Perform recent authentication | Platform and organization action methods | Not available | Not available |
98
+ | Change manual roles or owners | Organization authority methods | Not available | Not available |
99
+ | Read current platform roles | `getPlatformStatus()` | Not available | Not available |
100
+ | Request Drive access | `getDriveCredential()` | `getDriveCredential()` | `getDriveCredential()` |
101
+ | End the current session | `signOut()` | `signOut()` | `revokeCurrentDevice()` |
98
102
 
99
103
  ## Create An Organization Invitation
100
104
 
@@ -112,6 +116,15 @@ console.log(invitation.id, invitation.expiresAt);
112
116
 
113
117
  If the response is lost, repeat the same request with the same key. The SDK does not retry automatically. The result confirms invitation creation. It does not confirm email delivery.
114
118
 
119
+ Manual owners and manual administrators can resend or cancel the exact pending invitation through browser-cookie or server-cookie authority:
120
+
121
+ ```ts
122
+ const resent = await auth.resendOrganizationInvitation(organizationId, invitation.id, crypto.randomUUID());
123
+ const canceled = await auth.cancelOrganizationInvitation(organizationId, invitation.id, crypto.randomUUID());
124
+ ```
125
+
126
+ Resend success confirms the committed expiry. It does not confirm email delivery. Repeat an action with the same key only after a lost response. A bearer-backed server client rejects both methods before network I/O.
127
+
115
128
  ## Perform Recent Authentication
116
129
 
117
130
  The experimental passkey methods run only when the browser page uses the exact Auth Origin. To enroll a passkey, first create a new password-authenticated session on that origin:
@@ -129,7 +142,11 @@ Start one reviewed platform-role action before invoking passkey authentication:
129
142
  await auth.startPlatformRoleChangeRecentAuthentication({
130
143
  targetUserId: "user_01J8R7M6N5P4Q3S2T1",
131
144
  requestedRoles: ["administrator", "investigator"],
132
- expectedRevision: 4,
145
+ expectedTargetRevision: 0,
146
+ expectedGlobalRevision: 0,
147
+ reasonCategory: "initial_bootstrap",
148
+ changeReference: "change_01J8R7M6N5P4Q3S2T1",
149
+ idempotencyKey: crypto.randomUUID(),
133
150
  });
134
151
 
135
152
  await auth.verifyRecentAuthenticationWithPasskey();
@@ -140,10 +157,35 @@ if (continuation.status === "ready") {
140
157
  }
141
158
  ```
142
159
 
143
- The maintained passkey client performs the WebAuthn ceremony for the pending action when a compatible authenticator is present. Auth binds the exact generated challenge to that action. The SDK returns only `pending` or `ready`. It does not expose the challenge, WebAuthn response, session, cookies, proof, or hashes. `ready` does not grant a platform role or reusable administration mode. The tracer has no proof-consumption API. A future exact mutation must add atomic one-use consumption with its state change. No platform mutation is currently implemented.
160
+ The maintained passkey client performs the WebAuthn ceremony for the pending action when a compatible authenticator is present. Auth binds the exact generated challenge to that action. The SDK returns only `pending` or `ready`. It does not expose the challenge, WebAuthn response, session, cookies, proof, or hashes. `ready` does not grant a platform role or reusable administration mode. Bootstrap approval consumes the proof atomically with its state transition.
161
+
162
+ Organization owner changes use the same ceremony. Pass the same input to the start and submit methods:
163
+
164
+ ```ts
165
+ const change = {
166
+ requestedManualRole: "owner" as const,
167
+ expectedActorAuthorityRevision: 3,
168
+ expectedTargetAuthorityRevision: 1,
169
+ idempotencyKey: crypto.randomUUID(),
170
+ };
171
+
172
+ await auth.startOrganizationOwnerRoleChangeRecentAuthentication(organizationId, memberId, change);
173
+ await auth.verifyRecentAuthenticationWithPasskey();
174
+ await auth.changeOrganizationMemberManualRole(organizationId, memberId, change);
175
+ ```
176
+
177
+ Atomic transfer similarly pairs `startOrganizationOwnershipTransferRecentAuthentication()` with `transferOrganizationOwnership()`. The input names distinct outgoing-owner and successor memberships, the outgoing owner's resulting `member` or `admin` role, all exact authority revisions, and one idempotency key. The SDK validates and sends one normalized request and never exposes the consumed proof.
144
178
 
145
179
  If a start result is unavailable, call `startPlatformRoleChangeRecentAuthentication()` again with the same normalized input. The client reuses the current request nonce. A different start or observed account transition removes this local retry state. Only one start request can be active. An overlapping call returns `state_conflict` before network I/O. The SDK does not retry automatically.
146
180
 
181
+ Read the signed-in User's current effective platform roles on the exact Auth Origin:
182
+
183
+ ```ts
184
+ const platform = await auth.getPlatformStatus();
185
+ ```
186
+
187
+ The result contains `eligible`, `roles`, and `globalRevision`. Use it for display and mutation preparation only. Each platform operation checks current authority again. The browser SDK sends Auth-origin cookie credentials and an exact browser-generated `Origin`. It sends no `Authorization` header. Server and device clients do not expose this method.
188
+
147
189
  ## Request A Drive Credential
148
190
 
149
191
  Request one short-lived credential for one workspace:
@@ -284,7 +326,7 @@ Use `error.code` for application behavior. Do not parse `error.message`. Except
284
326
  - Organization collections use page sizes from 1 through 100.
285
327
  - Organization Drive context supports at most 50 current teams.
286
328
  - Invitation creation supports only the `member` role in ordinary organizations.
287
- - Invitation resend and cancellation are not public SDK methods.
329
+ - Invitation resend and cancellation require browser-cookie or server-cookie authority and a manual owner or manual administrator.
288
330
  - Cross-root SSO, custom refresh tokens, and account deletion are not implemented.
289
331
  - Production support gates are incomplete. Ask the Auth operator for the current environment status before adoption.
290
332
 
package/dist/device.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { B as SocialIdTokenInput, C as OrganizationTeam, E as OrganizationTeamPage, F as ReceivedOrganizationInvitation, H as UpdateOrganizationDisplayInput, I as ReceivedOrganizationInvitationPage, J as AuthErrorStatus, K as AuthError, S as OrganizationPageInput, T as OrganizationTeamMemberPage, U as UpdateOrganizationTeamDisplayInput, V as SocialIdTokenProvider, W as UpdateProfileInput, _ as OrganizationInvitation, b as OrganizationMemberPage, d as CreatedOrganizationTeam, f as DriveCredential, g as OrganizationDisplay, l as CreateOrganizationMemberInvitationInput, o as AuthSession, p as DriveCredentialInput, q as AuthErrorCode, r as AuthOrganization, u as CreatedOrganizationMemberInvitation, v as OrganizationInvitationPage, w as OrganizationTeamDisplay, x as OrganizationPage, y as OrganizationMember } from "./types-BqpgLkQB.js";
1
+ import { C as OrganizationPageInput, D as OrganizationTeamPage, E as OrganizationTeamMemberPage, G as UpdateOrganizationDisplayInput, K as UpdateOrganizationTeamDisplayInput, L as ReceivedOrganizationInvitation, R as ReceivedOrganizationInvitationPage, S as OrganizationPage, T as OrganizationTeamDisplay, U as SocialIdTokenInput, W as SocialIdTokenProvider, X as AuthErrorCode, Y as AuthError, Z as AuthErrorStatus, _ as OrganizationDisplay, b as OrganizationMember, d as CreatedOrganizationMemberInvitation, f as CreatedOrganizationTeam, m as DriveCredentialInput, o as AuthSession, p as DriveCredential, q as UpdateProfileInput, r as AuthOrganization, u as CreateOrganizationMemberInvitationInput, v as OrganizationInvitation, w as OrganizationTeam, x as OrganizationMemberPage, y as OrganizationInvitationPage } from "./types-Sh23XMKv.js";
2
2
  //#region src/device.d.ts
3
3
  type DeviceAuthorizationErrorCode = "device_access_denied" | "device_authorization_cancelled" | "device_code_expired" | "invalid_device_authorization" | "identity_authority_unavailable";
4
4
  declare class DeviceAuthorizationError extends Error {
package/dist/device.js CHANGED
@@ -448,6 +448,7 @@ const messages = {
448
448
  invalid_invitation: "The invitation is invalid.",
449
449
  invitation_unavailable: "The invitation is not available.",
450
450
  invitation_recipient_mismatch: "The invitation cannot be accepted by this account.",
451
+ invitation_recovery_required: "Invitation administration requires separate recovery.",
451
452
  email_verification_required: "Email verification is required.",
452
453
  invalid_workspace_selection: "The workspace selection is invalid.",
453
454
  organization_context_too_large: "The organization context is too large.",
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { A as PlatformRole, B as SocialIdTokenInput, C as OrganizationTeam, D as OrganizationWorkspaceSelection, E as OrganizationTeamPage, F as ReceivedOrganizationInvitation, G as WorkspaceSelection, H as UpdateOrganizationDisplayInput, I as ReceivedOrganizationInvitationPage, J as AuthErrorStatus, K as AuthError, L as RecentAuthenticationStatus, M as PlatformRoleApproval, N as PlatformRoleChangeRecentAuthenticationInput, O as PersonalWorkspaceSelection, P as PlatformRoleFirstApproval, R as SendVerificationEmailInput, S as OrganizationPageInput, T as OrganizationTeamMemberPage, U as UpdateOrganizationTeamDisplayInput, V as SocialIdTokenProvider, W as UpdateProfileInput, _ as OrganizationInvitation, a as AuthPersistenceDriver, b as OrganizationMemberPage, c as AuthUser, d as CreatedOrganizationTeam, f as DriveCredential, g as OrganizationDisplay, h as InvitationAcceptance, i as AuthPersistence, j as PlatformRoleApplied, k as PlatformBootstrapRoleChangeInput, l as CreateOrganizationMemberInvitationInput, m as FinuevaAuthClient, n as AuthClientOptions, o as AuthSession, p as DriveCredentialInput, q as AuthErrorCode, r as AuthOrganization, s as AuthState, t as AcceptInvitationInput, u as CreatedOrganizationMemberInvitation, v as OrganizationInvitationPage, w as OrganizationTeamDisplay, x as OrganizationPage, y as OrganizationMember, z as SignInInput } from "./types-BqpgLkQB.js";
1
+ import { A as PlatformBootstrapRoleChangeInput, B as ResentOrganizationInvitation, C as OrganizationPageInput, D as OrganizationTeamPage, E as OrganizationTeamMemberPage, F as PlatformRoleFirstApproval, G as UpdateOrganizationDisplayInput, H as SignInInput, I as PlatformStatus, J as WorkspaceSelection, K as UpdateOrganizationTeamDisplayInput, L as ReceivedOrganizationInvitation, M as PlatformRoleApplied, N as PlatformRoleApproval, O as OrganizationWorkspaceSelection, P as PlatformRoleChangeRecentAuthenticationInput, R as ReceivedOrganizationInvitationPage, S as OrganizationPage, T as OrganizationTeamDisplay, U as SocialIdTokenInput, V as SendVerificationEmailInput, W as SocialIdTokenProvider, X as AuthErrorCode, Y as AuthError, Z as AuthErrorStatus, _ as OrganizationDisplay, a as AuthPersistenceDriver, b as OrganizationMember, c as AuthUser, d as CreatedOrganizationMemberInvitation, f as CreatedOrganizationTeam, g as InvitationAcceptance, h as FinuevaAuthClient, i as AuthPersistence, j as PlatformRole, k as PersonalWorkspaceSelection, l as CanceledOrganizationInvitation, m as DriveCredentialInput, n as AuthClientOptions, o as AuthSession, p as DriveCredential, q as UpdateProfileInput, r as AuthOrganization, s as AuthState, t as AcceptInvitationInput, u as CreateOrganizationMemberInvitationInput, v as OrganizationInvitation, w as OrganizationTeam, x as OrganizationMemberPage, y as OrganizationInvitationPage, z as RecentAuthenticationStatus } from "./types-Sh23XMKv.js";
2
2
  //#region src/client.d.ts
3
3
  declare function createAuthClient(options: AuthClientOptions): FinuevaAuthClient;
4
4
  //#endregion
5
- export { type AcceptInvitationInput, type AuthClientOptions, AuthError, type AuthErrorCode, type AuthErrorStatus, type AuthOrganization, type AuthPersistence, type AuthPersistenceDriver, type AuthSession, type AuthState, type AuthUser, type CreateOrganizationMemberInvitationInput, type CreatedOrganizationMemberInvitation, type CreatedOrganizationTeam, type DriveCredential, type DriveCredentialInput, type FinuevaAuthClient, type InvitationAcceptance, type OrganizationDisplay, type OrganizationInvitation, type OrganizationInvitationPage, type OrganizationMember, type OrganizationMemberPage, type OrganizationPage, type OrganizationPageInput, type OrganizationTeam, type OrganizationTeamDisplay, type OrganizationTeamMemberPage, type OrganizationTeamPage, type OrganizationWorkspaceSelection, type PersonalWorkspaceSelection, type PlatformBootstrapRoleChangeInput, type PlatformRole, type PlatformRoleApplied, type PlatformRoleApproval, type PlatformRoleChangeRecentAuthenticationInput, type PlatformRoleFirstApproval, type ReceivedOrganizationInvitation, type ReceivedOrganizationInvitationPage, type RecentAuthenticationStatus, type SendVerificationEmailInput, type SignInInput, type SocialIdTokenInput, type SocialIdTokenProvider, type UpdateOrganizationDisplayInput, type UpdateOrganizationTeamDisplayInput, type UpdateProfileInput, type WorkspaceSelection, createAuthClient };
5
+ export { type AcceptInvitationInput, type AuthClientOptions, AuthError, type AuthErrorCode, type AuthErrorStatus, type AuthOrganization, type AuthPersistence, type AuthPersistenceDriver, type AuthSession, type AuthState, type AuthUser, type CanceledOrganizationInvitation, type CreateOrganizationMemberInvitationInput, type CreatedOrganizationMemberInvitation, type CreatedOrganizationTeam, type DriveCredential, type DriveCredentialInput, type FinuevaAuthClient, type InvitationAcceptance, type OrganizationDisplay, type OrganizationInvitation, type OrganizationInvitationPage, type OrganizationMember, type OrganizationMemberPage, type OrganizationPage, type OrganizationPageInput, type OrganizationTeam, type OrganizationTeamDisplay, type OrganizationTeamMemberPage, type OrganizationTeamPage, type OrganizationWorkspaceSelection, type PersonalWorkspaceSelection, type PlatformBootstrapRoleChangeInput, type PlatformRole, type PlatformRoleApplied, type PlatformRoleApproval, type PlatformRoleChangeRecentAuthenticationInput, type PlatformRoleFirstApproval, type PlatformStatus, type ReceivedOrganizationInvitation, type ReceivedOrganizationInvitationPage, type RecentAuthenticationStatus, type ResentOrganizationInvitation, type SendVerificationEmailInput, type SignInInput, type SocialIdTokenInput, type SocialIdTokenProvider, type UpdateOrganizationDisplayInput, type UpdateOrganizationTeamDisplayInput, type UpdateProfileInput, type WorkspaceSelection, createAuthClient };
package/dist/index.js CHANGED
@@ -2544,6 +2544,7 @@ const messages = {
2544
2544
  invalid_invitation: "The invitation is invalid.",
2545
2545
  invitation_unavailable: "The invitation is not available.",
2546
2546
  invitation_recipient_mismatch: "The invitation cannot be accepted by this account.",
2547
+ invitation_recovery_required: "Invitation administration requires separate recovery.",
2547
2548
  email_verification_required: "Email verification is required.",
2548
2549
  invalid_workspace_selection: "The workspace selection is invalid.",
2549
2550
  organization_context_too_large: "The organization context is too large.",
@@ -3067,6 +3068,20 @@ function normalizeCreatedOrganizationMemberInvitation(value) {
3067
3068
  expiresAt: responseDate(value.expiresAt)
3068
3069
  };
3069
3070
  }
3071
+ function normalizeResentOrganizationInvitation(value, invitationId) {
3072
+ const invitation = normalizeCreatedOrganizationMemberInvitation(value);
3073
+ if (invitation.id !== invitationId) throw authorityUnavailable();
3074
+ return invitation;
3075
+ }
3076
+ function normalizeOrganizationInvitationAdministrationInput(idempotencyKey) {
3077
+ if (!boundedString(idempotencyKey, 255)) throw new AuthError("invalid_request", 400);
3078
+ return { idempotencyKey };
3079
+ }
3080
+ function normalizeCanceledOrganizationInvitation(value) {
3081
+ exact(value, ["status"]);
3082
+ if (value.status !== "canceled") throw authorityUnavailable();
3083
+ return { status: "canceled" };
3084
+ }
3070
3085
  function normalizeOrganizations(value) {
3071
3086
  exact(value, ["nextCursor", "organizations"]);
3072
3087
  if (!Array.isArray(value.organizations) || value.organizations.length > 100) throw authorityUnavailable();
@@ -3298,6 +3313,10 @@ function fromOrganizationMutationAuthorityError(value, status, retryAfter) {
3298
3313
  if (status === 403 && code === "REQUEST_ORIGIN_DENIED") return new AuthError("request_origin_denied", 403);
3299
3314
  return fromOrganizationReadAuthorityError(value, status);
3300
3315
  }
3316
+ function fromOrganizationInvitationAdministrationError(value, status, retryAfter) {
3317
+ if (status === 422 && isObject(value) && value.code === "RECOVERY_REQUIRED") return new AuthError("invitation_recovery_required", 422);
3318
+ return fromOrganizationMutationAuthorityError(value, status, retryAfter);
3319
+ }
3301
3320
  function normalizePlatformRoleChangeRecentAuthenticationInput(value) {
3302
3321
  if (!isObject(value) || Object.keys(value).sort().join(",") !== "changeReference,expectedGlobalRevision,expectedTargetRevision,idempotencyKey,reasonCategory,requestedRoles,targetUserId") throw new TypeError("recent authentication input is invalid");
3303
3322
  if (!isId(value.targetUserId)) throw new TypeError("targetUserId is invalid");
@@ -3322,6 +3341,88 @@ function normalizePlatformRoleChangeRecentAuthenticationInput(value) {
3322
3341
  idempotencyKey: value.idempotencyKey
3323
3342
  };
3324
3343
  }
3344
+ function normalizeOrganizationManualRoleChangeInput(value) {
3345
+ if (!isObject(value) || Object.keys(value).sort().join(",") !== "expectedActorAuthorityRevision,expectedTargetAuthorityRevision,idempotencyKey,requestedManualRole") throw new TypeError("organization manual role input is invalid");
3346
+ if (value.requestedManualRole !== "owner" && value.requestedManualRole !== "admin" && value.requestedManualRole !== "member") throw new TypeError("requestedManualRole is invalid");
3347
+ const expectedActorAuthorityRevision = normalizeAuthorityRevision(value.expectedActorAuthorityRevision, "expectedActorAuthorityRevision");
3348
+ const expectedTargetAuthorityRevision = normalizeAuthorityRevision(value.expectedTargetAuthorityRevision, "expectedTargetAuthorityRevision", true);
3349
+ return {
3350
+ requestedManualRole: value.requestedManualRole,
3351
+ expectedActorAuthorityRevision,
3352
+ expectedTargetAuthorityRevision,
3353
+ idempotencyKey: normalizeAuthorityIdempotencyKey(value.idempotencyKey)
3354
+ };
3355
+ }
3356
+ function normalizeOrganizationOwnershipTransferInput(value) {
3357
+ if (!isObject(value) || Object.keys(value).sort().join(",") !== "expectedActorAuthorityRevision,expectedOutgoingOwnerAuthorityRevision,expectedSuccessorAuthorityRevision,idempotencyKey,outgoingOwnerMembershipId,outgoingOwnerRole,successorMembershipId") throw new TypeError("organization ownership transfer input is invalid");
3358
+ if (!isId(value.outgoingOwnerMembershipId) || !isId(value.successorMembershipId)) throw new TypeError("ownership transfer membership is invalid");
3359
+ if (value.outgoingOwnerMembershipId === value.successorMembershipId) throw new TypeError("ownership transfer memberships must differ");
3360
+ if (value.outgoingOwnerRole !== "admin" && value.outgoingOwnerRole !== "member") throw new TypeError("outgoingOwnerRole is invalid");
3361
+ return {
3362
+ outgoingOwnerMembershipId: value.outgoingOwnerMembershipId,
3363
+ successorMembershipId: value.successorMembershipId,
3364
+ outgoingOwnerRole: value.outgoingOwnerRole,
3365
+ expectedActorAuthorityRevision: normalizeAuthorityRevision(value.expectedActorAuthorityRevision, "expectedActorAuthorityRevision"),
3366
+ expectedOutgoingOwnerAuthorityRevision: normalizeAuthorityRevision(value.expectedOutgoingOwnerAuthorityRevision, "expectedOutgoingOwnerAuthorityRevision", true),
3367
+ expectedSuccessorAuthorityRevision: normalizeAuthorityRevision(value.expectedSuccessorAuthorityRevision, "expectedSuccessorAuthorityRevision", true),
3368
+ idempotencyKey: normalizeAuthorityIdempotencyKey(value.idempotencyKey)
3369
+ };
3370
+ }
3371
+ function normalizeOrganizationManualRoleChangeResult(value) {
3372
+ exact(value, [
3373
+ "status",
3374
+ "memberId",
3375
+ "manualRole",
3376
+ "role",
3377
+ "authorityRevision"
3378
+ ]);
3379
+ if (value.status !== "applied") throw authorityUnavailable();
3380
+ return {
3381
+ status: "applied",
3382
+ ...normalizeOrganizationAuthorityMemberOutcome(value, true)
3383
+ };
3384
+ }
3385
+ function normalizeOrganizationOwnershipTransferResult(value) {
3386
+ exact(value, [
3387
+ "status",
3388
+ "outgoingOwner",
3389
+ "successor"
3390
+ ]);
3391
+ if (value.status !== "applied") throw authorityUnavailable();
3392
+ return {
3393
+ status: "applied",
3394
+ outgoingOwner: normalizeOrganizationAuthorityMemberOutcome(value.outgoingOwner),
3395
+ successor: normalizeOrganizationAuthorityMemberOutcome(value.successor)
3396
+ };
3397
+ }
3398
+ function normalizeOrganizationAuthorityMemberOutcome(value, includesStatus = false) {
3399
+ exact(value, [
3400
+ "memberId",
3401
+ "manualRole",
3402
+ "role",
3403
+ "authorityRevision",
3404
+ ...includesStatus ? ["status"] : []
3405
+ ]);
3406
+ if (!isId(value.memberId) || value.manualRole !== "owner" && value.manualRole !== "admin" && value.manualRole !== "member" || value.role !== "owner" && value.role !== "admin" && value.role !== "member") throw authorityUnavailable();
3407
+ return {
3408
+ memberId: value.memberId,
3409
+ manualRole: value.manualRole,
3410
+ role: value.role,
3411
+ authorityRevision: normalizeAuthorityResultRevision(value.authorityRevision)
3412
+ };
3413
+ }
3414
+ function normalizeAuthorityRevision(value, name, incremented = false) {
3415
+ if (!Number.isSafeInteger(value) || value < 0 || value >= Number.MAX_SAFE_INTEGER - (incremented ? 1 : 0)) throw new TypeError(`${name} is invalid`);
3416
+ return value;
3417
+ }
3418
+ function normalizeAuthorityResultRevision(value) {
3419
+ if (!Number.isSafeInteger(value) || value < 0 || value >= Number.MAX_SAFE_INTEGER) throw authorityUnavailable();
3420
+ return value;
3421
+ }
3422
+ function normalizeAuthorityIdempotencyKey(value) {
3423
+ if (typeof value !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(value)) throw new TypeError("idempotencyKey is invalid");
3424
+ return value;
3425
+ }
3325
3426
  function normalizePlatformBootstrapRoleChangeInput(value) {
3326
3427
  const request = normalizePlatformRoleChangeRecentAuthenticationInput(value);
3327
3428
  if (request.reasonCategory !== "initial_bootstrap" || !request.requestedRoles.includes("administrator")) throw new TypeError("platform first approval input is invalid");
@@ -3354,6 +3455,21 @@ function normalizePlatformRoleApproval(value) {
3354
3455
  roles: []
3355
3456
  };
3356
3457
  }
3458
+ function normalizePlatformStatus(value) {
3459
+ exact(value, [
3460
+ "eligible",
3461
+ "globalRevision",
3462
+ "roles"
3463
+ ]);
3464
+ if (typeof value.eligible !== "boolean") throw authorityUnavailable();
3465
+ if (!Number.isSafeInteger(value.globalRevision) || value.globalRevision < 0 || value.globalRevision >= Number.MAX_SAFE_INTEGER) throw authorityUnavailable();
3466
+ if (!Array.isArray(value.roles) || !(value.roles.length === 0 || value.roles.length === 1 && (value.roles[0] === "administrator" || value.roles[0] === "investigator") || value.roles.length === 2 && value.roles[0] === "administrator" && value.roles[1] === "investigator") || !value.eligible && value.roles.length !== 0) throw authorityUnavailable();
3467
+ return {
3468
+ eligible: value.eligible,
3469
+ globalRevision: value.globalRevision,
3470
+ roles: value.roles
3471
+ };
3472
+ }
3357
3473
  function normalizeRecentAuthenticationStatus(value) {
3358
3474
  exact(value, ["status"]);
3359
3475
  if (value.status !== "pending" && value.status !== "ready") throw authorityUnavailable();
@@ -4354,6 +4470,9 @@ function createAuthClient(options) {
4354
4470
  let selectedWorkspaceKey;
4355
4471
  let credentialGeneration = 0;
4356
4472
  let authorityGeneration = 0;
4473
+ let platformStatusReadEpoch = 0;
4474
+ let sessionAuthorityEpoch = 0;
4475
+ let sessionAuthorityObserved = false;
4357
4476
  let recentAuthenticationBrowserToken;
4358
4477
  let recentAuthenticationStartAttempt;
4359
4478
  let recentAuthenticationStartRequest;
@@ -4479,9 +4598,11 @@ function createAuthClient(options) {
4479
4598
  }
4480
4599
  function updateSession(session) {
4481
4600
  const unboundCredential = !sessionId && (credential || credentialRequests.size > 0);
4482
- const changedSession = !session || sessionId !== void 0 && (session.id !== sessionId || session.user.id !== sessionUserId);
4601
+ const changedSession = sessionId === void 0 ? !sessionAuthorityObserved || session !== null : !session || session.id !== sessionId || session.user.id !== sessionUserId;
4483
4602
  if (!session || unboundCredential || sessionId && (session.id !== sessionId || session.user.id !== sessionUserId)) clearCredential();
4484
4603
  if (changedSession || recentAuthenticationStartAttempt && (recentAuthenticationStartAttempt.sessionId !== session?.id || recentAuthenticationStartAttempt.userId !== session?.user.id)) recentAuthenticationStartAttempt = void 0;
4604
+ if (changedSession) sessionAuthorityEpoch += 1;
4605
+ sessionAuthorityObserved = true;
4485
4606
  sessionId = session?.id;
4486
4607
  sessionUserId = session?.user.id;
4487
4608
  return session;
@@ -4631,52 +4752,89 @@ function createAuthClient(options) {
4631
4752
  function assertRecentAuthenticationOrigin() {
4632
4753
  if (appOrigin !== authOrigin) throw new TypeError("recent authentication requires the exact Auth Origin");
4633
4754
  }
4755
+ function assertPlatformAdministrationOrigin() {
4756
+ if (appOrigin !== authOrigin) throw new TypeError("platform administration requires the exact Auth Origin");
4757
+ }
4758
+ async function startRecentAuthentication(body) {
4759
+ assertRecentAuthenticationOrigin();
4760
+ if (signOutRequest || activeAccountMutationGeneration !== void 0 || invitationRequest) throw new AuthError("state_conflict", 409);
4761
+ if (recentAuthenticationStartRequest) throw new AuthError("state_conflict", 409);
4762
+ const generation = authorityGeneration;
4763
+ recentAuthenticationBrowserToken ??= createOpaqueToken();
4764
+ const normalizedInput = JSON.stringify(body);
4765
+ const attempt = recentAuthenticationStartAttempt?.input === normalizedInput ? recentAuthenticationStartAttempt : {
4766
+ input: normalizedInput,
4767
+ requestToken: createOpaqueToken(),
4768
+ sessionId,
4769
+ userId: sessionUserId
4770
+ };
4771
+ recentAuthenticationStartAttempt = attempt;
4772
+ const request = (async () => {
4773
+ try {
4774
+ const value = await requestJson(joinURL(authOrigin, "api/v1/auth/recent-authentication"), {
4775
+ fetch: fetchImpl,
4776
+ timeoutMs,
4777
+ method: "POST",
4778
+ headers: {
4779
+ "content-type": "application/json",
4780
+ [RECENT_AUTHENTICATION_BROWSER_HEADER]: recentAuthenticationBrowserToken,
4781
+ [RECENT_AUTHENTICATION_REQUEST_HEADER]: attempt.requestToken
4782
+ },
4783
+ body,
4784
+ mapError: fromRecentAuthenticationError
4785
+ });
4786
+ if (generation !== authorityGeneration) throw new AuthError("state_conflict", 409);
4787
+ const status = normalizeRecentAuthenticationStatus(value);
4788
+ if (recentAuthenticationStartAttempt === attempt) recentAuthenticationStartAttempt = void 0;
4789
+ return status;
4790
+ } catch (error) {
4791
+ if (recentAuthenticationStartAttempt === attempt && (!(error instanceof AuthError) || error.code !== "identity_authority_unavailable")) recentAuthenticationStartAttempt = void 0;
4792
+ throw error;
4793
+ }
4794
+ })();
4795
+ recentAuthenticationStartRequest = request;
4796
+ try {
4797
+ return await request;
4798
+ } finally {
4799
+ if (recentAuthenticationStartRequest === request) recentAuthenticationStartRequest = void 0;
4800
+ }
4801
+ }
4634
4802
  return {
4635
4803
  _type: "browser",
4636
- async startPlatformRoleChangeRecentAuthentication(input) {
4637
- assertRecentAuthenticationOrigin();
4638
- const body = normalizePlatformRoleChangeRecentAuthenticationInput(input);
4804
+ async getPlatformStatus() {
4805
+ assertPlatformAdministrationOrigin();
4639
4806
  if (signOutRequest || activeAccountMutationGeneration !== void 0 || invitationRequest) throw new AuthError("state_conflict", 409);
4640
- if (recentAuthenticationStartRequest) throw new AuthError("state_conflict", 409);
4641
4807
  const generation = authorityGeneration;
4642
- recentAuthenticationBrowserToken ??= createOpaqueToken();
4643
- const normalizedInput = JSON.stringify(body);
4644
- const attempt = recentAuthenticationStartAttempt?.input === normalizedInput ? recentAuthenticationStartAttempt : {
4645
- input: normalizedInput,
4646
- requestToken: createOpaqueToken(),
4647
- sessionId,
4648
- userId: sessionUserId
4649
- };
4650
- recentAuthenticationStartAttempt = attempt;
4651
- const request = (async () => {
4652
- try {
4653
- const value = await requestJson(joinURL(authOrigin, "api/v1/auth/recent-authentication"), {
4654
- fetch: fetchImpl,
4655
- timeoutMs,
4656
- method: "POST",
4657
- headers: {
4658
- "content-type": "application/json",
4659
- [RECENT_AUTHENTICATION_BROWSER_HEADER]: recentAuthenticationBrowserToken,
4660
- [RECENT_AUTHENTICATION_REQUEST_HEADER]: attempt.requestToken
4661
- },
4662
- body,
4663
- mapError: fromRecentAuthenticationError
4664
- });
4665
- if (generation !== authorityGeneration) throw new AuthError("state_conflict", 409);
4666
- const status = normalizeRecentAuthenticationStatus(value);
4667
- if (recentAuthenticationStartAttempt === attempt) recentAuthenticationStartAttempt = void 0;
4668
- return status;
4669
- } catch (error) {
4670
- if (recentAuthenticationStartAttempt === attempt && (!(error instanceof AuthError) || error.code !== "identity_authority_unavailable")) recentAuthenticationStartAttempt = void 0;
4671
- throw error;
4672
- }
4673
- })();
4674
- recentAuthenticationStartRequest = request;
4675
- try {
4676
- return await request;
4677
- } finally {
4678
- if (recentAuthenticationStartRequest === request) recentAuthenticationStartRequest = void 0;
4679
- }
4808
+ const readEpoch = ++platformStatusReadEpoch;
4809
+ const sessionEpoch = sessionAuthorityEpoch;
4810
+ const value = await requestJson(joinURL(authOrigin, "api/v1/auth/platform/status"), {
4811
+ fetch: fetchImpl,
4812
+ timeoutMs,
4813
+ method: "POST",
4814
+ headers: { "content-type": "application/json" },
4815
+ body: {},
4816
+ mapError: fromPlatformAdministrationError
4817
+ });
4818
+ if (generation !== authorityGeneration || readEpoch !== platformStatusReadEpoch || sessionEpoch !== sessionAuthorityEpoch) throw new AuthError("state_conflict", 409);
4819
+ return normalizePlatformStatus(value);
4820
+ },
4821
+ async startPlatformRoleChangeRecentAuthentication(input) {
4822
+ return startRecentAuthentication(normalizePlatformRoleChangeRecentAuthenticationInput(input));
4823
+ },
4824
+ async startOrganizationOwnerRoleChangeRecentAuthentication(organizationId, targetMembershipId, input) {
4825
+ return startRecentAuthentication({
4826
+ action: "organization_owner_role_change",
4827
+ organizationId: normalizeIdInput(organizationId),
4828
+ targetMembershipId: normalizeIdInput(targetMembershipId),
4829
+ ...normalizeOrganizationManualRoleChangeInput(input)
4830
+ });
4831
+ },
4832
+ async startOrganizationOwnershipTransferRecentAuthentication(organizationId, input) {
4833
+ return startRecentAuthentication({
4834
+ action: "organization_ownership_transfer",
4835
+ organizationId: normalizeIdInput(organizationId),
4836
+ ...normalizeOrganizationOwnershipTransferInput(input)
4837
+ });
4680
4838
  },
4681
4839
  async approveBootstrapPlatformRoleChange(input) {
4682
4840
  assertRecentAuthenticationOrigin();
@@ -4804,6 +4962,40 @@ function createAuthClient(options) {
4804
4962
  if (generation !== authorityGeneration) throw new AuthError("state_conflict", 409);
4805
4963
  return normalizeCreatedOrganizationMemberInvitation(value);
4806
4964
  },
4965
+ async resendOrganizationInvitation(organizationId, invitationId, idempotencyKey) {
4966
+ const organization = normalizeIdInput(organizationId);
4967
+ const invitation = normalizeIdInput(invitationId);
4968
+ const body = normalizeOrganizationInvitationAdministrationInput(idempotencyKey);
4969
+ if (signOutRequest || activeAccountMutationGeneration !== void 0 || invitationRequest) throw new AuthError("state_conflict", 409);
4970
+ const generation = authorityGeneration;
4971
+ const value = await requestJson(joinURL(authOrigin, "api/v1/auth/organizations", organization, "invitations", invitation, "resend"), {
4972
+ fetch: fetchImpl,
4973
+ timeoutMs,
4974
+ method: "POST",
4975
+ headers: { "content-type": "application/json" },
4976
+ body,
4977
+ mapError: fromOrganizationInvitationAdministrationError
4978
+ });
4979
+ if (generation !== authorityGeneration) throw new AuthError("state_conflict", 409);
4980
+ return normalizeResentOrganizationInvitation(value, invitation);
4981
+ },
4982
+ async cancelOrganizationInvitation(organizationId, invitationId, idempotencyKey) {
4983
+ const organization = normalizeIdInput(organizationId);
4984
+ const invitation = normalizeIdInput(invitationId);
4985
+ const body = normalizeOrganizationInvitationAdministrationInput(idempotencyKey);
4986
+ if (signOutRequest || activeAccountMutationGeneration !== void 0 || invitationRequest) throw new AuthError("state_conflict", 409);
4987
+ const generation = authorityGeneration;
4988
+ const value = await requestJson(joinURL(authOrigin, "api/v1/auth/organizations", organization, "invitations", invitation, "cancel"), {
4989
+ fetch: fetchImpl,
4990
+ timeoutMs,
4991
+ method: "POST",
4992
+ headers: { "content-type": "application/json" },
4993
+ body,
4994
+ mapError: fromOrganizationInvitationAdministrationError
4995
+ });
4996
+ if (generation !== authorityGeneration) throw new AuthError("state_conflict", 409);
4997
+ return normalizeCanceledOrganizationInvitation(value);
4998
+ },
4807
4999
  async updateOrganizationDisplay(organizationId, input) {
4808
5000
  const id = normalizeIdInput(organizationId);
4809
5001
  const desired = normalizeOrganizationDisplayInput(input);
@@ -4837,6 +5029,39 @@ function createAuthClient(options) {
4837
5029
  if (generation !== authorityGeneration) throw new AuthError("state_conflict", 409);
4838
5030
  return normalizeOrganizationTeamDisplay(value);
4839
5031
  },
5032
+ async changeOrganizationMemberManualRole(organizationId, targetMembershipId, input) {
5033
+ const organization = normalizeIdInput(organizationId);
5034
+ const target = normalizeIdInput(targetMembershipId);
5035
+ const body = normalizeOrganizationManualRoleChangeInput(input);
5036
+ if (signOutRequest || activeAccountMutationGeneration !== void 0 || invitationRequest) throw new AuthError("state_conflict", 409);
5037
+ const generation = authorityGeneration;
5038
+ const value = await requestJson(joinURL(authOrigin, "api/v1/auth/organizations", organization, "members", target, "manual-role"), {
5039
+ fetch: fetchImpl,
5040
+ timeoutMs,
5041
+ method: "POST",
5042
+ headers: { "content-type": "application/json" },
5043
+ body,
5044
+ mapError: fromOrganizationMutationAuthorityError
5045
+ });
5046
+ if (generation !== authorityGeneration) throw new AuthError("state_conflict", 409);
5047
+ return normalizeOrganizationManualRoleChangeResult(value);
5048
+ },
5049
+ async transferOrganizationOwnership(organizationId, input) {
5050
+ const organization = normalizeIdInput(organizationId);
5051
+ const body = normalizeOrganizationOwnershipTransferInput(input);
5052
+ if (signOutRequest || activeAccountMutationGeneration !== void 0 || invitationRequest) throw new AuthError("state_conflict", 409);
5053
+ const generation = authorityGeneration;
5054
+ const value = await requestJson(joinURL(authOrigin, "api/v1/auth/organizations", organization, "ownership-transfer"), {
5055
+ fetch: fetchImpl,
5056
+ timeoutMs,
5057
+ method: "POST",
5058
+ headers: { "content-type": "application/json" },
5059
+ body,
5060
+ mapError: fromOrganizationMutationAuthorityError
5061
+ });
5062
+ if (generation !== authorityGeneration) throw new AuthError("state_conflict", 409);
5063
+ return normalizeOrganizationOwnershipTransferResult(value);
5064
+ },
4840
5065
  async listOrganizationMembers(organizationId, input) {
4841
5066
  return organizationRead(`${normalizeIdInput(organizationId)}/members`, input, normalizeOrganizationMembers);
4842
5067
  },
package/dist/server.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { B as SocialIdTokenInput, C as OrganizationTeam, D as OrganizationWorkspaceSelection, E as OrganizationTeamPage, F as ReceivedOrganizationInvitation, G as WorkspaceSelection, H as UpdateOrganizationDisplayInput, I as ReceivedOrganizationInvitationPage, J as AuthErrorStatus, K as AuthError, O as PersonalWorkspaceSelection, S as OrganizationPageInput, T as OrganizationTeamMemberPage, U as UpdateOrganizationTeamDisplayInput, V as SocialIdTokenProvider, _ as OrganizationInvitation, b as OrganizationMemberPage, c as AuthUser, d as CreatedOrganizationTeam, f as DriveCredential, g as OrganizationDisplay, h as InvitationAcceptance, l as CreateOrganizationMemberInvitationInput, o as AuthSession, p as DriveCredentialInput, q as AuthErrorCode, r as AuthOrganization, t as AcceptInvitationInput, u as CreatedOrganizationMemberInvitation, v as OrganizationInvitationPage, w as OrganizationTeamDisplay, x as OrganizationPage, y as OrganizationMember } from "./types-BqpgLkQB.js";
1
+ import { B as ResentOrganizationInvitation, C as OrganizationPageInput, D as OrganizationTeamPage, E as OrganizationTeamMemberPage, G as UpdateOrganizationDisplayInput, J as WorkspaceSelection, K as UpdateOrganizationTeamDisplayInput, L as ReceivedOrganizationInvitation, O as OrganizationWorkspaceSelection, R as ReceivedOrganizationInvitationPage, S as OrganizationPage, T as OrganizationTeamDisplay, U as SocialIdTokenInput, W as SocialIdTokenProvider, X as AuthErrorCode, Y as AuthError, Z as AuthErrorStatus, _ as OrganizationDisplay, b as OrganizationMember, c as AuthUser, d as CreatedOrganizationMemberInvitation, f as CreatedOrganizationTeam, g as InvitationAcceptance, k as PersonalWorkspaceSelection, l as CanceledOrganizationInvitation, m as DriveCredentialInput, o as AuthSession, p as DriveCredential, r as AuthOrganization, t as AcceptInvitationInput, u as CreateOrganizationMemberInvitationInput, v as OrganizationInvitation, w as OrganizationTeam, x as OrganizationMemberPage, y as OrganizationInvitationPage } from "./types-Sh23XMKv.js";
2
2
  //#region src/server.d.ts
3
3
  interface ServerAuthOptions {
4
4
  authOrigin: string;
@@ -24,6 +24,8 @@ interface FinuevaServerAuth {
24
24
  getOrganization(organizationId: string): Promise<AuthOrganization>;
25
25
  createOrganizationTeam(organizationId: string, name: string, idempotencyKey: string): Promise<CreatedOrganizationTeam>;
26
26
  createOrganizationMemberInvitation(organizationId: string, input: CreateOrganizationMemberInvitationInput): Promise<CreatedOrganizationMemberInvitation>;
27
+ resendOrganizationInvitation(organizationId: string, invitationId: string, idempotencyKey: string): Promise<ResentOrganizationInvitation>;
28
+ cancelOrganizationInvitation(organizationId: string, invitationId: string, idempotencyKey: string): Promise<CanceledOrganizationInvitation>;
27
29
  updateOrganizationDisplay(organizationId: string, input: UpdateOrganizationDisplayInput): Promise<OrganizationDisplay>;
28
30
  updateOrganizationTeamDisplay(organizationId: string, teamId: string, input: UpdateOrganizationTeamDisplayInput): Promise<OrganizationTeamDisplay>;
29
31
  listOrganizationMembers(organizationId: string, input?: OrganizationPageInput): Promise<OrganizationMemberPage>;
@@ -39,4 +41,4 @@ interface FinuevaServerAuth {
39
41
  }
40
42
  declare function createServerAuth(options: ServerAuthOptions): FinuevaServerAuth;
41
43
  //#endregion
42
- export { type AcceptInvitationInput, AuthError, type AuthErrorCode, type AuthErrorStatus, type AuthOrganization, type AuthSession, type AuthUser, type CreateOrganizationMemberInvitationInput, type CreatedOrganizationMemberInvitation, type CreatedOrganizationTeam, type DriveCredential, type DriveCredentialInput, FinuevaServerAuth, type InvitationAcceptance, type OrganizationDisplay, type OrganizationInvitation, type OrganizationInvitationPage, type OrganizationMember, type OrganizationMemberPage, type OrganizationPage, type OrganizationPageInput, type OrganizationTeam, type OrganizationTeamDisplay, type OrganizationTeamMemberPage, type OrganizationTeamPage, type OrganizationWorkspaceSelection, type PersonalWorkspaceSelection, type ReceivedOrganizationInvitation, type ReceivedOrganizationInvitationPage, ServerAuthOptions, ServerInvitationAcceptance, ServerSignOutResult, ServerSocialIdTokenSession, type SocialIdTokenInput, type SocialIdTokenProvider, type UpdateOrganizationDisplayInput, type UpdateOrganizationTeamDisplayInput, type WorkspaceSelection, createServerAuth };
44
+ export { type AcceptInvitationInput, AuthError, type AuthErrorCode, type AuthErrorStatus, type AuthOrganization, type AuthSession, type AuthUser, type CanceledOrganizationInvitation, type CreateOrganizationMemberInvitationInput, type CreatedOrganizationMemberInvitation, type CreatedOrganizationTeam, type DriveCredential, type DriveCredentialInput, FinuevaServerAuth, type InvitationAcceptance, type OrganizationDisplay, type OrganizationInvitation, type OrganizationInvitationPage, type OrganizationMember, type OrganizationMemberPage, type OrganizationPage, type OrganizationPageInput, type OrganizationTeam, type OrganizationTeamDisplay, type OrganizationTeamMemberPage, type OrganizationTeamPage, type OrganizationWorkspaceSelection, type PersonalWorkspaceSelection, type ReceivedOrganizationInvitation, type ReceivedOrganizationInvitationPage, type ResentOrganizationInvitation, ServerAuthOptions, ServerInvitationAcceptance, ServerSignOutResult, ServerSocialIdTokenSession, type SocialIdTokenInput, type SocialIdTokenProvider, type UpdateOrganizationDisplayInput, type UpdateOrganizationTeamDisplayInput, type WorkspaceSelection, createServerAuth };
package/dist/server.js CHANGED
@@ -185,6 +185,7 @@ const messages = {
185
185
  invalid_invitation: "The invitation is invalid.",
186
186
  invitation_unavailable: "The invitation is not available.",
187
187
  invitation_recipient_mismatch: "The invitation cannot be accepted by this account.",
188
+ invitation_recovery_required: "Invitation administration requires separate recovery.",
188
189
  email_verification_required: "Email verification is required.",
189
190
  invalid_workspace_selection: "The workspace selection is invalid.",
190
191
  organization_context_too_large: "The organization context is too large.",
@@ -673,6 +674,20 @@ function normalizeCreatedOrganizationMemberInvitation(value) {
673
674
  expiresAt: responseDate(value.expiresAt)
674
675
  };
675
676
  }
677
+ function normalizeResentOrganizationInvitation(value, invitationId) {
678
+ const invitation = normalizeCreatedOrganizationMemberInvitation(value);
679
+ if (invitation.id !== invitationId) throw authorityUnavailable();
680
+ return invitation;
681
+ }
682
+ function normalizeOrganizationInvitationAdministrationInput(idempotencyKey) {
683
+ if (!boundedString(idempotencyKey, 255)) throw new AuthError("invalid_request", 400);
684
+ return { idempotencyKey };
685
+ }
686
+ function normalizeCanceledOrganizationInvitation(value) {
687
+ exact(value, ["status"]);
688
+ if (value.status !== "canceled") throw authorityUnavailable();
689
+ return { status: "canceled" };
690
+ }
676
691
  function normalizeOrganizations(value) {
677
692
  exact(value, ["nextCursor", "organizations"]);
678
693
  if (!Array.isArray(value.organizations) || value.organizations.length > 100) throw authorityUnavailable();
@@ -890,6 +905,10 @@ function fromOrganizationMutationAuthorityError(value, status, retryAfter) {
890
905
  if (status === 403 && code === "REQUEST_ORIGIN_DENIED") return new AuthError("request_origin_denied", 403);
891
906
  return fromOrganizationReadAuthorityError(value, status);
892
907
  }
908
+ function fromOrganizationInvitationAdministrationError(value, status, retryAfter) {
909
+ if (status === 422 && isObject(value) && value.code === "RECOVERY_REQUIRED") return new AuthError("invitation_recovery_required", 422);
910
+ return fromOrganizationMutationAuthorityError(value, status, retryAfter);
911
+ }
893
912
  function fromInvitationAuthorityError(value, status) {
894
913
  const code = isObject(value) && typeof value.code === "string" ? value.code : void 0;
895
914
  if (status === 401 && code === "UNAUTHORIZED") return new AuthError("authentication_required", 401);
@@ -1186,6 +1205,42 @@ function createServerAuth(options) {
1186
1205
  mapError: fromOrganizationMutationAuthorityError
1187
1206
  }));
1188
1207
  },
1208
+ async resendOrganizationInvitation(organizationId, invitationId, idempotencyKey) {
1209
+ if (authorization) throw new AuthError("authentication_required", 401);
1210
+ const organization = normalizeIdInput(organizationId);
1211
+ const invitation = normalizeIdInput(invitationId);
1212
+ const body = normalizeOrganizationInvitationAdministrationInput(idempotencyKey);
1213
+ return normalizeResentOrganizationInvitation(await requestJson(joinURL(authOrigin, "api/v1/auth/organizations", organization, "invitations", invitation, "resend"), {
1214
+ fetch: fetchImpl,
1215
+ timeoutMs,
1216
+ method: "POST",
1217
+ credentials,
1218
+ headers: {
1219
+ ...authHeaders(),
1220
+ "content-type": "application/json"
1221
+ },
1222
+ body,
1223
+ mapError: fromOrganizationInvitationAdministrationError
1224
+ }), invitation);
1225
+ },
1226
+ async cancelOrganizationInvitation(organizationId, invitationId, idempotencyKey) {
1227
+ if (authorization) throw new AuthError("authentication_required", 401);
1228
+ const organization = normalizeIdInput(organizationId);
1229
+ const invitation = normalizeIdInput(invitationId);
1230
+ const body = normalizeOrganizationInvitationAdministrationInput(idempotencyKey);
1231
+ return normalizeCanceledOrganizationInvitation(await requestJson(joinURL(authOrigin, "api/v1/auth/organizations", organization, "invitations", invitation, "cancel"), {
1232
+ fetch: fetchImpl,
1233
+ timeoutMs,
1234
+ method: "POST",
1235
+ credentials,
1236
+ headers: {
1237
+ ...authHeaders(),
1238
+ "content-type": "application/json"
1239
+ },
1240
+ body,
1241
+ mapError: fromOrganizationInvitationAdministrationError
1242
+ }));
1243
+ },
1189
1244
  async updateOrganizationDisplay(organizationId, input) {
1190
1245
  const id = normalizeIdInput(organizationId);
1191
1246
  const desired = normalizeOrganizationDisplayInput(input);
@@ -1,5 +1,5 @@
1
1
  //#region src/errors.d.ts
2
- type AuthErrorCode = "invalid_credentials" | "invalid_auth_input" | "account_conflict" | "password_reset_invalid" | "authentication_required" | "request_origin_denied" | "invalid_request" | "invalid_invitation" | "invitation_unavailable" | "invitation_recipient_mismatch" | "email_verification_required" | "invalid_workspace_selection" | "organization_context_too_large" | "credential_rate_limited" | "auth_request_rate_limited" | "state_conflict" | "access_denied" | "platform_target_unavailable" | "platform_quorum_unavailable" | "platform_recovery_required" | "identity_authority_unavailable";
2
+ type AuthErrorCode = "invalid_credentials" | "invalid_auth_input" | "account_conflict" | "password_reset_invalid" | "authentication_required" | "request_origin_denied" | "invalid_request" | "invalid_invitation" | "invitation_unavailable" | "invitation_recipient_mismatch" | "invitation_recovery_required" | "email_verification_required" | "invalid_workspace_selection" | "organization_context_too_large" | "credential_rate_limited" | "auth_request_rate_limited" | "state_conflict" | "access_denied" | "platform_target_unavailable" | "platform_quorum_unavailable" | "platform_recovery_required" | "identity_authority_unavailable";
3
3
  type AuthErrorStatus = 400 | 401 | 403 | 404 | 409 | 413 | 415 | 422 | 429 | 503;
4
4
  declare class AuthError extends Error {
5
5
  readonly code: AuthErrorCode;
@@ -86,6 +86,13 @@ interface CreatedOrganizationMemberInvitation {
86
86
  id: string;
87
87
  expiresAt: Date;
88
88
  }
89
+ interface ResentOrganizationInvitation {
90
+ id: string;
91
+ expiresAt: Date;
92
+ }
93
+ interface CanceledOrganizationInvitation {
94
+ status: "canceled";
95
+ }
89
96
  interface OrganizationTeamPage {
90
97
  teams: OrganizationTeam[];
91
98
  nextCursor: string | null;
@@ -194,6 +201,11 @@ interface DriveCredentialInput {
194
201
  forceRefresh?: boolean;
195
202
  }
196
203
  type PlatformRole = "administrator" | "investigator";
204
+ interface PlatformStatus {
205
+ eligible: boolean;
206
+ globalRevision: number;
207
+ roles: PlatformRole[];
208
+ }
197
209
  interface PlatformRoleChangeRecentAuthenticationInput {
198
210
  targetUserId: string;
199
211
  requestedRoles: PlatformRole[];
@@ -225,6 +237,34 @@ type PlatformRoleApproval = PlatformRoleFirstApproval | PlatformRoleApplied;
225
237
  interface RecentAuthenticationStatus {
226
238
  status: "pending" | "ready";
227
239
  }
240
+ type OrganizationManualRole = "owner" | "admin" | "member";
241
+ interface OrganizationManualRoleChangeInput {
242
+ requestedManualRole: OrganizationManualRole;
243
+ expectedActorAuthorityRevision: number;
244
+ expectedTargetAuthorityRevision: number;
245
+ idempotencyKey: string;
246
+ }
247
+ interface OrganizationManualRoleChangeResult {
248
+ status: "applied";
249
+ memberId: string;
250
+ manualRole: OrganizationManualRole;
251
+ role: OrganizationManualRole;
252
+ authorityRevision: number;
253
+ }
254
+ interface OrganizationOwnershipTransferInput {
255
+ outgoingOwnerMembershipId: string;
256
+ successorMembershipId: string;
257
+ outgoingOwnerRole: "admin" | "member";
258
+ expectedActorAuthorityRevision: number;
259
+ expectedOutgoingOwnerAuthorityRevision: number;
260
+ expectedSuccessorAuthorityRevision: number;
261
+ idempotencyKey: string;
262
+ }
263
+ interface OrganizationOwnershipTransferResult {
264
+ status: "applied";
265
+ outgoingOwner: Omit<OrganizationManualRoleChangeResult, "status">;
266
+ successor: Omit<OrganizationManualRoleChangeResult, "status">;
267
+ }
228
268
  interface AuthClientOptions {
229
269
  authOrigin: string;
230
270
  appOrigin: string;
@@ -235,7 +275,10 @@ interface AuthClientOptions {
235
275
  }
236
276
  interface FinuevaAuthClient {
237
277
  readonly _type: "browser";
278
+ getPlatformStatus(): Promise<PlatformStatus>;
238
279
  startPlatformRoleChangeRecentAuthentication(input: PlatformRoleChangeRecentAuthenticationInput): Promise<RecentAuthenticationStatus>;
280
+ startOrganizationOwnerRoleChangeRecentAuthentication(organizationId: string, targetMembershipId: string, input: OrganizationManualRoleChangeInput): Promise<RecentAuthenticationStatus>;
281
+ startOrganizationOwnershipTransferRecentAuthentication(organizationId: string, input: OrganizationOwnershipTransferInput): Promise<RecentAuthenticationStatus>;
239
282
  approveBootstrapPlatformRoleChange(input: PlatformBootstrapRoleChangeInput): Promise<PlatformRoleApproval>;
240
283
  addPasskey(): Promise<void>;
241
284
  verifyRecentAuthenticationWithPasskey(): Promise<void>;
@@ -246,8 +289,12 @@ interface FinuevaAuthClient {
246
289
  getOrganization(organizationId: string): Promise<AuthOrganization>;
247
290
  createOrganizationTeam(organizationId: string, name: string, idempotencyKey: string): Promise<CreatedOrganizationTeam>;
248
291
  createOrganizationMemberInvitation(organizationId: string, input: CreateOrganizationMemberInvitationInput): Promise<CreatedOrganizationMemberInvitation>;
292
+ resendOrganizationInvitation(organizationId: string, invitationId: string, idempotencyKey: string): Promise<ResentOrganizationInvitation>;
293
+ cancelOrganizationInvitation(organizationId: string, invitationId: string, idempotencyKey: string): Promise<CanceledOrganizationInvitation>;
249
294
  updateOrganizationDisplay(organizationId: string, input: UpdateOrganizationDisplayInput): Promise<OrganizationDisplay>;
250
295
  updateOrganizationTeamDisplay(organizationId: string, teamId: string, input: UpdateOrganizationTeamDisplayInput): Promise<OrganizationTeamDisplay>;
296
+ changeOrganizationMemberManualRole(organizationId: string, targetMembershipId: string, input: OrganizationManualRoleChangeInput): Promise<OrganizationManualRoleChangeResult>;
297
+ transferOrganizationOwnership(organizationId: string, input: OrganizationOwnershipTransferInput): Promise<OrganizationOwnershipTransferResult>;
251
298
  listOrganizationMembers(organizationId: string, input?: OrganizationPageInput): Promise<OrganizationMemberPage>;
252
299
  listOrganizationTeams(organizationId: string, input?: OrganizationPageInput): Promise<OrganizationTeamPage>;
253
300
  listOrganizationTeamMembers(organizationId: string, teamId: string, input?: OrganizationPageInput): Promise<OrganizationTeamMemberPage>;
@@ -269,4 +316,4 @@ interface FinuevaAuthClient {
269
316
  onSessionChange(listener: (session: AuthSession | null) => void): () => void;
270
317
  }
271
318
  //#endregion
272
- export { PlatformRole as A, SocialIdTokenInput as B, OrganizationTeam as C, OrganizationWorkspaceSelection as D, OrganizationTeamPage as E, ReceivedOrganizationInvitation as F, WorkspaceSelection as G, UpdateOrganizationDisplayInput as H, ReceivedOrganizationInvitationPage as I, AuthErrorStatus as J, AuthError as K, RecentAuthenticationStatus as L, PlatformRoleApproval as M, PlatformRoleChangeRecentAuthenticationInput as N, PersonalWorkspaceSelection as O, PlatformRoleFirstApproval as P, SendVerificationEmailInput as R, OrganizationPageInput as S, OrganizationTeamMemberPage as T, UpdateOrganizationTeamDisplayInput as U, SocialIdTokenProvider as V, UpdateProfileInput as W, OrganizationInvitation as _, AuthPersistenceDriver as a, OrganizationMemberPage as b, AuthUser as c, CreatedOrganizationTeam as d, DriveCredential as f, OrganizationDisplay as g, InvitationAcceptance as h, AuthPersistence as i, PlatformRoleApplied as j, PlatformBootstrapRoleChangeInput as k, CreateOrganizationMemberInvitationInput as l, FinuevaAuthClient as m, AuthClientOptions as n, AuthSession as o, DriveCredentialInput as p, AuthErrorCode as q, AuthOrganization as r, AuthState as s, AcceptInvitationInput as t, CreatedOrganizationMemberInvitation as u, OrganizationInvitationPage as v, OrganizationTeamDisplay as w, OrganizationPage as x, OrganizationMember as y, SignInInput as z };
319
+ export { PlatformBootstrapRoleChangeInput as A, ResentOrganizationInvitation as B, OrganizationPageInput as C, OrganizationTeamPage as D, OrganizationTeamMemberPage as E, PlatformRoleFirstApproval as F, UpdateOrganizationDisplayInput as G, SignInInput as H, PlatformStatus as I, WorkspaceSelection as J, UpdateOrganizationTeamDisplayInput as K, ReceivedOrganizationInvitation as L, PlatformRoleApplied as M, PlatformRoleApproval as N, OrganizationWorkspaceSelection as O, PlatformRoleChangeRecentAuthenticationInput as P, ReceivedOrganizationInvitationPage as R, OrganizationPage as S, OrganizationTeamDisplay as T, SocialIdTokenInput as U, SendVerificationEmailInput as V, SocialIdTokenProvider as W, AuthErrorCode as X, AuthError as Y, AuthErrorStatus as Z, OrganizationDisplay as _, AuthPersistenceDriver as a, OrganizationMember as b, AuthUser as c, CreatedOrganizationMemberInvitation as d, CreatedOrganizationTeam as f, InvitationAcceptance as g, FinuevaAuthClient as h, AuthPersistence as i, PlatformRole as j, PersonalWorkspaceSelection as k, CanceledOrganizationInvitation as l, DriveCredentialInput as m, AuthClientOptions as n, AuthSession as o, DriveCredential as p, UpdateProfileInput as q, AuthOrganization as r, AuthState as s, AcceptInvitationInput as t, CreateOrganizationMemberInvitationInput as u, OrganizationInvitation as v, OrganizationTeam as w, OrganizationMemberPage as x, OrganizationInvitationPage as y, RecentAuthenticationStatus as z };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@finueva/auth",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "description": "Public browser and server SDK for Finueva Auth",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -43,8 +43,8 @@
43
43
  "lint": "oxlint . --deny-warnings",
44
44
  "prepack": "pnpm build",
45
45
  "test": "pnpm test:unit && pnpm build && pnpm test:package",
46
- "test:composition": "node scripts/test-composition.mjs",
47
- "test:package": "node scripts/test-package.mjs",
46
+ "test:composition": "node scripts/test/composition/runner.mjs",
47
+ "test:package": "node scripts/test/package.mjs",
48
48
  "test:unit": "vitest run",
49
49
  "typecheck": "tsc --noEmit"
50
50
  },