@passflow/core 0.0.1
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 +1087 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +2149 -0
- package/dist/index.mjs.map +1 -0
- package/dist/lib/api/app.d.ts +8 -0
- package/dist/lib/api/app.d.ts.map +1 -0
- package/dist/lib/api/auth.d.ts +23 -0
- package/dist/lib/api/auth.d.ts.map +1 -0
- package/dist/lib/api/axios-client.d.ts +36 -0
- package/dist/lib/api/axios-client.d.ts.map +1 -0
- package/dist/lib/api/index.d.ts +8 -0
- package/dist/lib/api/index.d.ts.map +1 -0
- package/dist/lib/api/invitation.d.ts +77 -0
- package/dist/lib/api/invitation.d.ts.map +1 -0
- package/dist/lib/api/model.d.ts +459 -0
- package/dist/lib/api/model.d.ts.map +1 -0
- package/dist/lib/api/setting.d.ts +10 -0
- package/dist/lib/api/setting.d.ts.map +1 -0
- package/dist/lib/api/tenant.d.ts +213 -0
- package/dist/lib/api/tenant.d.ts.map +1 -0
- package/dist/lib/api/user.d.ts +19 -0
- package/dist/lib/api/user.d.ts.map +1 -0
- package/dist/lib/constants/index.d.ts +8 -0
- package/dist/lib/constants/index.d.ts.map +1 -0
- package/dist/lib/device-service/index.d.ts +7 -0
- package/dist/lib/device-service/index.d.ts.map +1 -0
- package/dist/lib/index.d.ts +8 -0
- package/dist/lib/index.d.ts.map +1 -0
- package/dist/lib/passflow.d.ts +115 -0
- package/dist/lib/passflow.d.ts.map +1 -0
- package/dist/lib/services/auth-service.d.ts +67 -0
- package/dist/lib/services/auth-service.d.ts.map +1 -0
- package/dist/lib/services/index.d.ts +7 -0
- package/dist/lib/services/index.d.ts.map +1 -0
- package/dist/lib/services/invitation-service.d.ts +44 -0
- package/dist/lib/services/invitation-service.d.ts.map +1 -0
- package/dist/lib/services/logger.d.ts +24 -0
- package/dist/lib/services/logger.d.ts.map +1 -0
- package/dist/lib/services/tenant-service.d.ts +200 -0
- package/dist/lib/services/tenant-service.d.ts.map +1 -0
- package/dist/lib/services/tenant-user-membership.d.ts +76 -0
- package/dist/lib/services/tenant-user-membership.d.ts.map +1 -0
- package/dist/lib/services/token-cache-service.d.ts +26 -0
- package/dist/lib/services/token-cache-service.d.ts.map +1 -0
- package/dist/lib/services/user-service.d.ts +39 -0
- package/dist/lib/services/user-service.d.ts.map +1 -0
- package/dist/lib/storage-manager/index.d.ts +37 -0
- package/dist/lib/storage-manager/index.d.ts.map +1 -0
- package/dist/lib/store.d.ts +89 -0
- package/dist/lib/store.d.ts.map +1 -0
- package/dist/lib/token-service/index.d.ts +4 -0
- package/dist/lib/token-service/index.d.ts.map +1 -0
- package/dist/lib/token-service/membership.d.ts +37 -0
- package/dist/lib/token-service/membership.d.ts.map +1 -0
- package/dist/lib/token-service/service.d.ts +35 -0
- package/dist/lib/token-service/service.d.ts.map +1 -0
- package/dist/lib/token-service/token.d.ts +34 -0
- package/dist/lib/token-service/token.d.ts.map +1 -0
- package/dist/lib/types/index.d.ts +22 -0
- package/dist/lib/types/index.d.ts.map +1 -0
- package/dist/tests/storage-manager/fake-storage.d.ts +7 -0
- package/dist/tests/storage-manager/fake-storage.d.ts.map +1 -0
- package/dist/tests/storage-manager/storage-manager.test.d.ts +2 -0
- package/dist/tests/storage-manager/storage-manager.test.d.ts.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +81 -0
package/README.md
ADDED
|
@@ -0,0 +1,1087 @@
|
|
|
1
|
+
# Passflow JavaScript SDK Documentation
|
|
2
|
+
|
|
3
|
+
## Changelog
|
|
4
|
+
|
|
5
|
+
### Version 0.1.45
|
|
6
|
+
|
|
7
|
+
- Fixed bug related to null/undefined checks in the codebase
|
|
8
|
+
|
|
9
|
+
## Table of Contents
|
|
10
|
+
|
|
11
|
+
- [Passflow JavaScript SDK Documentation](#passflow-javascript-sdk-documentation)
|
|
12
|
+
- [Changelog](#changelog)
|
|
13
|
+
- [Version 0.1.45](#version-0145)
|
|
14
|
+
- [Table of Contents](#table-of-contents)
|
|
15
|
+
- [Quick Start Examples](#quick-start-examples)
|
|
16
|
+
- [Basic Initialization](#basic-initialization)
|
|
17
|
+
- [Simple Authentication](#simple-authentication)
|
|
18
|
+
- [Quick Passwordless Authentication](#quick-passwordless-authentication)
|
|
19
|
+
- [Basic Passkey Usage](#basic-passkey-usage)
|
|
20
|
+
- [Quick Password Reset](#quick-password-reset)
|
|
21
|
+
- [Basic Tenant Creation](#basic-tenant-creation)
|
|
22
|
+
- [Simple Event Subscription](#simple-event-subscription)
|
|
23
|
+
- [Introduction](#introduction)
|
|
24
|
+
- [Installation](#installation)
|
|
25
|
+
- [Getting Started](#getting-started)
|
|
26
|
+
- [Initialization](#initialization)
|
|
27
|
+
- [Session Management](#session-management)
|
|
28
|
+
- [Authentication](#authentication)
|
|
29
|
+
- [Sign In](#sign-in)
|
|
30
|
+
- [Sign Up](#sign-up)
|
|
31
|
+
- [Sign Out](#sign-out)
|
|
32
|
+
- [Passwordless Authentication](#passwordless-authentication)
|
|
33
|
+
- [Federated Authentication](#federated-authentication)
|
|
34
|
+
- [Passkey Authentication](#passkey-authentication)
|
|
35
|
+
- [Password Reset](#password-reset)
|
|
36
|
+
- [Token Management](#token-management)
|
|
37
|
+
- [Tenant Management](#tenant-management)
|
|
38
|
+
- [Invitation Management](#invitation-management)
|
|
39
|
+
- [Events and Subscriptions](#events-and-subscriptions)
|
|
40
|
+
- [Error Handling](#error-handling)
|
|
41
|
+
- [API Reference](#api-reference)
|
|
42
|
+
- [Passflow Class](#passflow-class)
|
|
43
|
+
- [Constructor](#constructor)
|
|
44
|
+
- [Methods](#methods)
|
|
45
|
+
- [Services](#services)
|
|
46
|
+
- [Types](#types)
|
|
47
|
+
- [PassflowConfig](#passflowconfig)
|
|
48
|
+
- [Tokens](#tokens)
|
|
49
|
+
- [Token](#token)
|
|
50
|
+
- [PassflowSignInPayload](#passflowsigninpayload)
|
|
51
|
+
- [PassflowSignUpPayload](#passflowsignuppayload)
|
|
52
|
+
- [UserMembership](#usermembership)
|
|
53
|
+
- [PassflowEvent](#passflowevent)
|
|
54
|
+
- [Detailed Examples](#detailed-examples)
|
|
55
|
+
- [Complete Sign In Flow](#complete-sign-in-flow)
|
|
56
|
+
- [Authentication with Passkeys](#authentication-with-passkeys)
|
|
57
|
+
- [Multi-tenant Application](#multi-tenant-application)
|
|
58
|
+
|
|
59
|
+
## Quick Start Examples
|
|
60
|
+
|
|
61
|
+
### Basic Initialization
|
|
62
|
+
|
|
63
|
+
```javascript
|
|
64
|
+
import { Passflow } from "passflow-js";
|
|
65
|
+
|
|
66
|
+
// Minimal initialization with just the required appId
|
|
67
|
+
const passflow = new Passflow({
|
|
68
|
+
appId: "your-app-id",
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Simple session setup
|
|
72
|
+
passflow.session({
|
|
73
|
+
createSession: () => console.log("User is authenticated"),
|
|
74
|
+
expiredSession: () => console.log("User session expired"),
|
|
75
|
+
});
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Simple Authentication
|
|
79
|
+
|
|
80
|
+
```javascript
|
|
81
|
+
// Basic sign in with just email and password
|
|
82
|
+
const simpleSignIn = async () => {
|
|
83
|
+
try {
|
|
84
|
+
await passflow.signIn({
|
|
85
|
+
email: "user@example.com",
|
|
86
|
+
password: "password123",
|
|
87
|
+
});
|
|
88
|
+
console.log("Signed in successfully");
|
|
89
|
+
} catch (error) {
|
|
90
|
+
console.error("Sign in failed", error);
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// Basic sign up with minimal user info
|
|
95
|
+
const simpleSignUp = async () => {
|
|
96
|
+
try {
|
|
97
|
+
await passflow.signUp({
|
|
98
|
+
user: {
|
|
99
|
+
email: "user@example.com",
|
|
100
|
+
password: "password123",
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
console.log("Registered successfully");
|
|
104
|
+
} catch (error) {
|
|
105
|
+
console.error("Registration failed", error);
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
// Simple logout
|
|
110
|
+
const simpleSignOut = async () => {
|
|
111
|
+
await passflow.logOut();
|
|
112
|
+
};
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Quick Passwordless Authentication
|
|
116
|
+
|
|
117
|
+
```javascript
|
|
118
|
+
// Start passwordless flow with just email
|
|
119
|
+
const simplePasswordless = async () => {
|
|
120
|
+
try {
|
|
121
|
+
const response = await passflow.passwordlessSignIn({
|
|
122
|
+
email: "user@example.com",
|
|
123
|
+
challenge_type: "otp",
|
|
124
|
+
redirect_url: window.location.origin,
|
|
125
|
+
});
|
|
126
|
+
console.log("Check your email for the code");
|
|
127
|
+
return response.challenge_id;
|
|
128
|
+
} catch (error) {
|
|
129
|
+
console.error("Failed to start passwordless flow", error);
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
// Complete with just the challenge ID and OTP
|
|
134
|
+
const completeSimplePasswordless = async (challengeId, otp) => {
|
|
135
|
+
try {
|
|
136
|
+
await passflow.passwordlessSignInComplete({
|
|
137
|
+
challenge_id: challengeId,
|
|
138
|
+
otp: otp,
|
|
139
|
+
});
|
|
140
|
+
console.log("Authentication successful");
|
|
141
|
+
} catch (error) {
|
|
142
|
+
console.error("Failed to complete passwordless flow", error);
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Basic Passkey Usage
|
|
148
|
+
|
|
149
|
+
```javascript
|
|
150
|
+
// Register a passkey with minimal options
|
|
151
|
+
const simplePasskeyRegister = async () => {
|
|
152
|
+
try {
|
|
153
|
+
await passflow.passkeyRegister({
|
|
154
|
+
relying_party_id: window.location.hostname,
|
|
155
|
+
redirect_url: window.location.origin,
|
|
156
|
+
scopes: ["id", "offline"],
|
|
157
|
+
});
|
|
158
|
+
console.log("Passkey registered");
|
|
159
|
+
} catch (error) {
|
|
160
|
+
console.error("Passkey registration failed", error);
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
// Authenticate with minimal options
|
|
165
|
+
const simplePasskeyAuthenticate = async () => {
|
|
166
|
+
try {
|
|
167
|
+
await passflow.passkeyAuthenticate({
|
|
168
|
+
relying_party_id: window.location.hostname,
|
|
169
|
+
});
|
|
170
|
+
console.log("Authenticated with passkey");
|
|
171
|
+
} catch (error) {
|
|
172
|
+
console.error("Passkey authentication failed", error);
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Quick Password Reset
|
|
178
|
+
|
|
179
|
+
```javascript
|
|
180
|
+
// Send reset email with just the email address
|
|
181
|
+
const simplePasswordReset = async () => {
|
|
182
|
+
try {
|
|
183
|
+
await passflow.sendPasswordResetEmail({
|
|
184
|
+
email: "user@example.com",
|
|
185
|
+
});
|
|
186
|
+
console.log("Password reset email sent");
|
|
187
|
+
} catch (error) {
|
|
188
|
+
console.error("Failed to send reset email", error);
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Basic Tenant Creation
|
|
194
|
+
|
|
195
|
+
```javascript
|
|
196
|
+
// Create a tenant with just a name
|
|
197
|
+
const simpleCreateTenant = async () => {
|
|
198
|
+
try {
|
|
199
|
+
await passflow.createTenant("My Organization");
|
|
200
|
+
console.log("Tenant created");
|
|
201
|
+
} catch (error) {
|
|
202
|
+
console.error("Failed to create tenant", error);
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Simple Event Subscription
|
|
208
|
+
|
|
209
|
+
```javascript
|
|
210
|
+
// Subscribe to authentication events with minimal setup
|
|
211
|
+
passflow.subscribe({
|
|
212
|
+
onAuthChange: (eventType) => {
|
|
213
|
+
console.log(`Auth event occurred: ${eventType}`);
|
|
214
|
+
},
|
|
215
|
+
});
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## Introduction
|
|
219
|
+
|
|
220
|
+
Passflow JavaScript SDK is a client library for interacting with the Passflow authentication service. It provides a comprehensive set of features for user authentication, token management, tenant management, and more. The SDK supports various authentication methods including email/password, passwordless, passkeys (WebAuthn), and federated identity providers.
|
|
221
|
+
|
|
222
|
+
## Installation
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
npm install passflow-js
|
|
226
|
+
# or
|
|
227
|
+
yarn add passflow-js
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
## Getting Started
|
|
231
|
+
|
|
232
|
+
### Initialization
|
|
233
|
+
|
|
234
|
+
Import and initialize the Passflow client with your configuration:
|
|
235
|
+
|
|
236
|
+
```javascript
|
|
237
|
+
import { Passflow } from "passflow-js";
|
|
238
|
+
|
|
239
|
+
const passflow = new Passflow({
|
|
240
|
+
url: "https://auth.passflow.cloud", // or your custom URL
|
|
241
|
+
appId: "your-app-id",
|
|
242
|
+
scopes: [
|
|
243
|
+
"id",
|
|
244
|
+
"offline",
|
|
245
|
+
"tenant",
|
|
246
|
+
"email",
|
|
247
|
+
"oidc",
|
|
248
|
+
"openid",
|
|
249
|
+
"access:tenant:all",
|
|
250
|
+
], // optional, these are the defaults
|
|
251
|
+
createTenantForNewUser: false, // optional
|
|
252
|
+
parseQueryParams: true, // optional, will parse tokens from URL query params
|
|
253
|
+
keyStoragePrefix: "myapp", // optional, prefix for localStorage keys
|
|
254
|
+
});
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### Session Management
|
|
258
|
+
|
|
259
|
+
Setup session management to handle authentication state:
|
|
260
|
+
|
|
261
|
+
```javascript
|
|
262
|
+
passflow.session({
|
|
263
|
+
createSession: (tokens) => {
|
|
264
|
+
console.log("Session created", tokens);
|
|
265
|
+
// Set your app's authenticated state
|
|
266
|
+
},
|
|
267
|
+
expiredSession: () => {
|
|
268
|
+
console.log("Session expired");
|
|
269
|
+
// Clear your app's authenticated state
|
|
270
|
+
},
|
|
271
|
+
doRefresh: true, // automatically refresh tokens when expired
|
|
272
|
+
});
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
## Authentication
|
|
276
|
+
|
|
277
|
+
### Sign In
|
|
278
|
+
|
|
279
|
+
```javascript
|
|
280
|
+
// Sign in with email and password
|
|
281
|
+
const signIn = async () => {
|
|
282
|
+
try {
|
|
283
|
+
const response = await passflow.signIn({
|
|
284
|
+
email: "user@example.com",
|
|
285
|
+
password: "password123",
|
|
286
|
+
scopes: ["id", "offline", "tenant", "email"], // optional
|
|
287
|
+
});
|
|
288
|
+
console.log("Signed in successfully", response);
|
|
289
|
+
} catch (error) {
|
|
290
|
+
console.error("Sign in failed", error);
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### Sign Up
|
|
296
|
+
|
|
297
|
+
```javascript
|
|
298
|
+
// Register a new user
|
|
299
|
+
const signUp = async () => {
|
|
300
|
+
try {
|
|
301
|
+
const response = await passflow.signUp({
|
|
302
|
+
user: {
|
|
303
|
+
email: "user@example.com",
|
|
304
|
+
password: "password123",
|
|
305
|
+
given_name: "John",
|
|
306
|
+
family_name: "Doe",
|
|
307
|
+
// Additional optional user fields
|
|
308
|
+
},
|
|
309
|
+
scopes: ["id", "offline", "tenant", "email"], // optional
|
|
310
|
+
create_tenant: true, // optional
|
|
311
|
+
});
|
|
312
|
+
console.log("Registered successfully", response);
|
|
313
|
+
} catch (error) {
|
|
314
|
+
console.error("Registration failed", error);
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
### Sign Out
|
|
320
|
+
|
|
321
|
+
```javascript
|
|
322
|
+
// Log out the current user
|
|
323
|
+
const signOut = async () => {
|
|
324
|
+
try {
|
|
325
|
+
await passflow.logOut();
|
|
326
|
+
console.log("Signed out successfully");
|
|
327
|
+
} catch (error) {
|
|
328
|
+
console.error("Sign out failed", error);
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
### Passwordless Authentication
|
|
334
|
+
|
|
335
|
+
```javascript
|
|
336
|
+
// Start passwordless authentication flow
|
|
337
|
+
const startPasswordless = async () => {
|
|
338
|
+
try {
|
|
339
|
+
const response = await passflow.passwordlessSignIn({
|
|
340
|
+
email: "user@example.com",
|
|
341
|
+
challenge_type: "otp", // or 'magic_link'
|
|
342
|
+
redirect_url: "https://yourapp.com/auth/callback",
|
|
343
|
+
});
|
|
344
|
+
console.log("Passwordless authentication started", response);
|
|
345
|
+
// Store the challenge_id for the next step
|
|
346
|
+
} catch (error) {
|
|
347
|
+
console.error("Passwordless authentication failed", error);
|
|
348
|
+
}
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
// Complete passwordless authentication flow
|
|
352
|
+
const completePasswordless = async (challengeId, otp) => {
|
|
353
|
+
try {
|
|
354
|
+
const response = await passflow.passwordlessSignInComplete({
|
|
355
|
+
challenge_id: challengeId,
|
|
356
|
+
otp: otp,
|
|
357
|
+
});
|
|
358
|
+
console.log("Passwordless authentication completed", response);
|
|
359
|
+
} catch (error) {
|
|
360
|
+
console.error("Passwordless authentication completion failed", error);
|
|
361
|
+
}
|
|
362
|
+
};
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
### Federated Authentication
|
|
366
|
+
|
|
367
|
+
```javascript
|
|
368
|
+
// Sign in with a provider using popup
|
|
369
|
+
passflow.federatedAuthWithPopup(
|
|
370
|
+
"google", // or 'facebook'
|
|
371
|
+
"https://yourapp.com/auth/callback",
|
|
372
|
+
["id", "offline", "tenant", "email"] // optional scopes
|
|
373
|
+
);
|
|
374
|
+
|
|
375
|
+
// Sign in with a provider using redirect
|
|
376
|
+
passflow.federatedAuthWithRedirect(
|
|
377
|
+
"google", // or 'facebook'
|
|
378
|
+
"https://yourapp.com/auth/callback",
|
|
379
|
+
["id", "offline", "tenant", "email"] // optional scopes
|
|
380
|
+
);
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
### Passkey Authentication
|
|
384
|
+
|
|
385
|
+
```javascript
|
|
386
|
+
// Register a new passkey
|
|
387
|
+
const registerPasskey = async () => {
|
|
388
|
+
try {
|
|
389
|
+
const response = await passflow.passkeyRegister({
|
|
390
|
+
passkey_display_name: "My Passkey",
|
|
391
|
+
passkey_username: "user@example.com",
|
|
392
|
+
relying_party_id: window.location.hostname,
|
|
393
|
+
redirect_url: "https://yourapp.com/auth/callback",
|
|
394
|
+
scopes: ["id", "offline", "tenant", "email"], // optional
|
|
395
|
+
});
|
|
396
|
+
console.log("Passkey registered successfully", response);
|
|
397
|
+
} catch (error) {
|
|
398
|
+
console.error("Passkey registration failed", error);
|
|
399
|
+
}
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
// Authenticate with a passkey
|
|
403
|
+
const authenticateWithPasskey = async () => {
|
|
404
|
+
try {
|
|
405
|
+
const response = await passflow.passkeyAuthenticate({
|
|
406
|
+
relying_party_id: window.location.hostname,
|
|
407
|
+
scopes: ["id", "offline", "tenant", "email"], // optional
|
|
408
|
+
});
|
|
409
|
+
console.log("Passkey authentication successful", response);
|
|
410
|
+
} catch (error) {
|
|
411
|
+
console.error("Passkey authentication failed", error);
|
|
412
|
+
}
|
|
413
|
+
};
|
|
414
|
+
|
|
415
|
+
// Add additional passkey to user account
|
|
416
|
+
const addPasskey = async () => {
|
|
417
|
+
try {
|
|
418
|
+
await passflow.addUserPasskey({
|
|
419
|
+
relyingPartyId: window.location.hostname,
|
|
420
|
+
passkeyUsername: "user@example.com",
|
|
421
|
+
passkeyDisplayName: "My Secondary Passkey",
|
|
422
|
+
});
|
|
423
|
+
console.log("Passkey added successfully");
|
|
424
|
+
} catch (error) {
|
|
425
|
+
console.error("Adding passkey failed", error);
|
|
426
|
+
}
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
// Manage user passkeys
|
|
430
|
+
const managePasskeys = async () => {
|
|
431
|
+
try {
|
|
432
|
+
// Get all passkeys
|
|
433
|
+
const passkeys = await passflow.getUserPasskeys();
|
|
434
|
+
console.log("User passkeys", passkeys);
|
|
435
|
+
|
|
436
|
+
// Rename a passkey
|
|
437
|
+
await passflow.renameUserPasskey("New Name", "passkey-id");
|
|
438
|
+
|
|
439
|
+
// Delete a passkey
|
|
440
|
+
await passflow.deleteUserPasskey("passkey-id");
|
|
441
|
+
} catch (error) {
|
|
442
|
+
console.error("Passkey management failed", error);
|
|
443
|
+
}
|
|
444
|
+
};
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
### Password Reset
|
|
448
|
+
|
|
449
|
+
```javascript
|
|
450
|
+
// Send password reset email
|
|
451
|
+
const sendResetEmail = async () => {
|
|
452
|
+
try {
|
|
453
|
+
await passflow.sendPasswordResetEmail({
|
|
454
|
+
email: "user@example.com",
|
|
455
|
+
reset_page_url: "https://yourapp.com/reset-password",
|
|
456
|
+
});
|
|
457
|
+
console.log("Password reset email sent");
|
|
458
|
+
} catch (error) {
|
|
459
|
+
console.error("Sending password reset email failed", error);
|
|
460
|
+
}
|
|
461
|
+
};
|
|
462
|
+
|
|
463
|
+
// Reset password (after receiving reset token)
|
|
464
|
+
const resetPassword = async (newPassword) => {
|
|
465
|
+
try {
|
|
466
|
+
// The token should be in the URL query parameters
|
|
467
|
+
const response = await passflow.resetPassword(newPassword);
|
|
468
|
+
console.log("Password reset successful", response);
|
|
469
|
+
} catch (error) {
|
|
470
|
+
console.error("Password reset failed", error);
|
|
471
|
+
}
|
|
472
|
+
};
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
## Token Management
|
|
476
|
+
|
|
477
|
+
```javascript
|
|
478
|
+
// Check if user is authenticated
|
|
479
|
+
const isAuthenticated = passflow.isAuthenticated();
|
|
480
|
+
|
|
481
|
+
// Get current tokens
|
|
482
|
+
const tokens = passflow.getTokensCache();
|
|
483
|
+
|
|
484
|
+
// Get parsed tokens (decoded JWT payload)
|
|
485
|
+
const parsedTokens = passflow.getParsedTokenCache();
|
|
486
|
+
|
|
487
|
+
// Manually refresh token
|
|
488
|
+
const refreshToken = async () => {
|
|
489
|
+
try {
|
|
490
|
+
const response = await passflow.refreshToken();
|
|
491
|
+
console.log("Token refreshed", response);
|
|
492
|
+
} catch (error) {
|
|
493
|
+
console.error("Token refresh failed", error);
|
|
494
|
+
}
|
|
495
|
+
};
|
|
496
|
+
|
|
497
|
+
// Set tokens manually
|
|
498
|
+
const setTokens = async (tokens) => {
|
|
499
|
+
try {
|
|
500
|
+
await passflow.setTokens({
|
|
501
|
+
access_token: tokens.access_token,
|
|
502
|
+
refresh_token: tokens.refresh_token,
|
|
503
|
+
id_token: tokens.id_token,
|
|
504
|
+
scopes: tokens.scopes,
|
|
505
|
+
});
|
|
506
|
+
} catch (error) {
|
|
507
|
+
console.error("Setting tokens failed", error);
|
|
508
|
+
}
|
|
509
|
+
};
|
|
510
|
+
|
|
511
|
+
// Handle tokens from redirect
|
|
512
|
+
const handleRedirect = () => {
|
|
513
|
+
const tokens = passflow.handleTokensRedirect();
|
|
514
|
+
if (tokens) {
|
|
515
|
+
console.log("Tokens received from redirect", tokens);
|
|
516
|
+
}
|
|
517
|
+
};
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
## Tenant Management
|
|
521
|
+
|
|
522
|
+
```javascript
|
|
523
|
+
// Create a new tenant
|
|
524
|
+
const createTenant = async () => {
|
|
525
|
+
try {
|
|
526
|
+
const response = await passflow.createTenant("My Organization", true);
|
|
527
|
+
console.log("Tenant created", response);
|
|
528
|
+
} catch (error) {
|
|
529
|
+
console.error("Tenant creation failed", error);
|
|
530
|
+
}
|
|
531
|
+
};
|
|
532
|
+
|
|
533
|
+
// Join a tenant via invitation
|
|
534
|
+
const joinTenant = async (invitationToken) => {
|
|
535
|
+
try {
|
|
536
|
+
const response = await passflow.joinInvitation(invitationToken);
|
|
537
|
+
console.log("Joined tenant", response);
|
|
538
|
+
} catch (error) {
|
|
539
|
+
console.error("Joining tenant failed", error);
|
|
540
|
+
}
|
|
541
|
+
};
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
## Invitation Management
|
|
545
|
+
|
|
546
|
+
```javascript
|
|
547
|
+
// Request an invitation link
|
|
548
|
+
const requestInvite = async () => {
|
|
549
|
+
try {
|
|
550
|
+
const response = await passflow.requestInviteLink({
|
|
551
|
+
email: "newuser@example.com",
|
|
552
|
+
tenant: "tenant-id", // optional
|
|
553
|
+
group: "group-id", // optional
|
|
554
|
+
role: "role-name", // optional
|
|
555
|
+
callback: "https://yourapp.com/onboarding", // optional
|
|
556
|
+
send_to_email: true, // optional
|
|
557
|
+
});
|
|
558
|
+
console.log("Invitation link created", response);
|
|
559
|
+
} catch (error) {
|
|
560
|
+
console.error("Creating invitation link failed", error);
|
|
561
|
+
}
|
|
562
|
+
};
|
|
563
|
+
|
|
564
|
+
// Get all active invitations
|
|
565
|
+
const getInvitations = async () => {
|
|
566
|
+
try {
|
|
567
|
+
const invitations = await passflow.getInvitations({
|
|
568
|
+
tenant_id: "tenant-id", // optional
|
|
569
|
+
group_id: "group-id", // optional
|
|
570
|
+
skip: 0, // optional
|
|
571
|
+
limit: 10, // optional
|
|
572
|
+
});
|
|
573
|
+
console.log("Active invitations", invitations);
|
|
574
|
+
} catch (error) {
|
|
575
|
+
console.error("Getting invitations failed", error);
|
|
576
|
+
}
|
|
577
|
+
};
|
|
578
|
+
|
|
579
|
+
// Delete an invitation
|
|
580
|
+
const deleteInvitation = async (token) => {
|
|
581
|
+
try {
|
|
582
|
+
await passflow.deleteInvitation(token);
|
|
583
|
+
console.log("Invitation deleted");
|
|
584
|
+
} catch (error) {
|
|
585
|
+
console.error("Deleting invitation failed", error);
|
|
586
|
+
}
|
|
587
|
+
};
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
## Events and Subscriptions
|
|
591
|
+
|
|
592
|
+
```javascript
|
|
593
|
+
// Define a subscriber
|
|
594
|
+
const subscriber = {
|
|
595
|
+
onAuthChange: (eventType, source) => {
|
|
596
|
+
console.log(`Auth event: ${eventType}`, source);
|
|
597
|
+
|
|
598
|
+
// Handle different event types
|
|
599
|
+
switch (eventType) {
|
|
600
|
+
case "signin":
|
|
601
|
+
// Handle sign in
|
|
602
|
+
break;
|
|
603
|
+
case "signout":
|
|
604
|
+
// Handle sign out
|
|
605
|
+
break;
|
|
606
|
+
case "register":
|
|
607
|
+
// Handle registration
|
|
608
|
+
break;
|
|
609
|
+
case "error":
|
|
610
|
+
// Handle error
|
|
611
|
+
break;
|
|
612
|
+
case "refresh":
|
|
613
|
+
// Handle token refresh
|
|
614
|
+
break;
|
|
615
|
+
}
|
|
616
|
+
},
|
|
617
|
+
};
|
|
618
|
+
|
|
619
|
+
// Subscribe to all events
|
|
620
|
+
passflow.subscribe(subscriber);
|
|
621
|
+
|
|
622
|
+
// Subscribe to specific events
|
|
623
|
+
passflow.subscribe(subscriber, ["signin", "signout"]);
|
|
624
|
+
|
|
625
|
+
// Unsubscribe from all events
|
|
626
|
+
passflow.unsubscribe(subscriber);
|
|
627
|
+
|
|
628
|
+
// Unsubscribe from specific events
|
|
629
|
+
passflow.unsubscribe(subscriber, ["error"]);
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
## Error Handling
|
|
633
|
+
|
|
634
|
+
Errors thrown by the SDK are typically instances of `PassflowError` which include details about the error:
|
|
635
|
+
|
|
636
|
+
```javascript
|
|
637
|
+
try {
|
|
638
|
+
await passflow.signIn({
|
|
639
|
+
email: "user@example.com",
|
|
640
|
+
password: "wrong-password",
|
|
641
|
+
});
|
|
642
|
+
} catch (error) {
|
|
643
|
+
if (error instanceof PassflowError) {
|
|
644
|
+
console.error(`Error ID: ${error.id}`);
|
|
645
|
+
console.error(`Error Message: ${error.message}`);
|
|
646
|
+
console.error(`Status Code: ${error.status}`);
|
|
647
|
+
console.error(`Location: ${error.location}`);
|
|
648
|
+
console.error(`Time: ${error.time}`);
|
|
649
|
+
} else {
|
|
650
|
+
console.error("Unknown error:", error);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
```
|
|
654
|
+
|
|
655
|
+
## API Reference
|
|
656
|
+
|
|
657
|
+
### Passflow Class
|
|
658
|
+
|
|
659
|
+
The main class that provides access to all functionality of the SDK.
|
|
660
|
+
|
|
661
|
+
#### Constructor
|
|
662
|
+
|
|
663
|
+
```typescript
|
|
664
|
+
constructor(config: PassflowConfig)
|
|
665
|
+
```
|
|
666
|
+
|
|
667
|
+
Configuration options:
|
|
668
|
+
|
|
669
|
+
- `url`: The URL of the Passflow service (default: 'https://auth.passflow.cloud')
|
|
670
|
+
- `appId`: Your application ID
|
|
671
|
+
- `scopes`: Token scopes to request (default: ['id', 'offline', 'tenant', 'email', 'oidc', 'openid', 'access:tenant:all'])
|
|
672
|
+
- `createTenantForNewUser`: Whether to create a tenant for new users (default: false)
|
|
673
|
+
- `parseQueryParams`: Whether to parse tokens from URL query parameters (default: false)
|
|
674
|
+
- `keyStoragePrefix`: Prefix for localStorage keys
|
|
675
|
+
|
|
676
|
+
#### Methods
|
|
677
|
+
|
|
678
|
+
**Authentication Methods**
|
|
679
|
+
|
|
680
|
+
| Method | Description |
|
|
681
|
+
| ---------------------------------------------------------- | -------------------------------------- |
|
|
682
|
+
| `session({ createSession, expiredSession, doRefresh })` | Set up session management |
|
|
683
|
+
| `signIn(payload)` | Sign in with email/password |
|
|
684
|
+
| `signUp(payload)` | Register a new user |
|
|
685
|
+
| `logOut()` | Sign out the current user |
|
|
686
|
+
| `passwordlessSignIn(payload)` | Start passwordless authentication |
|
|
687
|
+
| `passwordlessSignInComplete(payload)` | Complete passwordless authentication |
|
|
688
|
+
| `federatedAuthWithPopup(provider, redirectUrl, scopes)` | Sign in with a provider using popup |
|
|
689
|
+
| `federatedAuthWithRedirect(provider, redirectUrl, scopes)` | Sign in with a provider using redirect |
|
|
690
|
+
| `passkeyRegister(payload)` | Register a new passkey |
|
|
691
|
+
| `passkeyAuthenticate(payload)` | Authenticate with a passkey |
|
|
692
|
+
| `sendPasswordResetEmail(payload)` | Send password reset email |
|
|
693
|
+
| `resetPassword(newPassword, scopes)` | Reset password |
|
|
694
|
+
|
|
695
|
+
**Token Methods**
|
|
696
|
+
|
|
697
|
+
| Method | Description |
|
|
698
|
+
| -------------------------- | ------------------------------ |
|
|
699
|
+
| `isAuthenticated()` | Check if user is authenticated |
|
|
700
|
+
| `getTokensCache()` | Get current tokens |
|
|
701
|
+
| `getParsedTokenCache()` | Get parsed tokens |
|
|
702
|
+
| `refreshToken()` | Refresh the access token |
|
|
703
|
+
| `setTokens(tokens)` | Set tokens manually |
|
|
704
|
+
| `handleTokensRedirect()` | Handle tokens from redirect |
|
|
705
|
+
| `authRedirectUrl(options)` | Generate an auth redirect URL |
|
|
706
|
+
| `authRedirect(options)` | Redirect to the auth page |
|
|
707
|
+
|
|
708
|
+
**Tenant Methods**
|
|
709
|
+
|
|
710
|
+
| Method | Description |
|
|
711
|
+
| ---------------------------------- | ---------------------------- |
|
|
712
|
+
| `createTenant(name, refreshToken)` | Create a new tenant |
|
|
713
|
+
| `joinInvitation(token, scopes)` | Join a tenant via invitation |
|
|
714
|
+
|
|
715
|
+
**Invitation Methods**
|
|
716
|
+
|
|
717
|
+
| Method | Description |
|
|
718
|
+
| ---------------------------- | -------------------------- |
|
|
719
|
+
| `requestInviteLink(payload)` | Request an invitation link |
|
|
720
|
+
| `getInvitations(options)` | Get all active invitations |
|
|
721
|
+
| `deleteInvitation(token)` | Delete an invitation |
|
|
722
|
+
|
|
723
|
+
**Passkey Methods**
|
|
724
|
+
|
|
725
|
+
| Method | Description |
|
|
726
|
+
| ------------------------------------ | ----------------------------- |
|
|
727
|
+
| `getUserPasskeys()` | Get all user passkeys |
|
|
728
|
+
| `renameUserPasskey(name, passkeyId)` | Rename a passkey |
|
|
729
|
+
| `deleteUserPasskey(passkeyId)` | Delete a passkey |
|
|
730
|
+
| `addUserPasskey(options)` | Add a passkey to user account |
|
|
731
|
+
|
|
732
|
+
**Event Methods**
|
|
733
|
+
|
|
734
|
+
| Method | Description |
|
|
735
|
+
| --------------------------------- | ---------------------------- |
|
|
736
|
+
| `subscribe(subscriber, events)` | Subscribe to auth events |
|
|
737
|
+
| `unsubscribe(subscriber, events)` | Unsubscribe from auth events |
|
|
738
|
+
|
|
739
|
+
### Services
|
|
740
|
+
|
|
741
|
+
The SDK includes several services that handle specific functionality:
|
|
742
|
+
|
|
743
|
+
- `AuthService`: Handles authentication and session management
|
|
744
|
+
- `UserService`: Handles user-related operations
|
|
745
|
+
- `TenantService`: Handles tenant operations
|
|
746
|
+
- `InvitationService`: Handles invitation operations
|
|
747
|
+
- `DeviceService`: Manages device identification
|
|
748
|
+
- `TokenService`: Handles token parsing and validation
|
|
749
|
+
- `StorageManager`: Manages token storage
|
|
750
|
+
|
|
751
|
+
### Types
|
|
752
|
+
|
|
753
|
+
Key types used in the SDK:
|
|
754
|
+
|
|
755
|
+
#### PassflowConfig
|
|
756
|
+
|
|
757
|
+
```typescript
|
|
758
|
+
type PassflowConfig = {
|
|
759
|
+
url?: string;
|
|
760
|
+
appId?: string;
|
|
761
|
+
scopes?: string[];
|
|
762
|
+
createTenantForNewUser?: boolean;
|
|
763
|
+
parseQueryParams?: boolean;
|
|
764
|
+
keyStoragePrefix?: string;
|
|
765
|
+
};
|
|
766
|
+
```
|
|
767
|
+
|
|
768
|
+
#### Tokens
|
|
769
|
+
|
|
770
|
+
```typescript
|
|
771
|
+
type Tokens = {
|
|
772
|
+
access_token: string;
|
|
773
|
+
id_token?: string;
|
|
774
|
+
refresh_token?: string;
|
|
775
|
+
scopes?: string[];
|
|
776
|
+
};
|
|
777
|
+
```
|
|
778
|
+
|
|
779
|
+
#### Token
|
|
780
|
+
|
|
781
|
+
```typescript
|
|
782
|
+
type Token = {
|
|
783
|
+
aud: string[];
|
|
784
|
+
exp: number;
|
|
785
|
+
iat: number;
|
|
786
|
+
iss: string;
|
|
787
|
+
jti: string;
|
|
788
|
+
sub: string;
|
|
789
|
+
type: string;
|
|
790
|
+
email?: string;
|
|
791
|
+
passflow_tm?: RawUserMembership;
|
|
792
|
+
payload?: unknown;
|
|
793
|
+
membership?: UserMembership;
|
|
794
|
+
};
|
|
795
|
+
```
|
|
796
|
+
|
|
797
|
+
#### PassflowSignInPayload
|
|
798
|
+
|
|
799
|
+
```typescript
|
|
800
|
+
type PassflowSignInPayload = {
|
|
801
|
+
password: string;
|
|
802
|
+
scopes?: string[];
|
|
803
|
+
email?: string;
|
|
804
|
+
phone?: string;
|
|
805
|
+
username?: string;
|
|
806
|
+
} & ({ email: string } | { phone: string } | { username: string });
|
|
807
|
+
```
|
|
808
|
+
|
|
809
|
+
#### PassflowSignUpPayload
|
|
810
|
+
|
|
811
|
+
```typescript
|
|
812
|
+
type PassflowSignUpPayload = {
|
|
813
|
+
user: PassflowUserPayload;
|
|
814
|
+
scopes?: string[];
|
|
815
|
+
create_tenant?: boolean;
|
|
816
|
+
anonymous?: boolean;
|
|
817
|
+
invite?: string;
|
|
818
|
+
};
|
|
819
|
+
```
|
|
820
|
+
|
|
821
|
+
#### UserMembership
|
|
822
|
+
|
|
823
|
+
```typescript
|
|
824
|
+
type UserMembership = {
|
|
825
|
+
raw: RawUserMembership;
|
|
826
|
+
tenants: TenantMembership[];
|
|
827
|
+
};
|
|
828
|
+
```
|
|
829
|
+
|
|
830
|
+
#### PassflowEvent
|
|
831
|
+
|
|
832
|
+
```typescript
|
|
833
|
+
enum PassflowEvent {
|
|
834
|
+
SignIn = "signin",
|
|
835
|
+
Register = "register",
|
|
836
|
+
SignOut = "signout",
|
|
837
|
+
Error = "error",
|
|
838
|
+
Refresh = "refresh",
|
|
839
|
+
}
|
|
840
|
+
```
|
|
841
|
+
|
|
842
|
+
## Detailed Examples
|
|
843
|
+
|
|
844
|
+
### Complete Sign In Flow
|
|
845
|
+
|
|
846
|
+
```javascript
|
|
847
|
+
import { Passflow, PassflowError } from "passflow-js";
|
|
848
|
+
|
|
849
|
+
// Initialize Passflow
|
|
850
|
+
const passflow = new Passflow({
|
|
851
|
+
appId: "your-app-id",
|
|
852
|
+
parseQueryParams: true,
|
|
853
|
+
});
|
|
854
|
+
|
|
855
|
+
// Set up session management
|
|
856
|
+
passflow.session({
|
|
857
|
+
createSession: (tokens) => {
|
|
858
|
+
// Store authentication state
|
|
859
|
+
localStorage.setItem("isAuthenticated", "true");
|
|
860
|
+
|
|
861
|
+
// Update UI
|
|
862
|
+
document.getElementById("login-form").style.display = "none";
|
|
863
|
+
document.getElementById("user-dashboard").style.display = "block";
|
|
864
|
+
},
|
|
865
|
+
expiredSession: () => {
|
|
866
|
+
// Clear authentication state
|
|
867
|
+
localStorage.removeItem("isAuthenticated");
|
|
868
|
+
|
|
869
|
+
// Update UI
|
|
870
|
+
document.getElementById("login-form").style.display = "block";
|
|
871
|
+
document.getElementById("user-dashboard").style.display = "none";
|
|
872
|
+
},
|
|
873
|
+
doRefresh: true,
|
|
874
|
+
});
|
|
875
|
+
|
|
876
|
+
// Handle form submission
|
|
877
|
+
document
|
|
878
|
+
.getElementById("login-form")
|
|
879
|
+
.addEventListener("submit", async (event) => {
|
|
880
|
+
event.preventDefault();
|
|
881
|
+
|
|
882
|
+
const email = document.getElementById("email").value;
|
|
883
|
+
const password = document.getElementById("password").value;
|
|
884
|
+
|
|
885
|
+
try {
|
|
886
|
+
await passflow.signIn({ email, password });
|
|
887
|
+
// Login successful - session callback will handle UI update
|
|
888
|
+
} catch (error) {
|
|
889
|
+
if (error instanceof PassflowError) {
|
|
890
|
+
document.getElementById("error-message").textContent = error.message;
|
|
891
|
+
} else {
|
|
892
|
+
document.getElementById("error-message").textContent =
|
|
893
|
+
"An unexpected error occurred";
|
|
894
|
+
console.error(error);
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
});
|
|
898
|
+
|
|
899
|
+
// Handle logout
|
|
900
|
+
document.getElementById("logout-button").addEventListener("click", async () => {
|
|
901
|
+
await passflow.logOut();
|
|
902
|
+
// Logout successful - session callback will handle UI update
|
|
903
|
+
});
|
|
904
|
+
|
|
905
|
+
// Check for redirect tokens on page load
|
|
906
|
+
window.addEventListener("load", () => {
|
|
907
|
+
const tokens = passflow.handleTokensRedirect();
|
|
908
|
+
if (tokens) {
|
|
909
|
+
console.log("Authenticated via redirect");
|
|
910
|
+
}
|
|
911
|
+
});
|
|
912
|
+
```
|
|
913
|
+
|
|
914
|
+
### Authentication with Passkeys
|
|
915
|
+
|
|
916
|
+
```javascript
|
|
917
|
+
import { Passflow } from "passflow-js";
|
|
918
|
+
|
|
919
|
+
// Initialize Passflow
|
|
920
|
+
const passflow = new Passflow({
|
|
921
|
+
appId: "your-app-id",
|
|
922
|
+
});
|
|
923
|
+
|
|
924
|
+
// Set up session management
|
|
925
|
+
passflow.session({
|
|
926
|
+
createSession: (tokens) => {
|
|
927
|
+
console.log("Session created", tokens);
|
|
928
|
+
},
|
|
929
|
+
expiredSession: () => {
|
|
930
|
+
console.log("Session expired");
|
|
931
|
+
},
|
|
932
|
+
doRefresh: true,
|
|
933
|
+
});
|
|
934
|
+
|
|
935
|
+
// Handle passkey registration
|
|
936
|
+
document
|
|
937
|
+
.getElementById("register-passkey-button")
|
|
938
|
+
.addEventListener("click", async () => {
|
|
939
|
+
try {
|
|
940
|
+
const email = document.getElementById("email").value;
|
|
941
|
+
|
|
942
|
+
await passflow.passkeyRegister({
|
|
943
|
+
passkey_display_name: "My Passkey",
|
|
944
|
+
passkey_username: email,
|
|
945
|
+
relying_party_id: window.location.hostname,
|
|
946
|
+
redirect_url: window.location.origin,
|
|
947
|
+
scopes: ["id", "offline", "tenant", "email"],
|
|
948
|
+
});
|
|
949
|
+
|
|
950
|
+
alert("Passkey registered successfully!");
|
|
951
|
+
} catch (error) {
|
|
952
|
+
alert(`Passkey registration failed: ${error.message}`);
|
|
953
|
+
console.error(error);
|
|
954
|
+
}
|
|
955
|
+
});
|
|
956
|
+
|
|
957
|
+
// Handle passkey authentication
|
|
958
|
+
document
|
|
959
|
+
.getElementById("login-with-passkey-button")
|
|
960
|
+
.addEventListener("click", async () => {
|
|
961
|
+
try {
|
|
962
|
+
await passflow.passkeyAuthenticate({
|
|
963
|
+
relying_party_id: window.location.hostname,
|
|
964
|
+
});
|
|
965
|
+
|
|
966
|
+
alert("Authenticated with passkey successfully!");
|
|
967
|
+
} catch (error) {
|
|
968
|
+
alert(`Passkey authentication failed: ${error.message}`);
|
|
969
|
+
console.error(error);
|
|
970
|
+
}
|
|
971
|
+
});
|
|
972
|
+
```
|
|
973
|
+
|
|
974
|
+
### Multi-tenant Application
|
|
975
|
+
|
|
976
|
+
```javascript
|
|
977
|
+
import { Passflow, PassflowEvent } from "passflow-js";
|
|
978
|
+
|
|
979
|
+
// Initialize Passflow
|
|
980
|
+
const passflow = new Passflow({
|
|
981
|
+
appId: "your-app-id",
|
|
982
|
+
});
|
|
983
|
+
|
|
984
|
+
// Track current tenant
|
|
985
|
+
let currentTenant = null;
|
|
986
|
+
|
|
987
|
+
// Set up session management
|
|
988
|
+
passflow.session({
|
|
989
|
+
createSession: (tokens) => {
|
|
990
|
+
const parsedTokens = passflow.getParsedTokenCache();
|
|
991
|
+
if (parsedTokens?.access_token?.membership?.tenants?.length > 0) {
|
|
992
|
+
currentTenant = parsedTokens.access_token.membership.tenants[0];
|
|
993
|
+
updateTenantUI();
|
|
994
|
+
} else {
|
|
995
|
+
showCreateTenantUI();
|
|
996
|
+
}
|
|
997
|
+
},
|
|
998
|
+
expiredSession: () => {
|
|
999
|
+
currentTenant = null;
|
|
1000
|
+
showLoginUI();
|
|
1001
|
+
},
|
|
1002
|
+
doRefresh: true,
|
|
1003
|
+
});
|
|
1004
|
+
|
|
1005
|
+
// Subscribe to auth events
|
|
1006
|
+
passflow.subscribe({
|
|
1007
|
+
onAuthChange: (eventType) => {
|
|
1008
|
+
if (
|
|
1009
|
+
eventType === PassflowEvent.SignIn ||
|
|
1010
|
+
eventType === PassflowEvent.Register
|
|
1011
|
+
) {
|
|
1012
|
+
const parsedTokens = passflow.getParsedTokenCache();
|
|
1013
|
+
if (parsedTokens?.access_token?.membership?.tenants?.length > 0) {
|
|
1014
|
+
currentTenant = parsedTokens.access_token.membership.tenants[0];
|
|
1015
|
+
updateTenantUI();
|
|
1016
|
+
} else {
|
|
1017
|
+
showCreateTenantUI();
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
},
|
|
1021
|
+
});
|
|
1022
|
+
|
|
1023
|
+
// Create tenant function
|
|
1024
|
+
async function createNewTenant() {
|
|
1025
|
+
const tenantName = document.getElementById("tenant-name").value;
|
|
1026
|
+
try {
|
|
1027
|
+
await passflow.createTenant(tenantName, true);
|
|
1028
|
+
const parsedTokens = passflow.getParsedTokenCache();
|
|
1029
|
+
currentTenant = parsedTokens.access_token.membership.tenants[0];
|
|
1030
|
+
updateTenantUI();
|
|
1031
|
+
} catch (error) {
|
|
1032
|
+
console.error("Failed to create tenant", error);
|
|
1033
|
+
alert(`Failed to create tenant: ${error.message}`);
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
// Invite user function
|
|
1038
|
+
async function inviteUser() {
|
|
1039
|
+
const email = document.getElementById("invite-email").value;
|
|
1040
|
+
try {
|
|
1041
|
+
const response = await passflow.requestInviteLink({
|
|
1042
|
+
email,
|
|
1043
|
+
tenant: currentTenant.tenant.id,
|
|
1044
|
+
send_to_email: true,
|
|
1045
|
+
});
|
|
1046
|
+
alert(`Invitation sent to ${email}`);
|
|
1047
|
+
console.log("Invitation link:", response.link);
|
|
1048
|
+
} catch (error) {
|
|
1049
|
+
console.error("Failed to invite user", error);
|
|
1050
|
+
alert(`Failed to invite user: ${error.message}`);
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
// UI update functions
|
|
1055
|
+
function updateTenantUI() {
|
|
1056
|
+
document.getElementById("tenant-name-display").textContent =
|
|
1057
|
+
currentTenant.tenant.name;
|
|
1058
|
+
document.getElementById("tenant-id-display").textContent =
|
|
1059
|
+
currentTenant.tenant.id;
|
|
1060
|
+
document.getElementById("tenant-section").style.display = "block";
|
|
1061
|
+
document.getElementById("create-tenant-section").style.display = "none";
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
function showCreateTenantUI() {
|
|
1065
|
+
document.getElementById("tenant-section").style.display = "none";
|
|
1066
|
+
document.getElementById("create-tenant-section").style.display = "block";
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
function showLoginUI() {
|
|
1070
|
+
document.getElementById("tenant-section").style.display = "none";
|
|
1071
|
+
document.getElementById("create-tenant-section").style.display = "none";
|
|
1072
|
+
document.getElementById("login-section").style.display = "block";
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
// Event listeners
|
|
1076
|
+
document
|
|
1077
|
+
.getElementById("create-tenant-form")
|
|
1078
|
+
.addEventListener("submit", (e) => {
|
|
1079
|
+
e.preventDefault();
|
|
1080
|
+
createNewTenant();
|
|
1081
|
+
});
|
|
1082
|
+
|
|
1083
|
+
document.getElementById("invite-form").addEventListener("submit", (e) => {
|
|
1084
|
+
e.preventDefault();
|
|
1085
|
+
inviteUser();
|
|
1086
|
+
});
|
|
1087
|
+
```
|