@finueva/auth 0.10.0 → 0.11.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
@@ -1,19 +1,10 @@
1
- # `@finueva/auth`
1
+ # Finueva Auth SDK for TypeScript
2
2
 
3
- The official TypeScript client for Finueva Auth.
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
- Use it to:
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`.
6
6
 
7
- - sign users in with email and password;
8
- - read the current session;
9
- - manage profile and password details;
10
- - read organizations, create ordinary teams, replace display data, and accept invitations;
11
- - request short-lived Drive credentials;
12
- - authenticate CLIs and native applications.
13
-
14
- The package is ESM-only and has no runtime dependencies. Its API is experimental while the package is below `1.0.0`.
15
-
16
- Sections marked **Unreleased** describe repository source and are not available in the currently published `@finueva/auth@0.9.0` package.
7
+ The passkey and recent-authentication methods below describe this repository's next package candidate. Published version `0.10.1` does not include them.
17
8
 
18
9
  ## Install
19
10
 
@@ -21,22 +12,30 @@ Sections marked **Unreleased** describe repository source and are not available
21
12
  pnpm add @finueva/auth
22
13
  ```
23
14
 
24
- Node.js applications require Node.js 22 or later.
15
+ For a browser or server application, ask your Auth operator for:
16
+
17
+ - the Auth origin for your environment;
18
+ - your registered application origin.
25
19
 
26
- ## Pick The Right Client
20
+ For a CLI or native application, ask for:
27
21
 
28
- | Runtime | Import | How it authenticates |
29
- | ------------------------- | ---------------------- | ------------------------------------ |
30
- | Browser | `@finueva/auth` | Finueva Auth's HTTP-only cookie |
31
- | Server or SSR handler | `@finueva/auth/server` | Cookies or one incoming bearer token |
32
- | CLI or native application | `@finueva/auth/device` | A device bearer session |
22
+ - the Auth origin for your environment;
23
+ - a registered device client ID.
33
24
 
34
- Create one browser client for the application, one server client per request, or one device client per application process.
25
+ Do not reuse configuration from another application or environment.
35
26
 
36
- Before you start, ask the Auth operator to register your exact application origin. Device applications also need a registered client ID. Do not copy values from another application or environment.
27
+ ## Choose A Client
28
+
29
+ | Runtime | Import | Session authority | Create it |
30
+ | ------------------------- | ---------------------- | ------------------------------ | ------------------------ |
31
+ | Browser | `@finueva/auth` | Auth HTTP-only cookie | Once for the application |
32
+ | Server or SSR handler | `@finueva/auth/server` | Incoming Auth cookie or bearer | Once per request |
33
+ | CLI or native application | `@finueva/auth/device` | Device bearer session | Once per process |
37
34
 
38
35
  ## Browser Quick Start
39
36
 
37
+ Create the client with the exact Auth and application origins:
38
+
40
39
  ```ts
41
40
  import { AuthError, createAuthClient } from "@finueva/auth";
42
41
 
@@ -44,189 +43,62 @@ const auth = createAuthClient({
44
43
  authOrigin: "https://auth.finueva.com",
45
44
  appOrigin: window.location.origin,
46
45
  });
46
+ ```
47
+
48
+ Read the current session:
47
49
 
50
+ ```ts
48
51
  const session = await auth.getSession();
49
52
 
50
53
  if (session) {
51
54
  console.log(`Signed in as ${session.user.email}`);
52
- } else {
53
- console.log("Not signed in");
54
55
  }
56
+ ```
57
+
58
+ Sign in with email and password:
55
59
 
60
+ ```ts
56
61
  try {
57
- const nextSession = await auth.signIn({
62
+ const session = await auth.signIn({
58
63
  email: "ada@example.com",
59
64
  password: "correct-horse-battery-staple",
60
65
  });
61
66
 
62
- console.log(`Welcome ${nextSession.user.name}`);
67
+ console.log(`Welcome ${session.user.name}`);
63
68
  } catch (error) {
64
69
  if (error instanceof AuthError && error.code === "invalid_credentials") {
65
- console.log("Check the email and password.");
70
+ showInvalidCredentials();
66
71
  } else {
67
72
  throw error;
68
73
  }
69
74
  }
70
75
  ```
71
76
 
72
- Auth keeps the session token in an HTTP-only cookie. The browser client never returns that token.
73
-
74
- ### React To Session Changes
75
-
76
- Use `onAuthStateChange()` to update the interface:
77
-
78
- ```ts
79
- const stop = auth.onAuthStateChange((state) => {
80
- switch (state.status) {
81
- case "loading":
82
- showLoading();
83
- break;
84
- case "cached":
85
- showAccountHint(state.session.user);
86
- break;
87
- case "authenticated":
88
- showAccount(state.session.user);
89
- break;
90
- case "unauthenticated":
91
- showSignIn();
92
- break;
93
- case "unavailable":
94
- showAuthUnavailable();
95
- break;
96
- }
97
- });
98
-
99
- window.addEventListener("pagehide", stop, { once: true });
100
- ```
101
-
102
- `cached` state is only a display hint. Never use cached browser state to authorize an operation. Check access on the server.
103
-
104
- Browser snapshots use local storage by default. Choose another mode when you create the client:
105
-
106
- ```ts
107
- const auth = createAuthClient({
108
- authOrigin: "https://auth.finueva.com",
109
- appOrigin: window.location.origin,
110
- persistence: "session", // "local", "session", or "memory"
111
- timeoutMs: 10_000,
112
- });
113
- ```
114
-
115
- The snapshot contains normalized display data, not cookies or Drive credentials.
116
-
117
- ## Common Browser Tasks
118
-
119
- ### Create An Account
120
-
121
- ```ts
122
- const session = await auth.signUp("ada@example.com", "correct-horse-battery-staple", "Ada Lovelace");
123
- ```
124
-
125
- New passwords must contain 10 to 128 characters.
126
-
127
- ### Update The Profile
128
-
129
- ```ts
130
- const session = await auth.updateProfile({
131
- name: "Ada Lovelace",
132
- image: "https://images.example.com/ada.png",
133
- });
134
- ```
135
-
136
- Pass `image: null` to remove the image.
137
-
138
- ### Change The Password
139
-
140
- ```ts
141
- await auth.changePassword(currentPassword, newPassword);
142
- ```
143
-
144
- This revokes the user's other sessions by default. Pass `false` as the third argument only when the product deliberately keeps those sessions.
145
-
146
- ### Reset A Password
147
-
148
- ```ts
149
- await auth.requestPasswordReset("ada@example.com", `${window.location.origin}/account/reset-password`);
150
- ```
151
-
152
- On the reset page, remove the token from the address bar before loading other resources. Use it once:
153
-
154
- ```ts
155
- const url = new URL(window.location.href);
156
- let token = url.searchParams.get("token");
157
-
158
- url.searchParams.delete("token");
159
- window.history.replaceState(null, "", `${url.pathname}${url.search}${url.hash}`);
160
-
161
- if (!token) throw new Error("Reset token is missing");
162
-
163
- try {
164
- await auth.resetPassword(token, newPassword);
165
- } finally {
166
- token = null;
167
- }
168
- ```
169
-
170
- Serve this page with `Cache-Control: no-store` and `Referrer-Policy: no-referrer`. Do not load analytics or third-party content on it.
171
-
172
- ### Verify An Email
173
-
174
- ```ts
175
- await auth.sendVerificationEmail({
176
- email: "ada@example.com",
177
- callbackURL: `${window.location.origin}/account/email-verified`,
178
- });
179
- ```
180
-
181
- The callback must use the application's exact origin. Omit it to use the default Auth result page.
182
-
183
- ### List Organizations
184
-
185
- ```ts
186
- let page = await auth.listOrganizations({ limit: 50 });
187
-
188
- for (const organization of page.organizations) {
189
- console.log(organization.id, organization.name, organization.slug);
190
- }
191
-
192
- if (page.nextCursor) {
193
- page = await auth.listOrganizations({ limit: 50, cursor: page.nextCursor });
194
- }
195
- ```
196
-
197
- All organization collections use an opaque cursor and a page size from 1 through 100. Use
198
- `getOrganization()`, `listOrganizationMembers()`, `listOrganizationTeams()`, and
199
- `listOrganizationTeamMembers()` with an explicit organization ID. Manual owners and admins can use
200
- `listOrganizationInvitations()` and `getOrganizationInvitation()`. A verified recipient can use
201
- `listReceivedInvitations()` and `getReceivedInvitation()`.
202
-
203
- Pagination is deterministic while a collection does not change. During concurrent mutation, an
204
- insert at or before the cursor can be absent, an insert after it can appear once, and a delete,
205
- update, status change, or expiry can change later pages. Restart pagination when you need a current
206
- view.
207
-
208
- ### Create A Team
209
-
210
- **Unreleased:** this method will ship in a later package version and is not in `@finueva/auth@0.9.0`.
211
-
212
- ```ts
213
- const idempotencyKey = crypto.randomUUID();
214
- const team = await auth.createOrganizationTeam("org_01J8R7M6N5P4Q3S2T1", "Platform Engineering", idempotencyKey);
215
- ```
77
+ The browser owns the session cookie. The SDK does not expose or persist it.
216
78
 
217
- The name and caller-owned idempotency key must each contain from 1 through 255 UTF-8 bytes. Use a
218
- new key for each intended team. If a response is lost, send the same organization ID, name, and key
219
- again. Auth returns the original creation result for a completed same-key request, even if a later
220
- operation changed the team display. A conflicting or unresolved same-key request returns
221
- `state_conflict`. The SDK never retries automatically.
79
+ ## Common Tasks
222
80
 
223
- For an ordinary organization, a current effective owner or administrator can use the cookie or
224
- bearer lane. For a managed organization, a current manual owner or manual administrator must use the
225
- cookie lane. The method creates only an ordinary team.
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()` |
226
98
 
227
- ### Create A Member Invitation
99
+ ## Create An Organization Invitation
228
100
 
229
- **Unreleased:** this method will ship in a later package version and is not in `@finueva/auth@0.9.0`.
101
+ Use one new idempotency key for each invitation that you intend to create:
230
102
 
231
103
  ```ts
232
104
  const invitation = await auth.createOrganizationMemberInvitation("org_01J8R7M6N5P4Q3S2T1", {
@@ -238,68 +110,54 @@ const invitation = await auth.createOrganizationMemberInvitation("org_01J8R7M6N5
238
110
  console.log(invitation.id, invitation.expiresAt);
239
111
  ```
240
112
 
241
- The SDK trims and lowercases the email address. The email address can contain no more than 320 UTF-8 bytes. `teamIds` is optional and can contain no more than 100 unique ordinary team IDs. The caller-owned idempotency key must contain from 1 through 255 UTF-8 bytes.
242
-
243
- Use a new key for each intended invitation. If a response is lost, send the same organization ID and input again. The method returns only the invitation ID and expiry. It does not report email delivery. The SDK never retries automatically.
244
-
245
- For an ordinary organization, a current effective owner or administrator can use the cookie or bearer lane. Managed organizations are denied.
246
-
247
- ### Accept An Invitation
248
-
249
- ```ts
250
- const result = await auth.acceptInvitation({
251
- invitationId: "invitation_01J8R7M6N5P4Q3S2T1",
252
- });
113
+ 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.
253
114
 
254
- console.log(result.organizationId);
255
- ```
115
+ ## Perform Recent Authentication
256
116
 
257
- ### Replace Organization Display
117
+ 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:
258
118
 
259
119
  ```ts
260
- const display = await auth.updateOrganizationDisplay("org_01J8R7M6N5P4Q3S2T1", {
261
- name: "Example Group",
262
- slug: "example-group",
263
- logo: null,
264
- });
120
+ await auth.signIn({ email, password });
121
+ await auth.addPasskey();
265
122
  ```
266
123
 
267
- Send all three fields. `name` and `slug` can contain no more than 255 UTF-8 bytes. Use `logo: null` to remove the logo. A non-null logo must be an exact canonical HTTPS URL of no more than 2048 UTF-8 bytes. The method supports manual owners and manual administrators of ordinary organizations. Provider-only administrators are denied. It does not update metadata, lifecycle, membership, teams, invitations, or active-organization state.
268
-
269
- Browser and cookie-based server calls require the exact configured application Origin. The SDK reports a denied or missing Origin as `request_origin_denied`. Other organization authority denials use `access_denied`.
124
+ 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.
270
125
 
271
- The method does not retry. If a response is lost, submit the same complete desired state again. Concurrent different updates use last-write-wins behavior.
272
-
273
- ### Replace Team Display
274
-
275
- **Unreleased:** this method will ship in a later package version and is not in `@finueva/auth@0.9.0`.
126
+ Start one reviewed platform-role action before invoking passkey authentication:
276
127
 
277
128
  ```ts
278
- const display = await auth.updateOrganizationTeamDisplay("org_01J8R7M6N5P4Q3S2T1", "team_01J8R7M6N5P4Q3S2T1", {
279
- name: "Platform Engineering",
129
+ await auth.startPlatformRoleChangeRecentAuthentication({
130
+ targetUserId: "user_01J8R7M6N5P4Q3S2T1",
131
+ requestedRoles: ["administrator", "investigator"],
132
+ expectedRevision: 4,
280
133
  });
281
- ```
282
134
 
283
- Send the complete `name`. It can contain no more than 255 UTF-8 bytes. For an ordinary organization, a current effective owner or administrator can use the cookie or bearer lane. For a managed organization, a current manual owner or manual administrator must use the cookie lane. Managed teams are denied.
135
+ await auth.verifyRecentAuthenticationWithPasskey();
284
136
 
285
- The method does not retry. If a response is lost, submit the same complete desired name again. Concurrent different updates use last-write-wins behavior.
137
+ const continuation = await auth.getRecentAuthenticationStatus();
138
+ if (continuation.status === "ready") {
139
+ showReviewedActionIsReady();
140
+ }
141
+ ```
286
142
 
287
- ### Sign Out
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.
288
144
 
289
- ```ts
290
- await auth.signOut();
291
- ```
145
+ 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.
292
146
 
293
147
  ## Request A Drive Credential
294
148
 
295
- Ask Auth for a credential for one workspace:
149
+ Request one short-lived credential for one workspace:
296
150
 
297
151
  ```ts
298
152
  const credential = await auth.getDriveCredential({
299
153
  workspace: { type: "personal" },
300
154
  });
301
155
 
302
- const authorization = `${credential.tokenType} ${credential.token}`;
156
+ await fetch("https://drive.finueva.com/api/files", {
157
+ headers: {
158
+ authorization: `${credential.tokenType} ${credential.token}`,
159
+ },
160
+ });
303
161
  ```
304
162
 
305
163
  For an organization workspace:
@@ -313,184 +171,87 @@ const credential = await auth.getDriveCredential({
313
171
  });
314
172
  ```
315
173
 
316
- Drive credentials last about 14 minutes. Browser and device clients keep them in memory only and refresh them when two minutes remain. The server client does not cache them.
317
-
318
- If Drive rejects a credential, request a replacement:
319
-
320
- ```ts
321
- const replacement = await auth.getDriveCredential({
322
- workspace: { type: "personal" },
323
- forceRefresh: true,
324
- });
325
- ```
326
-
327
- The SDK does not replay the failed Drive request. Replay it only when the operation is safe to repeat.
174
+ 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.
328
175
 
329
- ## Server And SSR
176
+ ## Server Quick Start
330
177
 
331
- Create a server client inside each request handler. It forwards only Finueva Auth cookies. If the incoming request has a bearer token, that token takes precedence and the client forwards no cookies.
178
+ Create one client inside each request handler:
332
179
 
333
180
  ```ts
334
- import { AuthError, createServerAuth } from "@finueva/auth/server";
181
+ import { createServerAuth } from "@finueva/auth/server";
335
182
 
336
- export async function handleAccountRequest(request: Request): Promise<Response> {
183
+ export async function handleRequest(request: Request): Promise<Response> {
337
184
  const auth = createServerAuth({
338
185
  authOrigin: "https://auth.finueva.com",
339
186
  appOrigin: "https://files.finueva.com",
340
187
  request,
341
188
  });
342
189
 
343
- try {
344
- const session = await auth.getSession();
345
-
346
- if (!session) {
347
- return Response.json(
348
- { authenticated: false },
349
- {
350
- status: 401,
351
- headers: { "cache-control": "no-store" },
352
- },
353
- );
354
- }
355
-
356
- return Response.json(
357
- { user: session.user },
358
- {
359
- headers: { "cache-control": "no-store" },
360
- },
361
- );
362
- } catch (error) {
363
- if (error instanceof AuthError) {
364
- return Response.json(
365
- { code: error.code },
366
- {
367
- status: error.status,
368
- headers: { "cache-control": "no-store" },
369
- },
370
- );
371
- }
190
+ const session = await auth.getSession();
372
191
 
373
- throw error;
374
- }
192
+ return Response.json(
193
+ { authenticated: session !== null, user: session?.user ?? null },
194
+ { headers: { "cache-control": "no-store" } },
195
+ );
375
196
  }
376
197
  ```
377
198
 
378
- Some server mutations return cookies. Apply them to the outgoing response:
199
+ If the incoming request has `Authorization`, the server client uses that bearer and forwards no cookies. Otherwise, it forwards only Auth cookies.
200
+
201
+ Some server methods return cookie changes. Apply them before you send the response:
379
202
 
380
203
  ```ts
381
204
  const result = await auth.signOut();
382
- const headers = new Headers({
383
- "cache-control": "no-store",
384
- location: "/signed-out",
385
- });
205
+ const headers = new Headers({ location: "/signed-out" });
386
206
 
387
207
  result.applyCookies(headers);
388
208
 
389
209
  return new Response(null, { status: 303, headers });
390
210
  ```
391
211
 
392
- Call `applyCookies()` after server `signOut()`, `acceptInvitation()`, and `signInWithSocialIdToken()`. If you omit it, the browser does not receive the session change.
393
-
394
- Check `Origin`, Fetch Metadata, or a server-generated CSRF token before a server-side mutation. The SDK forwards authority; it does not replace your application's CSRF protection.
212
+ Call `applyCookies()` after server `signOut()`, `acceptInvitation()`, and `signInWithSocialIdToken()`.
395
213
 
396
- ## CLI And Native Applications
214
+ ## Device Quick Start
397
215
 
398
- Use the device flow when a CLI or device can open a system browser:
216
+ Use the device flow for a CLI or native application that can open a system browser:
399
217
 
400
218
  ```ts
401
- import { AuthError, DeviceAuthorizationError, createDeviceAuth } from "@finueva/auth/device";
219
+ import { createDeviceAuth } from "@finueva/auth/device";
402
220
 
403
221
  const auth = createDeviceAuth({
404
222
  authOrigin: "https://auth.finueva.com",
405
223
  clientId: "finueva-cli",
406
224
  });
407
225
 
408
- const controller = new AbortController();
409
- process.once("SIGINT", () => controller.abort());
410
-
411
- try {
412
- const authorization = await auth.requestAuthorization({
413
- signal: controller.signal,
414
- });
415
-
416
- console.log(`Open ${authorization.verificationUriComplete}`);
417
- console.log(`Code: ${authorization.userCode}`);
418
-
419
- await auth.pollForToken(authorization, {
420
- signal: controller.signal,
421
- });
422
-
423
- const session = await auth.getSession();
424
- console.log(session ? `Signed in as ${session.user.email}` : "Not signed in");
425
- } catch (error) {
426
- if (error instanceof DeviceAuthorizationError || error instanceof AuthError) {
427
- console.error(error.code);
428
- } else {
429
- throw error;
430
- }
431
- }
432
- ```
433
-
434
- `clientId` is a public registered identifier, not a secret. Never display or log `authorization.deviceCode`.
435
-
436
- Device sessions are memory-only unless you provide `DeviceTokenPersistence`:
437
-
438
- ```ts
439
- import { createDeviceAuth, type DeviceTokenPersistence } from "@finueva/auth/device";
226
+ const authorization = await auth.requestAuthorization();
440
227
 
441
- const persistence: DeviceTokenPersistence = {
442
- read: (signal) => vault.read("finueva-auth", signal),
443
- replace: (token, signal) => vault.replace("finueva-auth", token, signal),
444
- delete: (signal) => vault.delete("finueva-auth", signal),
445
- };
228
+ console.log(`Open ${authorization.verificationUriComplete}`);
229
+ console.log(`Code: ${authorization.userCode}`);
446
230
 
447
- const auth = createDeviceAuth({
448
- authOrigin: "https://auth.finueva.com",
449
- clientId: "finueva-desktop",
450
- fetch: nativeFetch,
451
- persistence,
452
- });
231
+ await auth.pollForToken(authorization);
453
232
  ```
454
233
 
455
- Use the operating system's credential vault. `replace()` must replace the complete token atomically. Do not use local storage, a WebView cookie jar, or a plaintext file. A native application must also supply a reviewed native HTTPS transport that omits ambient WebView cookies.
456
-
457
- Direct first-party native email sign-in is available to registered applications:
458
-
459
- ```ts
460
- const session = await auth.signInWithEmail("ada@example.com", "correct-horse-battery-staple");
461
- ```
234
+ `clientId` is public. `deviceCode` is not. Never log or display the device code.
462
235
 
463
- End the current device session with `revokeCurrentDevice()`. End every browser and device session for the account with `revokeAllSessions()`.
236
+ 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.
464
237
 
465
- ## Social ID Tokens
238
+ ## Handle Errors
466
239
 
467
- Browser, server, and device clients accept Google and Apple ID tokens from a first-party provider integration:
240
+ Constructor and selected account-input errors can throw `TypeError`. Validated SDK request failures throw `AuthError`. Device authorization can throw `DeviceAuthorizationError`:
468
241
 
469
242
  ```ts
470
- const session = await auth.signInWithSocialIdToken({
471
- provider: "google",
472
- idToken,
473
- nonce,
474
- signUp: false,
475
- });
476
- ```
477
-
478
- Set `signUp: true` only after the user chooses to create an account. The application owns the provider authorization flow, state, nonce, and PKCE transaction. Pass only the ID token to Auth. Do not pass provider access or refresh tokens.
243
+ import { AuthError } from "@finueva/auth";
479
244
 
480
- The generic Google and Apple redirect flow is not exposed by this package.
481
-
482
- ## Errors
483
-
484
- Invalid local arguments throw `TypeError`. Auth failures throw `AuthError`:
485
-
486
- ```ts
487
245
  try {
488
- await auth.getSession();
246
+ await auth.signIn({
247
+ email: "ada@example.com",
248
+ password: "correct-horse-battery-staple",
249
+ });
489
250
  } catch (error) {
490
251
  if (!(error instanceof AuthError)) throw error;
491
252
 
492
- if (error.code === "auth_request_rate_limited") {
493
- scheduleRetry(error.retryAfterSeconds ?? 60);
253
+ if (error.code === "invalid_credentials") {
254
+ showInvalidCredentials();
494
255
  } else if (error.code === "identity_authority_unavailable") {
495
256
  showAuthUnavailable();
496
257
  } else {
@@ -499,48 +260,55 @@ try {
499
260
  }
500
261
  ```
501
262
 
502
- Use `error.code` for application behavior. Do not parse `error.message`.
503
-
504
- | Code | What the application should do |
505
- | -------------------------------- | --------------------------------------------------------------------- |
506
- | `invalid_credentials` | Ask the user to check the credential. |
507
- | `invalid_auth_input` | Correct the account input. |
508
- | `account_conflict` | Stop the account action. Do not expose account-existence details. |
509
- | `authentication_required` | Start sign-in. |
510
- | `email_verification_required` | Ask the user to verify the current email. |
511
- | `invitation_unavailable` | Ask for a new invitation. Do not retry the old one. |
512
- | `invalid_workspace_selection` | Send one exact personal or organization workspace. |
513
- | `state_conflict` | Wait for the active authority operation, then read the session again. |
514
- | `access_denied` | Stop the operation. |
515
- | `credential_rate_limited` | Wait for `retryAfterSeconds` when present. |
516
- | `auth_request_rate_limited` | Wait for `retryAfterSeconds` when present. |
517
- | `identity_authority_unavailable` | Keep state unverified and try again later. |
518
-
519
- Device authorization can also throw `DeviceAuthorizationError`. Its codes are `device_access_denied`, `device_authorization_cancelled`, `device_code_expired`, `invalid_device_authorization`, and `identity_authority_unavailable`.
520
-
521
- The SDK does not automatically retry requests.
263
+ Use `error.code` for application behavior. Do not parse `error.message`. Except for RFC 8628 device polling, the SDK does not automatically repeat requests.
522
264
 
523
- ## Security Notes
265
+ ## Security Checklist
524
266
 
525
267
  - Use HTTPS. Plain HTTP is accepted only on localhost.
526
- - Use exact registered origins. Do not build an Auth origin from the current request host.
268
+ - Register exact application origins.
269
+ - Create server clients per request.
270
+ - Apply returned server cookies before you send the response.
527
271
  - Keep browser session snapshots display-only.
528
- - Create server clients per request. Do not keep one in a module singleton.
529
- - Apply returned cookies before sending a server response.
530
- - Keep device bearer tokens in an operating-system credential vault.
531
- - Keep Drive credentials in memory and send them only to Drive.
272
+ - Keep device sessions in an operating-system credential vault.
273
+ - Keep Drive credentials in memory.
274
+ - Protect server-side mutations against CSRF.
532
275
  - Do not log cookies, bearer tokens, ID tokens, reset tokens, device codes, or complete Auth responses.
533
- - Protect application mutations against CSRF before calling the server client.
534
276
 
535
277
  ## Current Limits
536
278
 
537
- - The public API is experimental and can change before `1.0.0`.
538
- - Requests time out after 10 seconds by default. `timeoutMs` accepts 1 to 60,000 milliseconds.
539
- - Auth JSON responses are limited to 64 KiB.
279
+ - The package is Experimental until `1.0.0`.
280
+ - Requests time out after 10 seconds by default.
281
+ - `timeoutMs` accepts 1 through 60,000 milliseconds.
282
+ - Most responses are limited to 64 KiB. Organization read responses can use up to 256 KiB.
540
283
  - Browser snapshots and bearer authorization values are limited to 16 KiB.
541
- - Organization lists fail closed at 100 records.
284
+ - Organization collections use page sizes from 1 through 100.
542
285
  - Organization Drive context supports at most 50 current teams.
543
- - Ordinary-organization display replacement is the only organization mutation in `@finueva/auth@0.9.0`. Unreleased source also exposes ordinary-team creation and display replacement. Account deletion and other organization management are not exposed.
544
- - Cross-root SSO and custom refresh tokens are not implemented.
545
- - Hosted mobile transport and operating-system vault qualification are incomplete.
546
- - Production support gates are not complete.
286
+ - Invitation creation supports only the `member` role in ordinary organizations.
287
+ - Invitation resend and cancellation are not public SDK methods.
288
+ - Cross-root SSO, custom refresh tokens, and account deletion are not implemented.
289
+ - Production support gates are incomplete. Ask the Auth operator for the current environment status before adoption.
290
+
291
+ ## Package Exports
292
+
293
+ ```ts
294
+ import { AuthError, createAuthClient } from "@finueva/auth";
295
+ import { createServerAuth } from "@finueva/auth/server";
296
+ import { DeviceAuthorizationError, createDeviceAuth } from "@finueva/auth/device";
297
+ ```
298
+
299
+ TypeScript declarations ship with the package. The public declarations do not expose Better Auth types.
300
+
301
+ ## Documentation
302
+
303
+ Long-form task guides are maintained with the Auth source:
304
+
305
+ - [Get started](https://github.com/Ifkafin/auth/blob/main/apps/developer/docs/sdk/index.md)
306
+ - [Browser applications](https://github.com/Ifkafin/auth/blob/main/apps/developer/docs/sdk/browser.md)
307
+ - [Server and SSR applications](https://github.com/Ifkafin/auth/blob/main/apps/developer/docs/sdk/server.md)
308
+ - [CLI and native applications](https://github.com/Ifkafin/auth/blob/main/apps/developer/docs/sdk/device.md)
309
+ - [Social ID-token sign-in](https://github.com/Ifkafin/auth/blob/main/apps/developer/docs/sdk/social-sign-in.md)
310
+ - [Organizations and invitations](https://github.com/Ifkafin/auth/blob/main/apps/developer/docs/sdk/organizations.md)
311
+ - [Drive credentials](https://github.com/Ifkafin/auth/blob/main/apps/developer/docs/sdk/drive-credentials.md)
312
+ - [Errors and recovery](https://github.com/Ifkafin/auth/blob/main/apps/developer/docs/sdk/errors-and-recovery.md)
313
+
314
+ 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.