@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 +153 -385
- package/THIRD_PARTY_NOTICES.md +26 -0
- package/dist/device.d.ts +1 -1
- package/dist/device.js +3 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +863 -8
- package/dist/server.d.ts +1 -1
- package/dist/server.js +3 -0
- package/dist/{types-Byw9JQwL.d.ts → types-BqpgLkQB.d.ts} +40 -3
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -1,19 +1,10 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Finueva Auth SDK for TypeScript
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Use `@finueva/auth` to add Finueva sign-in, organization access, invitations, and Drive credentials to a browser, server, CLI, or native application.
|
|
4
4
|
|
|
5
|
-
|
|
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
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
20
|
+
For a CLI or native application, ask for:
|
|
27
21
|
|
|
28
|
-
|
|
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
|
-
|
|
25
|
+
Do not reuse configuration from another application or environment.
|
|
35
26
|
|
|
36
|
-
|
|
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
|
|
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 ${
|
|
67
|
+
console.log(`Welcome ${session.user.name}`);
|
|
63
68
|
} catch (error) {
|
|
64
69
|
if (error instanceof AuthError && error.code === "invalid_credentials") {
|
|
65
|
-
|
|
70
|
+
showInvalidCredentials();
|
|
66
71
|
} else {
|
|
67
72
|
throw error;
|
|
68
73
|
}
|
|
69
74
|
}
|
|
70
75
|
```
|
|
71
76
|
|
|
72
|
-
|
|
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
|
-
|
|
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
|
-
|
|
224
|
-
|
|
225
|
-
|
|
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
|
-
|
|
99
|
+
## Create An Organization Invitation
|
|
228
100
|
|
|
229
|
-
|
|
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
|
-
|
|
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
|
-
|
|
255
|
-
```
|
|
115
|
+
## Perform Recent Authentication
|
|
256
116
|
|
|
257
|
-
|
|
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
|
-
|
|
261
|
-
|
|
262
|
-
slug: "example-group",
|
|
263
|
-
logo: null,
|
|
264
|
-
});
|
|
120
|
+
await auth.signIn({ email, password });
|
|
121
|
+
await auth.addPasskey();
|
|
265
122
|
```
|
|
266
123
|
|
|
267
|
-
|
|
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
|
-
|
|
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
|
-
|
|
279
|
-
|
|
129
|
+
await auth.startPlatformRoleChangeRecentAuthentication({
|
|
130
|
+
targetUserId: "user_01J8R7M6N5P4Q3S2T1",
|
|
131
|
+
requestedRoles: ["administrator", "investigator"],
|
|
132
|
+
expectedRevision: 4,
|
|
280
133
|
});
|
|
281
|
-
```
|
|
282
134
|
|
|
283
|
-
|
|
135
|
+
await auth.verifyRecentAuthenticationWithPasskey();
|
|
284
136
|
|
|
285
|
-
|
|
137
|
+
const continuation = await auth.getRecentAuthenticationStatus();
|
|
138
|
+
if (continuation.status === "ready") {
|
|
139
|
+
showReviewedActionIsReady();
|
|
140
|
+
}
|
|
141
|
+
```
|
|
286
142
|
|
|
287
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
176
|
+
## Server Quick Start
|
|
330
177
|
|
|
331
|
-
Create
|
|
178
|
+
Create one client inside each request handler:
|
|
332
179
|
|
|
333
180
|
```ts
|
|
334
|
-
import {
|
|
181
|
+
import { createServerAuth } from "@finueva/auth/server";
|
|
335
182
|
|
|
336
|
-
export async function
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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()`.
|
|
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
|
-
##
|
|
214
|
+
## Device Quick Start
|
|
397
215
|
|
|
398
|
-
Use the device flow
|
|
216
|
+
Use the device flow for a CLI or native application that can open a system browser:
|
|
399
217
|
|
|
400
218
|
```ts
|
|
401
|
-
import {
|
|
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
|
|
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
|
-
|
|
442
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
238
|
+
## Handle Errors
|
|
466
239
|
|
|
467
|
-
|
|
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
|
-
|
|
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.
|
|
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 === "
|
|
493
|
-
|
|
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
|
|
265
|
+
## Security Checklist
|
|
524
266
|
|
|
525
267
|
- Use HTTPS. Plain HTTP is accepted only on localhost.
|
|
526
|
-
-
|
|
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
|
-
-
|
|
529
|
-
-
|
|
530
|
-
-
|
|
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
|
|
538
|
-
- Requests time out after 10 seconds by default.
|
|
539
|
-
-
|
|
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
|
|
284
|
+
- Organization collections use page sizes from 1 through 100.
|
|
542
285
|
- Organization Drive context supports at most 50 current teams.
|
|
543
|
-
-
|
|
544
|
-
-
|
|
545
|
-
-
|
|
546
|
-
- Production support gates are
|
|
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.
|