@finueva/auth 0.15.0 → 0.16.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
@@ -32,6 +32,22 @@ Do not reuse configuration from another application or environment.
32
32
  | Server or SSR handler | `@finueva/auth/server` | Incoming Auth cookie or bearer | Once per request |
33
33
  | CLI or native application | `@finueva/auth/device` | Device bearer session | Once per process |
34
34
 
35
+ ## Private Identity Resolution Candidate
36
+
37
+ For private server-to-server identity resolution, the next package candidate exports `createIdentityResolverAuth` from `@finueva/auth/server`. This helper is not in published `0.15.0`.
38
+
39
+ ```ts
40
+ import { createIdentityResolverAuth } from "@finueva/auth/server";
41
+
42
+ const identities = createIdentityResolverAuth({ authOrigin: "https://auth.example.test" });
43
+
44
+ async function resolvePrivateIdentity(identifier: string, clientAssertion: string) {
45
+ return identities.resolveIdentity({ identifier }, clientAssertion);
46
+ }
47
+ ```
48
+
49
+ The approved server signs a fresh assertion with its registered key and explicit `identities:resolve` capability. The method returns only `{ userId }` or null for one exact username or email. Keep the result private. It grants no product authority. See the [server guide](https://github.com/Ifkafin/auth/blob/main/apps/developer/docs/sdk/server.md#resolve-a-private-identity) for the assertion contract and limits.
50
+
35
51
  ## Browser Quick Start
36
52
 
37
53
  Create the client with the exact Auth and application origins:
package/dist/server.d.ts CHANGED
@@ -1,4 +1,22 @@
1
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/identity-resolution.d.ts
3
+ interface IdentityResolverAuthOptions {
4
+ authOrigin: string;
5
+ fetch?: typeof globalThis.fetch;
6
+ timeoutMs?: number;
7
+ }
8
+ interface ResolveIdentityInput {
9
+ identifier: string;
10
+ }
11
+ interface ResolvedIdentity {
12
+ userId: string;
13
+ }
14
+ interface FinuevaIdentityResolverAuth {
15
+ resolveIdentity(input: ResolveIdentityInput, clientAssertion: string): Promise<ResolvedIdentity | null>;
16
+ }
17
+ /** Server-only identity resolution. Keep results private; they grant no product authority. */
18
+ declare function createIdentityResolverAuth(options: IdentityResolverAuthOptions): FinuevaIdentityResolverAuth;
19
+ //#endregion
2
20
  //#region src/drive-recipients.d.ts
3
21
  interface DriveRecipientAuthOptions {
4
22
  authOrigin: string;
@@ -80,4 +98,4 @@ interface FinuevaServerAuth {
80
98
  }
81
99
  declare function createServerAuth(options: ServerAuthOptions): FinuevaServerAuth;
82
100
  //#endregion
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 };
101
+ 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, type FinuevaIdentityResolverAuth, FinuevaServerAuth, type IdentityResolverAuthOptions, 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, type ResolveIdentityInput, type ResolvedIdentity, ServerAuthOptions, ServerInvitationAcceptance, ServerSignOutResult, ServerSocialIdTokenSession, type SocialIdTokenInput, type SocialIdTokenProvider, type UpdateOrganizationDisplayInput, type UpdateOrganizationTeamDisplayInput, type WorkspaceSelection, createDriveProvisioningAuth, createDriveRecipientAuth, createIdentityResolverAuth, createServerAuth };
package/dist/server.js CHANGED
@@ -307,7 +307,7 @@ function normalizeIdInput(value) {
307
307
  return value;
308
308
  }
309
309
  function normalizeOrganization(value) {
310
- exact$1(value, [
310
+ exact$2(value, [
311
311
  "capabilities",
312
312
  "id",
313
313
  "lifecycle",
@@ -317,7 +317,7 @@ function normalizeOrganization(value) {
317
317
  "slug"
318
318
  ]);
319
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();
320
- exact$1(value.capabilities, [
320
+ exact$2(value.capabilities, [
321
321
  "readInvitations",
322
322
  "readMembers",
323
323
  "readTeams"
@@ -378,7 +378,7 @@ function normalizeOrganizationTeamCreationInput(name, idempotencyKey) {
378
378
  };
379
379
  }
380
380
  function normalizeCreatedOrganizationTeam(value) {
381
- exact$1(value, ["id", "name"]);
381
+ exact$2(value, ["id", "name"]);
382
382
  if (!isId(value.id) || !boundedString(value.name, 255)) throw authorityUnavailable();
383
383
  return {
384
384
  id: value.id,
@@ -405,7 +405,7 @@ function normalizeOrganizationMemberInvitationCreationInput(value) {
405
405
  };
406
406
  }
407
407
  function normalizeCreatedOrganizationMemberInvitation(value) {
408
- exact$1(value, ["expiresAt", "id"]);
408
+ exact$2(value, ["expiresAt", "id"]);
409
409
  if (!isId(value.id)) throw authorityUnavailable();
410
410
  return {
411
411
  id: value.id,
@@ -422,12 +422,12 @@ function normalizeOrganizationInvitationAdministrationInput(idempotencyKey) {
422
422
  return { idempotencyKey };
423
423
  }
424
424
  function normalizeCanceledOrganizationInvitation(value) {
425
- exact$1(value, ["status"]);
425
+ exact$2(value, ["status"]);
426
426
  if (value.status !== "canceled") throw authorityUnavailable();
427
427
  return { status: "canceled" };
428
428
  }
429
429
  function normalizeOrganizations(value) {
430
- exact$1(value, ["nextCursor", "organizations"]);
430
+ exact$2(value, ["nextCursor", "organizations"]);
431
431
  if (!Array.isArray(value.organizations) || value.organizations.length > 100) throw authorityUnavailable();
432
432
  return {
433
433
  organizations: unique(value.organizations.map(normalizeOrganization), (item) => item.id),
@@ -435,11 +435,11 @@ function normalizeOrganizations(value) {
435
435
  };
436
436
  }
437
437
  function normalizeOrganizationMembers(value) {
438
- exact$1(value, ["members", "nextCursor"]);
438
+ exact$2(value, ["members", "nextCursor"]);
439
439
  if (!Array.isArray(value.members) || value.members.length > 100) throw authorityUnavailable();
440
440
  return {
441
441
  members: unique(value.members.map((member) => {
442
- exact$1(member, [
442
+ exact$2(member, [
443
443
  "email",
444
444
  "id",
445
445
  "image",
@@ -468,11 +468,11 @@ function normalizeOrganizationMembers(value) {
468
468
  };
469
469
  }
470
470
  function normalizeOrganizationTeams(value) {
471
- exact$1(value, ["nextCursor", "teams"]);
471
+ exact$2(value, ["nextCursor", "teams"]);
472
472
  if (!Array.isArray(value.teams) || value.teams.length > 100) throw authorityUnavailable();
473
473
  return {
474
474
  teams: unique(value.teams.map((team) => {
475
- exact$1(team, [
475
+ exact$2(team, [
476
476
  "category",
477
477
  "createdAt",
478
478
  "id",
@@ -490,7 +490,7 @@ function normalizeOrganizationTeams(value) {
490
490
  };
491
491
  }
492
492
  function normalizeOrganizationTeamMembers(value) {
493
- exact$1(value, ["memberIds", "nextCursor"]);
493
+ exact$2(value, ["memberIds", "nextCursor"]);
494
494
  if (!Array.isArray(value.memberIds) || value.memberIds.length > 100) throw authorityUnavailable();
495
495
  return {
496
496
  memberIds: unique(value.memberIds.map((item) => normalizeResponseId(item)), (item) => item),
@@ -498,7 +498,7 @@ function normalizeOrganizationTeamMembers(value) {
498
498
  };
499
499
  }
500
500
  function normalizeOrganizationInvitation(value) {
501
- exact$1(value, [
501
+ exact$2(value, [
502
502
  "createdAt",
503
503
  "email",
504
504
  "expiresAt",
@@ -509,7 +509,7 @@ function normalizeOrganizationInvitation(value) {
509
509
  "teamIds"
510
510
  ]);
511
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();
512
- exact$1(value.inviter, ["membershipId", "name"]);
512
+ exact$2(value.inviter, ["membershipId", "name"]);
513
513
  if (!isId(value.inviter.membershipId) || !boundedString(value.inviter.name, 255)) throw authorityUnavailable();
514
514
  return {
515
515
  id: value.id,
@@ -526,7 +526,7 @@ function normalizeOrganizationInvitation(value) {
526
526
  };
527
527
  }
528
528
  function normalizeOrganizationInvitations(value) {
529
- exact$1(value, ["invitations", "nextCursor"]);
529
+ exact$2(value, ["invitations", "nextCursor"]);
530
530
  if (!Array.isArray(value.invitations) || value.invitations.length > 100) throw authorityUnavailable();
531
531
  return {
532
532
  invitations: unique(value.invitations.map(normalizeOrganizationInvitation), (item) => item.id),
@@ -534,7 +534,7 @@ function normalizeOrganizationInvitations(value) {
534
534
  };
535
535
  }
536
536
  function normalizeReceivedInvitation(value) {
537
- exact$1(value, [
537
+ exact$2(value, [
538
538
  "expiresAt",
539
539
  "id",
540
540
  "inviterName",
@@ -543,7 +543,7 @@ function normalizeReceivedInvitation(value) {
543
543
  "teams"
544
544
  ]);
545
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();
546
- exact$1(value.organization, [
546
+ exact$2(value.organization, [
547
547
  "id",
548
548
  "logo",
549
549
  "name",
@@ -560,7 +560,7 @@ function normalizeReceivedInvitation(value) {
560
560
  },
561
561
  role: value.role,
562
562
  teams: unique(value.teams.map((team) => {
563
- exact$1(team, ["id", "name"]);
563
+ exact$2(team, ["id", "name"]);
564
564
  if (!isId(team.id) || !boundedString(team.name, 255)) throw authorityUnavailable();
565
565
  return {
566
566
  id: team.id,
@@ -572,7 +572,7 @@ function normalizeReceivedInvitation(value) {
572
572
  };
573
573
  }
574
574
  function normalizeReceivedInvitations(value) {
575
- exact$1(value, ["invitations", "nextCursor"]);
575
+ exact$2(value, ["invitations", "nextCursor"]);
576
576
  if (!Array.isArray(value.invitations) || value.invitations.length > 100) throw authorityUnavailable();
577
577
  return {
578
578
  invitations: unique(value.invitations.map(normalizeReceivedInvitation), (item) => item.id),
@@ -648,7 +648,7 @@ function fromOrganizationInvitationAdministrationError(value, status, retryAfter
648
648
  return fromOrganizationMutationAuthorityError(value, status, retryAfter);
649
649
  }
650
650
  function normalizeOrganizationMemberAuthority(value, organizationId, memberId) {
651
- exact$1(value, [
651
+ exact$2(value, [
652
652
  "organizationId",
653
653
  "actor",
654
654
  "target"
@@ -666,7 +666,7 @@ function normalizeOrganizationMemberAuthority(value, organizationId, memberId) {
666
666
  };
667
667
  }
668
668
  function normalizeOrganizationAuthorityMemberOutcome(value, includesStatus = false) {
669
- exact$1(value, [
669
+ exact$2(value, [
670
670
  "memberId",
671
671
  "manualRole",
672
672
  "role",
@@ -759,7 +759,7 @@ function toDate(value) {
759
759
  function isId(value) {
760
760
  return typeof value === "string" && id.test(value);
761
761
  }
762
- function exact$1(value, keys) {
762
+ function exact$2(value, keys) {
763
763
  if (!isObject(value) || Object.keys(value).sort().join(",") !== [...keys].sort().join(",")) throw authorityUnavailable();
764
764
  }
765
765
  function boundedString(value, maximum) {
@@ -803,6 +803,98 @@ function isObject(value) {
803
803
  return typeof value === "object" && value !== null && !Array.isArray(value);
804
804
  }
805
805
  //#endregion
806
+ //#region src/identity-resolution.ts
807
+ const emailPattern$1 = /^(?!\.)(?!.*\.\.)([A-Za-z0-9_'+\-.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9-]*\.)+[A-Za-z]{2,}$(?![\s\S])/;
808
+ function exact$1(value, keys) {
809
+ return value !== null && typeof value === "object" && !Array.isArray(value) && Object.keys(value).length === keys.length && keys.every((key) => Object.hasOwn(value, key));
810
+ }
811
+ function createIdentityResolverAuth(options) {
812
+ const url = joinURL(normalizeOrigin(options.authOrigin, "authOrigin"), "api/v1/auth/identities/resolve");
813
+ const fetchImpl = options.fetch ?? globalThis.fetch?.bind(globalThis);
814
+ if (typeof fetchImpl !== "function") throw new TypeError("fetch is required");
815
+ const timeoutMs = normalizeTimeout(options.timeoutMs);
816
+ let inFlight = false;
817
+ return { async resolveIdentity(input, clientAssertion) {
818
+ if (!exact$1(input, ["identifier"]) || typeof input.identifier !== "string" || input.identifier.length > 324) throw new AuthError("invalid_request", 400);
819
+ const identifier = input.identifier.trim().toLowerCase();
820
+ if (!(identifier.length <= 320 && emailPattern$1.test(identifier)) && !/^[a-z0-9_.]{3,30}$(?![\s\S])/.test(identifier)) throw new AuthError("invalid_request", 400);
821
+ 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);
822
+ if (inFlight) throw new AuthError("state_conflict", 409);
823
+ inFlight = true;
824
+ const controller = new AbortController();
825
+ let reader;
826
+ let timer;
827
+ let mappedError;
828
+ try {
829
+ const deadline = new Promise((_, reject) => {
830
+ timer = setTimeout(() => {
831
+ reject(authorityUnavailable());
832
+ controller.abort();
833
+ reader?.cancel().catch(() => {});
834
+ }, timeoutMs);
835
+ });
836
+ const exchange = async () => {
837
+ const response = await fetchImpl(url, {
838
+ method: "POST",
839
+ headers: {
840
+ authorization: `Bearer ${clientAssertion}`,
841
+ accept: "application/json",
842
+ "content-type": "application/json"
843
+ },
844
+ body: JSON.stringify({ identifier }),
845
+ credentials: "omit",
846
+ cache: "no-store",
847
+ redirect: "manual",
848
+ signal: controller.signal
849
+ });
850
+ if (controller.signal.aborted) {
851
+ response.body?.cancel().catch(() => {});
852
+ throw authorityUnavailable();
853
+ }
854
+ if (!response.body) throw authorityUnavailable();
855
+ reader = response.body.getReader();
856
+ const length = response.headers.get("content-length");
857
+ if (response.redirected || response.headers.get("content-type")?.split(";", 1)[0]?.trim().toLowerCase() !== "application/json" || length !== null && (!/^\d+$/.test(length) || Number(length) > 1024)) throw authorityUnavailable();
858
+ const bytes = new Uint8Array(1024);
859
+ let size = 0;
860
+ while (true) {
861
+ const chunk = await reader.read();
862
+ if (controller.signal.aborted) throw authorityUnavailable();
863
+ if (chunk.done) break;
864
+ if (size + chunk.value.byteLength > bytes.length) throw authorityUnavailable();
865
+ bytes.set(chunk.value, size);
866
+ size += chunk.value.byteLength;
867
+ }
868
+ const value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes.subarray(0, size)));
869
+ if (response.status !== 200) {
870
+ if (!exact$1(value, ["code", "message"])) throw authorityUnavailable();
871
+ if (response.status === 401 && value.code === "UNAUTHORIZED" && value.message === "The identity resolution assertion is invalid.") mappedError = new AuthError("authentication_required", 401);
872
+ if (response.status === 429 && value.code === "TOO_MANY_REQUESTS" && value.message === "Identity resolution requests are temporarily limited.") {
873
+ const retryAfter = response.headers.get("retry-after");
874
+ if (retryAfter === null || !/^(?:[1-9]|[1-5][0-9]|60)$/.test(retryAfter)) throw authorityUnavailable();
875
+ mappedError = new AuthError("auth_request_rate_limited", 429, Number(retryAfter));
876
+ }
877
+ throw mappedError ?? authorityUnavailable();
878
+ }
879
+ if (!exact$1(value, ["identity"])) throw authorityUnavailable();
880
+ if (value.identity === null) return null;
881
+ if (!exact$1(value.identity, ["userId"]) || typeof value.identity.userId !== "string" || !/^[A-Za-z0-9_-]{1,128}$(?![\s\S])/.test(value.identity.userId)) throw authorityUnavailable();
882
+ return { userId: value.identity.userId };
883
+ };
884
+ return await Promise.race([exchange().finally(() => {
885
+ inFlight = false;
886
+ }), deadline]);
887
+ } catch (error) {
888
+ if (mappedError && mappedError === error) throw mappedError;
889
+ throw authorityUnavailable();
890
+ } finally {
891
+ clearTimeout(timer);
892
+ controller.abort();
893
+ reader?.cancel().catch(() => {});
894
+ }
895
+ } };
896
+ }
897
+ //#endregion
806
898
  //#region src/drive-recipients.ts
807
899
  const encoder = new TextEncoder();
808
900
  const emailPattern = /^(?!\.)(?!.*\.\.)([A-Za-z0-9_'+\-.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9-]*\.)+[A-Za-z]{2,}$/;
@@ -1693,4 +1785,4 @@ function authAuthorizationHeader(value) {
1693
1785
  return value;
1694
1786
  }
1695
1787
  //#endregion
1696
- export { AuthError, createDriveProvisioningAuth, createDriveRecipientAuth, createServerAuth };
1788
+ export { AuthError, createDriveProvisioningAuth, createDriveRecipientAuth, createIdentityResolverAuth, createServerAuth };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@finueva/auth",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "description": "Public browser and server SDK for Finueva Auth",
5
5
  "type": "module",
6
6
  "sideEffects": false,