@finueva/auth

中文文档
0.35.0发布于 2026/11/14MITTypeScriptES Module零依赖近 30 天下载 4575

Public browser and server SDK for Finueva Auth

原始使用文档
文档目录21 节
  1. Install
  2. Choose A Client
  3. Private Identity Resolution Candidate
  4. Browser Quick Start
  5. Common Tasks
  6. Create An Organization Invitation
  7. Remove An Organization Member
  8. Manage Team Membership
  9. Perform Recent Authentication
  10. Request A Drive Credential
  11. Server Quick Start
  12. Recover A Google Account Conflict
  13. Resolve A Drive Recipient
  14. Device Quick Start
  15. Handle Errors
  16. Security Checklist
  17. Current Limits
  18. Package Exports
  19. Documentation
  20. Organization presence authorization candidate
  21. Organization admission candidate

Finueva Auth SDK for TypeScript

Use @finueva/auth to add Finueva sign-in, organization access, invitations, and Drive credentials to a browser, server, CLI, or native application.

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.

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.

Install

pnpm add @finueva/auth

For a browser or server application, ask your Auth operator for:

  • the Auth origin for your environment;
  • your registered application origin.

For a CLI or native application, ask for:

  • the Auth origin for your environment;
  • a registered device client ID.

Do not reuse configuration from another application or environment.

Choose A Client

RuntimeImportSession authorityCreate it
Browser@finueva/authAuth HTTP-only cookieOnce for the application
Server or SSR handler@finueva/auth/serverIncoming Auth cookie or bearerOnce per request
CLI or native application@finueva/auth/deviceDevice bearer sessionOnce per process

Private Identity Resolution Candidate

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.

import { createIdentityResolverAuth } from "@finueva/auth/server";

const identities = createIdentityResolverAuth({ authOrigin: "https://auth.example.test" });

async function resolvePrivateIdentity(identifier: string, clientAssertion: string) {
  return identities.resolveIdentity({ identifier }, clientAssertion);
}

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 for the assertion contract and limits.

Browser Quick Start

Create the client with the exact Auth and application origins:

import { AuthError, createAuthClient } from "@finueva/auth";

const auth = createAuthClient({
  authOrigin: "https://auth.finueva.com",
  appOrigin: window.location.origin,
});

Read the current session:

const session = await auth.getSession();

if (session) {
  console.log(`Signed in as ${session.user.email}`);
}

Sign in with email and password:

try {
  const session = await auth.signIn({
    email: "[email protected]",
    password: "correct-horse-battery-staple",
  });

  console.log(`Welcome ${session.user.name}`);
} catch (error) {
  if (error instanceof AuthError && error.code === "invalid_credentials") {
    showInvalidCredentials();
  } else {
    throw error;
  }
}

The browser owns the session cookie. The SDK does not expose or persist it.

Common Tasks

TaskBrowserServerDevice
Create an accountsignUp()Not availablesignUpWithEmail()
Read the current sessiongetSession()getSession()getSession()
Observe session stateonAuthStateChange()Not availableNot available
Update a profileupdateProfile()Not availableupdateProfile()
Change a passwordchangePassword()Not availablechangePassword()
Complete a second-factor sign-inverifyTwoFactor()Not availableDevice authorization
Manage two-step verificationstartTwoFactorEnrollment(), confirmTwoFactorEnrollment(), disableTwoFactor(), regenerateTwoFactorBackupCodes()Not availableNot available
Read two-step verification stategetTwoFactorStatus()Not availablegetTwoFactorStatus()
Reset a passwordrequestPasswordReset(), resetPassword()Not availableNot available
Verify an emailsendVerificationEmail()Not availableNot available
List organizationslistOrganizations()listOrganizations()listOrganizations()
Read member authoritygetOrganizationMemberAuthority()getOrganizationMemberAuthority()getOrganizationMemberAuthority()
Create an ordinary organizationcreateOrganization()createOrganization()createOrganization()
Create an ordinary teamcreateOrganizationTeam()createOrganizationTeam()createOrganizationTeam()
Invite an ordinary membercreateOrganizationMemberInvitation()createOrganizationMemberInvitation()createOrganizationMemberInvitation()
Resend a member invitationresendOrganizationInvitation()resendOrganizationInvitation() (cookie)Not available
Cancel a member invitationcancelOrganizationInvitation()cancelOrganizationInvitation() (cookie)Not available
Remove an ordinary memberremoveOrganizationMember()removeOrganizationMember() (cookie)Not available
Leave an organizationleaveOrganization()leaveOrganization() (cookie)Not available
Add a member to a teamaddOrganizationTeamMember()addOrganizationTeamMember() (cookie)Not available
Remove a member from a teamremoveOrganizationTeamMember()removeOrganizationTeamMember() (cookie)Not available
Delete an ordinary teamdeleteOrganizationTeam()deleteOrganizationTeam() (cookie)Not available
Accept an invitationacceptInvitation()acceptInvitation()Not available
Decline an invitationdeclineInvitation()declineInvitation()Not available
Enroll a passkeyaddPasskey()Not availableNot available
Perform recent authenticationPlatform and organization action methodsNot availableNot available
Change manual roles or ownersOrganization authority methodsNot availableNot available
Read current platform rolesgetPlatformStatus()Not availableNot available
Request Drive accessgetDriveCredential()getDriveCredential()getDriveCredential()
List signed-in sessionslistSessions()Not availablelistSessions()
End other sessionsrevokeSession(), revokeOtherSessions()Not availablerevokeSession(), revokeOtherSessions()
Start an Auth-Origin ceremonycreateCeremonyHandoff()Not availablecreateCeremonyHandoff()
List and rename passkeyslistPasskeys(), renamePasskey()Not availablelistPasskeys(), renamePasskey()
End the current sessionsignOut()signOut()revokeCurrentDevice()

Create An Organization Invitation

Use one new idempotency key for each invitation that you intend to create:

const invitation = await auth.createOrganizationMemberInvitation("org_01J8R7M6N5P4Q3S2T1", {
  email: "[email protected]",
  teamIds: ["team_01J8R7M6N5P4Q3S2T1"],
  idempotencyKey: crypto.randomUUID(),
});

console.log(invitation.id, invitation.expiresAt);

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.

Manual owners and manual administrators can resend or cancel the exact pending invitation through browser-cookie or server-cookie authority:

const resent = await auth.resendOrganizationInvitation(organizationId, invitation.id, crypto.randomUUID());
const canceled = await auth.cancelOrganizationInvitation(organizationId, invitation.id, crypto.randomUUID());

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.

Remove An Organization Member

Manual owners and manual administrators remove one other member. Every current member can leave. Both operations use browser-cookie or server-cookie authority:

const removed = await auth.removeOrganizationMember(organizationId, memberId, crypto.randomUUID());
const left = await auth.leaveOrganization(organizationId, crypto.randomUUID());

An owner target is refused. Transfer ownership first. Repeat a removal with the same key only after a lost response; a membership that is already gone under that key replays the same result. A bearer-backed server client rejects both methods before network I/O.

Manage Team Membership

Manual owners and manual administrators add a current organization member to an ordinary team, remove a member from a team, or delete an ordinary team. The three operations use browser-cookie or server-cookie authority:

await auth.addOrganizationTeamMember(organizationId, teamId, memberId, crypto.randomUUID());
await auth.removeOrganizationTeamMember(organizationId, teamId, memberId, crypto.randomUUID());
await auth.deleteOrganizationTeam(organizationId, teamId, crypto.randomUUID());

Auth keeps the last active team, the caller's selected team, and a team that a pending invitation targets. Repeat an operation with the same key only after a lost response. A bearer-backed server client rejects the three methods before network I/O. The invitation recipient declines an invitation with declineInvitation({ invitationId }).

Perform Recent Authentication

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:

await auth.signIn({ email, password });
await auth.addPasskey();

The enrollment authorization expires after five minutes. It is single-use. Signup, social sign-in, session refresh, bearer use, and generic session creation do not authorize enrollment. A User without a fresh password sign-in cannot use this path.

Start one reviewed platform-role action before invoking passkey authentication:

await auth.startPlatformRoleChangeRecentAuthentication({
  targetUserId: "user_01J8R7M6N5P4Q3S2T1",
  requestedRoles: ["administrator", "investigator"],
  expectedTargetRevision: 0,
  expectedGlobalRevision: 0,
  reasonCategory: "initial_bootstrap",
  changeReference: "change_01J8R7M6N5P4Q3S2T1",
  idempotencyKey: crypto.randomUUID(),
});

await auth.verifyRecentAuthenticationWithPasskey();

const continuation = await auth.getRecentAuthenticationStatus();
if (continuation.status === "ready") {
  showReviewedActionIsReady();
}

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.

Organization owner changes use the same ceremony. Pass the same input to the start and submit methods:

const change = {
  requestedManualRole: "owner" as const,
  expectedActorAuthorityRevision: 3,
  expectedTargetAuthorityRevision: 1,
  idempotencyKey: crypto.randomUUID(),
};

await auth.startOrganizationOwnerRoleChangeRecentAuthentication(organizationId, memberId, change);
await auth.verifyRecentAuthenticationWithPasskey();
await auth.changeOrganizationMemberManualRole(organizationId, memberId, change);

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.

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.

Read the signed-in User's current effective platform roles on the exact Auth Origin:

const platform = await auth.getPlatformStatus();

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.

Request A Drive Credential

Request one short-lived credential for one workspace:

const credential = await auth.getDriveCredential({
  workspace: { type: "personal" },
});

await fetch("https://drive.finueva.com/api/files", {
  headers: {
    authorization: `${credential.tokenType} ${credential.token}`,
  },
});

For an organization workspace:

const credential = await auth.getDriveCredential({
  workspace: {
    type: "organization",
    organizationId: "org_01J8R7M6N5P4Q3S2T1",
  },
});

Browser and device clients cache one completed credential in memory. The server client does not cache credentials. The SDK never replays a failed Drive operation.

Server Quick Start

Create one client inside each request handler:

import { createServerAuth } from "@finueva/auth/server";

export async function handleRequest(request: Request): Promise<Response> {
  const auth = createServerAuth({
    authOrigin: "https://auth.finueva.com",
    appOrigin: "https://app.heylomeet.com",
    request,
  });

  const session = await auth.getSession();

  return Response.json(
    { authenticated: session !== null, user: session?.user ?? null },
    { headers: { "cache-control": "no-store" } },
  );
}

If the incoming request has Authorization, the server client uses that bearer and forwards no cookies. Otherwise, it forwards only Auth cookies.

Some server methods return cookie changes. Apply them before you send the response:

const result = await auth.signOut();
const headers = new Headers({ location: "/signed-out" });

result.applyCookies(headers);

return new Response(null, { status: 303, headers });

Call applyCookies() after server signOut(), acceptInvitation(), and signInWithSocialIdToken().

Recover A Google Account Conflict

Use the request-scoped server client. If Google sign-in reports an account conflict, call startGoogleAccountRecovery({ idToken: { token, nonce } }). Auth verifies the Google identity and confirms that an existing verified password account requires linking. A generic sign-in error alone does not authorize recovery.

The result contains an opaque recoveryToken and an ISO expiresAt. Keep the result and Google ID token in short-lived encrypted server-managed state. Do not put them in URLs, logs, or browser storage.

Ask the user to sign in to the existing account with its password. Complete the configured second factor. Then create a new request-scoped client with that session and call completeGoogleAccountRecovery({ recoveryToken, idToken: { token, nonce } }). Auth requires a fresh password authentication after the recovery request. An old session or a different account cannot complete recovery. The result is { linked: true }; the current session remains in use.

If completion returns google_recovery_in_progress, retain the current session and recovery state. A retry can confirm a committed link. Auth does not repeat an uncertain linking mutation. If the original attempt cannot finish, wait for its ten-minute expiry before restarting.

Recovery is cookie-only and bound to the registered app origin. It does not enable implicit linking or link users by an email match alone. If recovery expires, restart Google sign-in. If email verification is required, complete the existing account's verification first.

Resolve A Drive Recipient

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.

import { createDriveRecipientAuth } from "@finueva/auth/server";

const recipients = createDriveRecipientAuth({
  authOrigin: "https://auth.finueva.com",
  timeoutMs: 10_000,
});

const recipient = await recipients.resolveRecipient(
  { email: "[email protected]" },
  clientAssertion, // Generate a new service assertion for this request.
);

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.

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. Optionally include actorUserId, the canonical Auth User ID of the User for whom the server resolves the recipient. Take it from the server's verified session, not from request values. It contains 1 to 128 URL-safe characters. Auth limits each actor to 30 resolutions per 60 seconds and rejects an invalid actorUserId with status 401. Without actorUserId, all requests from the service share 10 resolutions per 60 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.

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.

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.

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.

The factory and recipient types exist only in @finueva/auth/server. The cookie and session client from createServerAuth() does not expose recipient resolution.

Device Quick Start

Use the device flow for a CLI or native application that can open a system browser:

import { createDeviceAuth } from "@finueva/auth/device";

const auth = createDeviceAuth({
  authOrigin: "https://auth.finueva.com",
  clientId: "finueva-cli",
});

const authorization = await auth.requestAuthorization();

console.log(`Open ${authorization.verificationUriComplete}`);
console.log(`Code: ${authorization.userCode}`);

await auth.pollForToken(authorization);

clientId is public. deviceCode is not. Never log or display the device code.

Device tokens are memory-only by default. For persistence, supply a DeviceTokenPersistence adapter backed by an operating-system credential vault. Do not use local storage, a WebView cookie jar, or a plaintext file.

Handle Errors

Constructor and selected account-input errors can throw TypeError. Validated SDK request failures throw AuthError. Device authorization can throw DeviceAuthorizationError:

import { AuthError } from "@finueva/auth";

try {
  await auth.signIn({
    email: "[email protected]",
    password: "correct-horse-battery-staple",
  });
} catch (error) {
  if (!(error instanceof AuthError)) throw error;

  if (error.code === "invalid_credentials") {
    showInvalidCredentials();
  } else if (error.code === "identity_authority_unavailable") {
    showAuthUnavailable();
  } else {
    showAuthError(error.code);
  }
}

Use error.code for application behavior. Do not parse error.message. Except for RFC 8628 device polling, the SDK does not automatically repeat requests.

Security Checklist

  • Use HTTPS. Plain HTTP is accepted only on localhost.
  • Register exact application origins.
  • Create server clients per request.
  • Apply returned server cookies before you send the response.
  • Keep browser session snapshots display-only.
  • Keep device sessions in an operating-system credential vault.
  • Keep Drive credentials in memory.
  • Protect server-side mutations against CSRF.
  • Do not log cookies, bearer tokens, ID tokens, reset tokens, device codes, or complete Auth responses.

Current Limits

  • The package is Experimental until 1.0.0.
  • Requests time out after 10 seconds by default.
  • timeoutMs accepts 1 through 60,000 milliseconds.
  • Most responses are limited to 64 KiB. Organization read responses can use up to 256 KiB.
  • Browser snapshots and bearer authorization values are limited to 16 KiB.
  • Organization collections use page sizes from 1 through 100.
  • Organization Drive context supports at most 50 current teams.
  • Invitation creation supports only the member role in ordinary organizations.
  • Invitation resend and cancellation require browser-cookie or server-cookie authority and a manual owner or manual administrator.
  • Cross-root SSO, custom refresh tokens, and account deletion are not implemented.
  • Production support gates are incomplete. Ask the Auth operator for the current environment status before adoption.

Package Exports

import { AuthError, createAuthClient } from "@finueva/auth";
import { createServerAuth } from "@finueva/auth/server";
import { DeviceAuthorizationError, createDeviceAuth } from "@finueva/auth/device";

TypeScript declarations ship with the package. The public declarations do not expose Better Auth types.

Documentation

Long-form task guides are maintained with the Auth source:

These links track current source and can describe a newer SDK version. Check the version at the start of the guide. Repository access is required until these guides have a public, versioned documentation deployment.

Organization presence authorization candidate

The request-scoped server method authorizeOrganizationPresence(organizationId, { applicationId, userIds }, serviceAssertion, { applicationCredential }) forwards fresh actor, application and registered service proofs. The exact ordered selection contains 1 to 50 Auth User IDs. The supplied application credential remains untrusted proof. Auth checks its signature and exact current actor session. The result binds the actor, organization, application and selected IDs; its observation time is not an authority lease. Your application must enforce its own disclosure and visibility policies. Browser and device clients have no direct service facade. This source candidate requires release and registration qualification before use.

Organization admission candidate

Import createOrganizationAdmissionAuth, createOrganizationAdmissionBillingAuth, and createOrganizationAdmissionDirectoryAuth from @finueva/auth/server. These separate clients expose actor setup operations, private billing attestations, and the pending setup directory. Actor operations require a current incoming request and a separate service assertion. Use organizationAdmissionBodyDigest(operation, input) and the client's audience(operation) when signing each fresh assertion. A pending setup grants no organization membership or Drive authority. getCurrentAdmission returns null or an admission whose state is pending or active. Auth keeps no admission policy, cohort, or seal. This source candidate requires release and hosted qualification before use.

版本与发布记录

@finueva/auth 共发布 39 个版本,首次发布于 2026/07/29;过去 12 个月发布了 39 个版本。

版本发布日期距今
0.35.0latest2026/11/14今天
0.34.02026/11/14今天
0.33.02026/11/059 天前
0.32.02026/10/2520 天前
0.31.02026/10/121 个月前
0.30.02026/10/111 个月前

关于 @finueva/auth 的常见问题

@finueva/auth 是什么?

Public browser and server SDK for Finueva Auth

如何安装 @finueva/auth?

在项目目录运行 npm install @finueva/auth,或使用 pnpm add / yarn add / bun add。

@finueva/auth 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@finueva/auth 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@finueva/auth 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=22。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@finueva/auth 可以商用吗?

@finueva/auth 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@finueva/auth 的下载量有多少?

根据 npm 官方下载统计,@finueva/auth 在 2026/10/15 – 2026/11/13 这 30 天内被下载 4,575 次,日均约 153 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@finueva/auth 还在维护吗?

最新版本 0.35.0 发布于 2026/11/14(今天);至今共发布 39 个版本,其中过去 12 个月发布了 39 个;首次发布于 2026/07/29。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。