@finueva/auth 0.14.0 → 0.15.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
@@ -254,6 +254,36 @@ return new Response(null, { status: 303, headers });
254
254
 
255
255
  Call `applyCookies()` after server `signOut()`, `acceptInvitation()`, and `signInWithSocialIdToken()`.
256
256
 
257
+ ## Resolve A Drive Recipient
258
+
259
+ Use the separate server client. Before lookup, Drive must check the caller's current permission to manage the item's shares. Recipient resolution does not grant file access.
260
+
261
+ ```ts
262
+ import { createDriveRecipientAuth } from "@finueva/auth/server";
263
+
264
+ const recipients = createDriveRecipientAuth({
265
+ authOrigin: "https://auth.finueva.com",
266
+ timeoutMs: 10_000,
267
+ });
268
+
269
+ const recipient = await recipients.resolveRecipient(
270
+ { email: "ada@example.com" },
271
+ clientAssertion, // Generate a new service assertion for this request.
272
+ );
273
+ ```
274
+
275
+ The result contains only `{ userId, name, email }`. The SDK trims and lowercases the exact email input. It accepts no additional input fields. Email has a 320-byte UTF-8 limit. The JSON request has a 1024-byte limit. The response has a 4096-byte limit. The canonical ID contains 1 to 128 URL-safe characters. The nonempty name has a 1024-byte UTF-8 limit. A response email must be normalized and match the requested email.
276
+
277
+ The caller generates one Ed25519 assertion with `typ: "finueva-drive-recipient-assertion+jwt"` and the registered JWK-thumbprint `kid`. Set `aud` to the exact Auth endpoint, such as `https://auth.finueva.com/api/v1/auth/drive-share-recipients/resolve`. Set `iss` and `sub` to the same registered service ID. Include the registered `environment`, `ver: 1`, `principal_type: "ecosystem-service"`, a new bounded `jti`, and `exp = iat + 60` in seconds. Auth permits 30 seconds of clock tolerance. A provisioning assertion cannot replace this assertion. The SDK accepts no keys. Auth verifies the assertion; the SDK checks only its compact syntax and the 16 KiB bearer-header limit.
278
+
279
+ This client sends no cookies or Origin. It follows no redirects, retains no recipient cache, and makes no automatic retries. It permits one active request per client and no queue. The default deadline is 10 seconds. `timeoutMs` accepts an integer from 1 to 60000. If a custom transport ignores abort, the client rejects new calls until that transport settles. A custom `fetch` must preserve the request's credential and redirect restrictions.
280
+
281
+ After a lost response or failure, use a new assertion for an explicit retry. Auth can consume an assertion even when the request fails. Missing or ineligible recipients return `AuthError` with `recipient_unavailable` and status 404. Exact invalid-request responses map to `invalid_request` with status 400 or 413. Invalid assertions map to `authentication_required` with status 401. Rate limits map to `auth_request_rate_limited` with status 429 and `retryAfterSeconds` from 1 to 60. Invalid responses, transport failures, and service failures map to `identity_authority_unavailable` with status 503. An overlapping call returns `state_conflict` with status 409. Error messages contain no request values.
282
+
283
+ Malformed assertion syntax fails locally with `invalid_request` and status 400. An exact `405 METHOD_NOT_ALLOWED` response with message `METHOD_NOT_ALLOWED` maps to `invalid_request` with status 405. Other non-contract errors fail closed with status 503. The SDK does not expose raw Auth error messages.
284
+
285
+ The factory and recipient types exist only in `@finueva/auth/server`. The cookie and session client from `createServerAuth()` does not expose recipient resolution.
286
+
257
287
  ## Device Quick Start
258
288
 
259
289
  Use the device flow for a CLI or native application that can open a system browser:
package/dist/device.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { C as OrganizationPage, D as OrganizationTeamMemberPage, E as OrganizationTeamDisplay, G as SocialIdTokenProvider, J as UpdateProfileInput, K as UpdateOrganizationDisplayInput, O as OrganizationTeamPage, Q as AuthErrorStatus, R as ReceivedOrganizationInvitation, S as OrganizationMemberPage, T as OrganizationTeam, W as SocialIdTokenInput, X as AuthError, Z as AuthErrorCode, _ as OrganizationDisplay, b as OrganizationMember, d as CreatedOrganizationMemberInvitation, f as CreatedOrganizationTeam, m as DriveCredentialInput, o as AuthSession, p as DriveCredential, q as UpdateOrganizationTeamDisplayInput, r as AuthOrganization, u as CreateOrganizationMemberInvitationInput, v as OrganizationInvitation, w as OrganizationPageInput, x as OrganizationMemberAuthority, y as OrganizationInvitationPage, z as ReceivedOrganizationInvitationPage } from "./types-Dsu3nEaN.js";
1
+ import { C as OrganizationPage, D as OrganizationTeamMemberPage, E as OrganizationTeamDisplay, G as SocialIdTokenProvider, J as UpdateProfileInput, K as UpdateOrganizationDisplayInput, O as OrganizationTeamPage, Q as AuthErrorStatus, R as ReceivedOrganizationInvitation, S as OrganizationMemberPage, T as OrganizationTeam, W as SocialIdTokenInput, X as AuthError, Z as AuthErrorCode, _ as OrganizationDisplay, b as OrganizationMember, d as CreatedOrganizationMemberInvitation, f as CreatedOrganizationTeam, m as DriveCredentialInput, o as AuthSession, p as DriveCredential, q as UpdateOrganizationTeamDisplayInput, r as AuthOrganization, u as CreateOrganizationMemberInvitationInput, v as OrganizationInvitation, w as OrganizationPageInput, x as OrganizationMemberAuthority, y as OrganizationInvitationPage, z as ReceivedOrganizationInvitationPage } from "./types-Dpy17xNC.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
@@ -447,6 +447,7 @@ const messages = {
447
447
  invalid_request: "The request is invalid.",
448
448
  invalid_invitation: "The invitation is invalid.",
449
449
  invitation_unavailable: "The invitation is not available.",
450
+ recipient_unavailable: "The recipient is unavailable.",
450
451
  invitation_recipient_mismatch: "The invitation cannot be accepted by this account.",
451
452
  invitation_recovery_required: "Invitation administration requires separate recovery.",
452
453
  email_verification_required: "Email verification is required.",
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { A as PersonalWorkspaceSelection, B as RecentAuthenticationStatus, C as OrganizationPage, D as OrganizationTeamMemberPage, E as OrganizationTeamDisplay, F as PlatformRoleChangeRecentAuthenticationInput, G as SocialIdTokenProvider, H as SendVerificationEmailInput, I as PlatformRoleFirstApproval, J as UpdateProfileInput, K as UpdateOrganizationDisplayInput, L as PlatformStatus, M as PlatformRole, N as PlatformRoleApplied, O as OrganizationTeamPage, P as PlatformRoleApproval, Q as AuthErrorStatus, R as ReceivedOrganizationInvitation, S as OrganizationMemberPage, T as OrganizationTeam, U as SignInInput, V as ResentOrganizationInvitation, W as SocialIdTokenInput, X as AuthError, Y as WorkspaceSelection, Z as AuthErrorCode, _ 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 PlatformBootstrapRoleChangeInput, k as OrganizationWorkspaceSelection, l as CanceledOrganizationInvitation, m as DriveCredentialInput, n as AuthClientOptions, o as AuthSession, p as DriveCredential, q as UpdateOrganizationTeamDisplayInput, r as AuthOrganization, s as AuthState, t as AcceptInvitationInput, u as CreateOrganizationMemberInvitationInput, v as OrganizationInvitation, w as OrganizationPageInput, x as OrganizationMemberAuthority, y as OrganizationInvitationPage, z as ReceivedOrganizationInvitationPage } from "./types-Dsu3nEaN.js";
1
+ import { A as PersonalWorkspaceSelection, B as RecentAuthenticationStatus, C as OrganizationPage, D as OrganizationTeamMemberPage, E as OrganizationTeamDisplay, F as PlatformRoleChangeRecentAuthenticationInput, G as SocialIdTokenProvider, H as SendVerificationEmailInput, I as PlatformRoleFirstApproval, J as UpdateProfileInput, K as UpdateOrganizationDisplayInput, L as PlatformStatus, M as PlatformRole, N as PlatformRoleApplied, O as OrganizationTeamPage, P as PlatformRoleApproval, Q as AuthErrorStatus, R as ReceivedOrganizationInvitation, S as OrganizationMemberPage, T as OrganizationTeam, U as SignInInput, V as ResentOrganizationInvitation, W as SocialIdTokenInput, X as AuthError, Y as WorkspaceSelection, Z as AuthErrorCode, _ 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 PlatformBootstrapRoleChangeInput, k as OrganizationWorkspaceSelection, l as CanceledOrganizationInvitation, m as DriveCredentialInput, n as AuthClientOptions, o as AuthSession, p as DriveCredential, q as UpdateOrganizationTeamDisplayInput, r as AuthOrganization, s as AuthState, t as AcceptInvitationInput, u as CreateOrganizationMemberInvitationInput, v as OrganizationInvitation, w as OrganizationPageInput, x as OrganizationMemberAuthority, y as OrganizationInvitationPage, z as ReceivedOrganizationInvitationPage } from "./types-Dpy17xNC.js";
2
2
  //#region src/client.d.ts
3
3
  declare function createAuthClient(options: AuthClientOptions): FinuevaAuthClient;
4
4
  //#endregion
package/dist/index.js CHANGED
@@ -2543,6 +2543,7 @@ const messages = {
2543
2543
  invalid_request: "The request is invalid.",
2544
2544
  invalid_invitation: "The invitation is invalid.",
2545
2545
  invitation_unavailable: "The invitation is not available.",
2546
+ recipient_unavailable: "The recipient is unavailable.",
2546
2547
  invitation_recipient_mismatch: "The invitation cannot be accepted by this account.",
2547
2548
  invitation_recovery_required: "Invitation administration requires separate recovery.",
2548
2549
  email_verification_required: "Email verification is required.",
package/dist/server.d.ts CHANGED
@@ -1,4 +1,24 @@
1
- import { A as PersonalWorkspaceSelection, C as OrganizationPage, D as OrganizationTeamMemberPage, E as OrganizationTeamDisplay, G as SocialIdTokenProvider, K as UpdateOrganizationDisplayInput, O as OrganizationTeamPage, Q as AuthErrorStatus, R as ReceivedOrganizationInvitation, S as OrganizationMemberPage, T as OrganizationTeam, V as ResentOrganizationInvitation, W as SocialIdTokenInput, X as AuthError, Y as WorkspaceSelection, Z as AuthErrorCode, _ as OrganizationDisplay, b as OrganizationMember, c as AuthUser, d as CreatedOrganizationMemberInvitation, f as CreatedOrganizationTeam, g as InvitationAcceptance, k as OrganizationWorkspaceSelection, l as CanceledOrganizationInvitation, m as DriveCredentialInput, o as AuthSession, p as DriveCredential, q as UpdateOrganizationTeamDisplayInput, r as AuthOrganization, t as AcceptInvitationInput, u as CreateOrganizationMemberInvitationInput, v as OrganizationInvitation, w as OrganizationPageInput, x as OrganizationMemberAuthority, y as OrganizationInvitationPage, z as ReceivedOrganizationInvitationPage } from "./types-Dsu3nEaN.js";
1
+ import { A as PersonalWorkspaceSelection, C as OrganizationPage, D as OrganizationTeamMemberPage, E as OrganizationTeamDisplay, G as SocialIdTokenProvider, K as UpdateOrganizationDisplayInput, O as OrganizationTeamPage, Q as AuthErrorStatus, R as ReceivedOrganizationInvitation, S as OrganizationMemberPage, T as OrganizationTeam, V as ResentOrganizationInvitation, W as SocialIdTokenInput, X as AuthError, Y as WorkspaceSelection, Z as AuthErrorCode, _ as OrganizationDisplay, b as OrganizationMember, c as AuthUser, d as CreatedOrganizationMemberInvitation, f as CreatedOrganizationTeam, g as InvitationAcceptance, k as OrganizationWorkspaceSelection, l as CanceledOrganizationInvitation, m as DriveCredentialInput, o as AuthSession, p as DriveCredential, q as UpdateOrganizationTeamDisplayInput, r as AuthOrganization, t as AcceptInvitationInput, u as CreateOrganizationMemberInvitationInput, v as OrganizationInvitation, w as OrganizationPageInput, x as OrganizationMemberAuthority, y as OrganizationInvitationPage, z as ReceivedOrganizationInvitationPage } from "./types-Dpy17xNC.js";
2
+ //#region src/drive-recipients.d.ts
3
+ interface DriveRecipientAuthOptions {
4
+ authOrigin: string;
5
+ fetch?: typeof globalThis.fetch;
6
+ timeoutMs?: number;
7
+ }
8
+ interface ResolveDriveRecipientInput {
9
+ email: string;
10
+ }
11
+ interface DriveRecipient {
12
+ userId: string;
13
+ name: string;
14
+ email: string;
15
+ }
16
+ interface FinuevaDriveRecipientAuth {
17
+ resolveRecipient(input: ResolveDriveRecipientInput, clientAssertion: string): Promise<DriveRecipient>;
18
+ }
19
+ /** Service authority only. The caller signs assertions; Auth verifies their claims and signature. */
20
+ declare function createDriveRecipientAuth(options: DriveRecipientAuthOptions): FinuevaDriveRecipientAuth;
21
+ //#endregion
2
22
  //#region src/provisioning.d.ts
3
23
  interface DriveProvisioningAuthOptions {
4
24
  authOrigin: string;
@@ -60,4 +80,4 @@ interface FinuevaServerAuth {
60
80
  }
61
81
  declare function createServerAuth(options: ServerAuthOptions): FinuevaServerAuth;
62
82
  //#endregion
63
- 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, type DriveProvisioningAuthOptions, type DriveProvisioningCredential, type FinuevaDriveProvisioningAuth, FinuevaServerAuth, type InvitationAcceptance, type OrganizationDisplay, type OrganizationInvitation, type OrganizationInvitationPage, type OrganizationMember, type OrganizationMemberAuthority, 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, createDriveProvisioningAuth, createServerAuth };
83
+ 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, type DriveProvisioningAuthOptions, type DriveProvisioningCredential, type DriveRecipient, type DriveRecipientAuthOptions, type FinuevaDriveProvisioningAuth, type FinuevaDriveRecipientAuth, FinuevaServerAuth, type InvitationAcceptance, type OrganizationDisplay, type OrganizationInvitation, type OrganizationInvitationPage, type OrganizationMember, type OrganizationMemberAuthority, type OrganizationMemberPage, type OrganizationPage, type OrganizationPageInput, type OrganizationTeam, type OrganizationTeamDisplay, type OrganizationTeamMemberPage, type OrganizationTeamPage, type OrganizationWorkspaceSelection, type PersonalWorkspaceSelection, type ReceivedOrganizationInvitation, type ReceivedOrganizationInvitationPage, type ResentOrganizationInvitation, type ResolveDriveRecipientInput, ServerAuthOptions, ServerInvitationAcceptance, ServerSignOutResult, ServerSocialIdTokenSession, type SocialIdTokenInput, type SocialIdTokenProvider, type UpdateOrganizationDisplayInput, type UpdateOrganizationTeamDisplayInput, type WorkspaceSelection, createDriveProvisioningAuth, createDriveRecipientAuth, createServerAuth };
package/dist/server.js CHANGED
@@ -184,6 +184,7 @@ const messages = {
184
184
  invalid_request: "The request is invalid.",
185
185
  invalid_invitation: "The invitation is invalid.",
186
186
  invitation_unavailable: "The invitation is not available.",
187
+ recipient_unavailable: "The recipient is unavailable.",
187
188
  invitation_recipient_mismatch: "The invitation cannot be accepted by this account.",
188
189
  invitation_recovery_required: "Invitation administration requires separate recovery.",
189
190
  email_verification_required: "Email verification is required.",
@@ -306,7 +307,7 @@ function normalizeIdInput(value) {
306
307
  return value;
307
308
  }
308
309
  function normalizeOrganization(value) {
309
- exact(value, [
310
+ exact$1(value, [
310
311
  "capabilities",
311
312
  "id",
312
313
  "lifecycle",
@@ -316,7 +317,7 @@ function normalizeOrganization(value) {
316
317
  "slug"
317
318
  ]);
318
319
  if (!isId(value.id) || !boundedString(value.name, 255) || !boundedString(value.slug, 255) || value.lifecycle !== "ordinary" && value.lifecycle !== "managed" || !role(value.role) || !isObject(value.capabilities)) throw authorityUnavailable();
319
- exact(value.capabilities, [
320
+ exact$1(value.capabilities, [
320
321
  "readInvitations",
321
322
  "readMembers",
322
323
  "readTeams"
@@ -377,7 +378,7 @@ function normalizeOrganizationTeamCreationInput(name, idempotencyKey) {
377
378
  };
378
379
  }
379
380
  function normalizeCreatedOrganizationTeam(value) {
380
- exact(value, ["id", "name"]);
381
+ exact$1(value, ["id", "name"]);
381
382
  if (!isId(value.id) || !boundedString(value.name, 255)) throw authorityUnavailable();
382
383
  return {
383
384
  id: value.id,
@@ -404,7 +405,7 @@ function normalizeOrganizationMemberInvitationCreationInput(value) {
404
405
  };
405
406
  }
406
407
  function normalizeCreatedOrganizationMemberInvitation(value) {
407
- exact(value, ["expiresAt", "id"]);
408
+ exact$1(value, ["expiresAt", "id"]);
408
409
  if (!isId(value.id)) throw authorityUnavailable();
409
410
  return {
410
411
  id: value.id,
@@ -421,12 +422,12 @@ function normalizeOrganizationInvitationAdministrationInput(idempotencyKey) {
421
422
  return { idempotencyKey };
422
423
  }
423
424
  function normalizeCanceledOrganizationInvitation(value) {
424
- exact(value, ["status"]);
425
+ exact$1(value, ["status"]);
425
426
  if (value.status !== "canceled") throw authorityUnavailable();
426
427
  return { status: "canceled" };
427
428
  }
428
429
  function normalizeOrganizations(value) {
429
- exact(value, ["nextCursor", "organizations"]);
430
+ exact$1(value, ["nextCursor", "organizations"]);
430
431
  if (!Array.isArray(value.organizations) || value.organizations.length > 100) throw authorityUnavailable();
431
432
  return {
432
433
  organizations: unique(value.organizations.map(normalizeOrganization), (item) => item.id),
@@ -434,11 +435,11 @@ function normalizeOrganizations(value) {
434
435
  };
435
436
  }
436
437
  function normalizeOrganizationMembers(value) {
437
- exact(value, ["members", "nextCursor"]);
438
+ exact$1(value, ["members", "nextCursor"]);
438
439
  if (!Array.isArray(value.members) || value.members.length > 100) throw authorityUnavailable();
439
440
  return {
440
441
  members: unique(value.members.map((member) => {
441
- exact(member, [
442
+ exact$1(member, [
442
443
  "email",
443
444
  "id",
444
445
  "image",
@@ -467,11 +468,11 @@ function normalizeOrganizationMembers(value) {
467
468
  };
468
469
  }
469
470
  function normalizeOrganizationTeams(value) {
470
- exact(value, ["nextCursor", "teams"]);
471
+ exact$1(value, ["nextCursor", "teams"]);
471
472
  if (!Array.isArray(value.teams) || value.teams.length > 100) throw authorityUnavailable();
472
473
  return {
473
474
  teams: unique(value.teams.map((team) => {
474
- exact(team, [
475
+ exact$1(team, [
475
476
  "category",
476
477
  "createdAt",
477
478
  "id",
@@ -489,7 +490,7 @@ function normalizeOrganizationTeams(value) {
489
490
  };
490
491
  }
491
492
  function normalizeOrganizationTeamMembers(value) {
492
- exact(value, ["memberIds", "nextCursor"]);
493
+ exact$1(value, ["memberIds", "nextCursor"]);
493
494
  if (!Array.isArray(value.memberIds) || value.memberIds.length > 100) throw authorityUnavailable();
494
495
  return {
495
496
  memberIds: unique(value.memberIds.map((item) => normalizeResponseId(item)), (item) => item),
@@ -497,7 +498,7 @@ function normalizeOrganizationTeamMembers(value) {
497
498
  };
498
499
  }
499
500
  function normalizeOrganizationInvitation(value) {
500
- exact(value, [
501
+ exact$1(value, [
501
502
  "createdAt",
502
503
  "email",
503
504
  "expiresAt",
@@ -508,7 +509,7 @@ function normalizeOrganizationInvitation(value) {
508
509
  "teamIds"
509
510
  ]);
510
511
  if (!isId(value.id) || typeof value.email !== "string" || value.email.length > 320 || !email.test(value.email) || !role(value.role) || value.status !== "pending" || !Array.isArray(value.teamIds) || value.teamIds.length > 100 || !isObject(value.inviter)) throw authorityUnavailable();
511
- exact(value.inviter, ["membershipId", "name"]);
512
+ exact$1(value.inviter, ["membershipId", "name"]);
512
513
  if (!isId(value.inviter.membershipId) || !boundedString(value.inviter.name, 255)) throw authorityUnavailable();
513
514
  return {
514
515
  id: value.id,
@@ -525,7 +526,7 @@ function normalizeOrganizationInvitation(value) {
525
526
  };
526
527
  }
527
528
  function normalizeOrganizationInvitations(value) {
528
- exact(value, ["invitations", "nextCursor"]);
529
+ exact$1(value, ["invitations", "nextCursor"]);
529
530
  if (!Array.isArray(value.invitations) || value.invitations.length > 100) throw authorityUnavailable();
530
531
  return {
531
532
  invitations: unique(value.invitations.map(normalizeOrganizationInvitation), (item) => item.id),
@@ -533,7 +534,7 @@ function normalizeOrganizationInvitations(value) {
533
534
  };
534
535
  }
535
536
  function normalizeReceivedInvitation(value) {
536
- exact(value, [
537
+ exact$1(value, [
537
538
  "expiresAt",
538
539
  "id",
539
540
  "inviterName",
@@ -542,7 +543,7 @@ function normalizeReceivedInvitation(value) {
542
543
  "teams"
543
544
  ]);
544
545
  if (!isId(value.id) || !boundedString(value.inviterName, 255) || !role(value.role) || !Array.isArray(value.teams) || value.teams.length > 100 || !isObject(value.organization)) throw authorityUnavailable();
545
- exact(value.organization, [
546
+ exact$1(value.organization, [
546
547
  "id",
547
548
  "logo",
548
549
  "name",
@@ -559,7 +560,7 @@ function normalizeReceivedInvitation(value) {
559
560
  },
560
561
  role: value.role,
561
562
  teams: unique(value.teams.map((team) => {
562
- exact(team, ["id", "name"]);
563
+ exact$1(team, ["id", "name"]);
563
564
  if (!isId(team.id) || !boundedString(team.name, 255)) throw authorityUnavailable();
564
565
  return {
565
566
  id: team.id,
@@ -571,7 +572,7 @@ function normalizeReceivedInvitation(value) {
571
572
  };
572
573
  }
573
574
  function normalizeReceivedInvitations(value) {
574
- exact(value, ["invitations", "nextCursor"]);
575
+ exact$1(value, ["invitations", "nextCursor"]);
575
576
  if (!Array.isArray(value.invitations) || value.invitations.length > 100) throw authorityUnavailable();
576
577
  return {
577
578
  invitations: unique(value.invitations.map(normalizeReceivedInvitation), (item) => item.id),
@@ -647,7 +648,7 @@ function fromOrganizationInvitationAdministrationError(value, status, retryAfter
647
648
  return fromOrganizationMutationAuthorityError(value, status, retryAfter);
648
649
  }
649
650
  function normalizeOrganizationMemberAuthority(value, organizationId, memberId) {
650
- exact(value, [
651
+ exact$1(value, [
651
652
  "organizationId",
652
653
  "actor",
653
654
  "target"
@@ -665,7 +666,7 @@ function normalizeOrganizationMemberAuthority(value, organizationId, memberId) {
665
666
  };
666
667
  }
667
668
  function normalizeOrganizationAuthorityMemberOutcome(value, includesStatus = false) {
668
- exact(value, [
669
+ exact$1(value, [
669
670
  "memberId",
670
671
  "manualRole",
671
672
  "role",
@@ -758,7 +759,7 @@ function toDate(value) {
758
759
  function isId(value) {
759
760
  return typeof value === "string" && id.test(value);
760
761
  }
761
- function exact(value, keys) {
762
+ function exact$1(value, keys) {
762
763
  if (!isObject(value) || Object.keys(value).sort().join(",") !== [...keys].sort().join(",")) throw authorityUnavailable();
763
764
  }
764
765
  function boundedString(value, maximum) {
@@ -802,6 +803,159 @@ function isObject(value) {
802
803
  return typeof value === "object" && value !== null && !Array.isArray(value);
803
804
  }
804
805
  //#endregion
806
+ //#region src/drive-recipients.ts
807
+ const encoder = new TextEncoder();
808
+ const emailPattern = /^(?!\.)(?!.*\.\.)([A-Za-z0-9_'+\-.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9-]*\.)+[A-Za-z]{2,}$/;
809
+ function validEmail(value) {
810
+ return typeof value === "string" && encoder.encode(value).byteLength <= 320 && emailPattern.test(value);
811
+ }
812
+ function exact(value, keys) {
813
+ return value !== null && typeof value === "object" && !Array.isArray(value) && Object.keys(value).length === keys.length && keys.every((key) => Object.hasOwn(value, key));
814
+ }
815
+ const failures = {
816
+ 400: [
817
+ "BAD_REQUEST",
818
+ "The recipient request is invalid.",
819
+ "invalid_request"
820
+ ],
821
+ 401: [
822
+ "UNAUTHORIZED",
823
+ "The recipient service assertion is invalid.",
824
+ "authentication_required"
825
+ ],
826
+ 404: [
827
+ "NOT_FOUND",
828
+ "The recipient is unavailable.",
829
+ "recipient_unavailable"
830
+ ],
831
+ 405: [
832
+ "METHOD_NOT_ALLOWED",
833
+ "METHOD_NOT_ALLOWED",
834
+ "invalid_request"
835
+ ],
836
+ 413: [
837
+ "REQUEST_TOO_LONG",
838
+ "The recipient request is too large.",
839
+ "invalid_request"
840
+ ],
841
+ 429: [
842
+ "TOO_MANY_REQUESTS",
843
+ "Recipient requests are temporarily limited.",
844
+ "auth_request_rate_limited"
845
+ ],
846
+ 503: [
847
+ "SERVICE_UNAVAILABLE",
848
+ "Recipient resolution is temporarily unavailable.",
849
+ "identity_authority_unavailable"
850
+ ]
851
+ };
852
+ function createDriveRecipientAuth(options) {
853
+ const url = joinURL(normalizeOrigin(options.authOrigin, "authOrigin"), "api/v1/auth/drive-share-recipients/resolve");
854
+ const fetchImpl = options.fetch ?? globalThis.fetch?.bind(globalThis);
855
+ if (typeof fetchImpl !== "function") throw new TypeError("fetch is required");
856
+ const timeoutMs = normalizeTimeout(options.timeoutMs);
857
+ let inFlight = false;
858
+ return { async resolveRecipient(input, clientAssertion) {
859
+ let body;
860
+ let email;
861
+ try {
862
+ if (!exact(input, ["email"]) || typeof input.email !== "string") throw new Error();
863
+ email = input.email.trim().toLowerCase();
864
+ if (!validEmail(email)) throw new Error();
865
+ body = JSON.stringify({ email });
866
+ } catch {
867
+ throw new AuthError("invalid_request", 400);
868
+ }
869
+ if (encoder.encode(body).byteLength > 1024) throw new AuthError("invalid_request", 413);
870
+ if (typeof clientAssertion !== "string" || clientAssertion.length + 7 > 16 * 1024 || !/^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$(?![\s\S])/.test(clientAssertion)) throw new AuthError("invalid_request", 400);
871
+ if (inFlight) throw new AuthError("state_conflict", 409);
872
+ inFlight = true;
873
+ const controller = new AbortController();
874
+ let reader;
875
+ let timer;
876
+ let authorityError;
877
+ try {
878
+ const deadline = new Promise((_, reject) => {
879
+ timer = setTimeout(() => {
880
+ reject(authorityUnavailable());
881
+ controller.abort();
882
+ reader?.cancel().catch(() => {});
883
+ }, timeoutMs);
884
+ });
885
+ const exchange = async () => {
886
+ const response = await fetchImpl(url, {
887
+ method: "POST",
888
+ headers: {
889
+ authorization: `Bearer ${clientAssertion}`,
890
+ accept: "application/json",
891
+ "content-type": "application/json"
892
+ },
893
+ body,
894
+ credentials: "omit",
895
+ cache: "no-store",
896
+ redirect: "manual",
897
+ signal: controller.signal
898
+ });
899
+ if (controller.signal.aborted) {
900
+ response.body?.cancel().catch(() => {});
901
+ throw authorityUnavailable();
902
+ }
903
+ if (!response.body) throw authorityUnavailable();
904
+ reader = response.body.getReader();
905
+ const contentType = response.headers.get("content-type")?.split(";", 1)[0]?.trim().toLowerCase();
906
+ const length = response.headers.get("content-length");
907
+ if (response.redirected || contentType !== "application/json" || length !== null && (!/^\d+$/.test(length) || Number(length) > 4096)) throw authorityUnavailable();
908
+ const bytes = new Uint8Array(4096);
909
+ let size = 0;
910
+ while (true) {
911
+ const chunk = await reader.read();
912
+ if (controller.signal.aborted) throw authorityUnavailable();
913
+ if (chunk.done) break;
914
+ if (size + chunk.value.byteLength > bytes.byteLength) throw authorityUnavailable();
915
+ bytes.set(chunk.value, size);
916
+ size += chunk.value.byteLength;
917
+ }
918
+ const value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes.subarray(0, size)));
919
+ if (response.status !== 200) {
920
+ const status = response.status;
921
+ const failure = failures[status];
922
+ if (!failure || !exact(value, ["code", "message"]) || value.code !== failure[0] || value.message !== failure[1]) throw authorityUnavailable();
923
+ let retryAfterSeconds;
924
+ if (status === 429) {
925
+ const retryAfter = response.headers.get("retry-after");
926
+ if (retryAfter === null || !/^(?:[1-9]|[1-5][0-9]|60)$/.test(retryAfter)) throw authorityUnavailable();
927
+ retryAfterSeconds = Number(retryAfter);
928
+ }
929
+ authorityError = new AuthError(failure[2], status, retryAfterSeconds);
930
+ throw authorityError;
931
+ }
932
+ if (!exact(value, ["recipient"]) || !exact(value.recipient, [
933
+ "userId",
934
+ "name",
935
+ "email"
936
+ ])) throw authorityUnavailable();
937
+ const recipient = value.recipient;
938
+ if (typeof recipient.userId !== "string" || !/^[A-Za-z0-9_-]{1,128}$(?![\s\S])/.test(recipient.userId) || typeof recipient.name !== "string" || recipient.name.length === 0 || encoder.encode(recipient.name).byteLength > 1024 || !validEmail(recipient.email) || recipient.email !== recipient.email.trim().toLowerCase() || recipient.email !== email) throw authorityUnavailable();
939
+ return {
940
+ userId: recipient.userId,
941
+ name: recipient.name,
942
+ email: recipient.email
943
+ };
944
+ };
945
+ return await Promise.race([exchange().finally(() => {
946
+ inFlight = false;
947
+ }), deadline]);
948
+ } catch (error) {
949
+ if (authorityError && error === authorityError) throw authorityError;
950
+ throw authorityUnavailable();
951
+ } finally {
952
+ clearTimeout(timer);
953
+ controller.abort();
954
+ reader?.cancel().catch(() => {});
955
+ }
956
+ } };
957
+ }
958
+ //#endregion
805
959
  //#region src/provisioning.ts
806
960
  const MAX_BYTES = 16 * 1024;
807
961
  const compactJwt = /^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/;
@@ -1494,7 +1648,11 @@ function createServerAuth(options) {
1494
1648
  response = await fetchImpl(joinURL(authOrigin, "api/v1/auth/sign-out"), {
1495
1649
  method: "POST",
1496
1650
  credentials,
1497
- headers: authHeaders(),
1651
+ headers: {
1652
+ ...authHeaders(),
1653
+ "content-type": "application/json; charset=utf-8"
1654
+ },
1655
+ body: "{}",
1498
1656
  redirect: "manual",
1499
1657
  signal: AbortSignal.timeout(timeoutMs)
1500
1658
  });
@@ -1535,4 +1693,4 @@ function authAuthorizationHeader(value) {
1535
1693
  return value;
1536
1694
  }
1537
1695
  //#endregion
1538
- export { AuthError, createDriveProvisioningAuth, createServerAuth };
1696
+ export { AuthError, createDriveProvisioningAuth, createDriveRecipientAuth, createServerAuth };
@@ -1,6 +1,6 @@
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" | "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
- type AuthErrorStatus = 400 | 401 | 403 | 404 | 409 | 413 | 415 | 422 | 429 | 503;
2
+ type AuthErrorCode = "invalid_credentials" | "invalid_auth_input" | "account_conflict" | "password_reset_invalid" | "authentication_required" | "request_origin_denied" | "invalid_request" | "invalid_invitation" | "invitation_unavailable" | "recipient_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
+ type AuthErrorStatus = 400 | 401 | 403 | 404 | 405 | 409 | 413 | 415 | 422 | 429 | 503;
4
4
  declare class AuthError extends Error {
5
5
  readonly code: AuthErrorCode;
6
6
  readonly status: AuthErrorStatus;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@finueva/auth",
3
- "version": "0.14.0",
3
+ "version": "0.15.0",
4
4
  "description": "Public browser and server SDK for Finueva Auth",
5
5
  "type": "module",
6
6
  "sideEffects": false,