passkey-magic 0.1.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 +302 -0
- package/dist/adapters/memory.d.mts +10 -0
- package/dist/adapters/memory.mjs +142 -0
- package/dist/adapters/unstorage.d.mts +30 -0
- package/dist/adapters/unstorage.mjs +238 -0
- package/dist/better-auth/client.d.mts +9 -0
- package/dist/better-auth/client.mjs +7 -0
- package/dist/better-auth/index.d.mts +2 -0
- package/dist/better-auth/index.mjs +4044 -0
- package/dist/client/index.d.mts +167 -0
- package/dist/client/index.mjs +166 -0
- package/dist/crypto-KHRNe6EL.mjs +45 -0
- package/dist/index-BoHvgaqz.d.mts +2816 -0
- package/dist/index-Cqqpr_uS.d.mts +176 -0
- package/dist/nitro/index.d.mts +89 -0
- package/dist/nitro/index.mjs +117 -0
- package/dist/server/index.d.mts +3 -0
- package/dist/server/index.mjs +3 -0
- package/dist/server-BXkm8lU0.mjs +823 -0
- package/dist/types-BjM1f6uu.d.mts +249 -0
- package/package.json +74 -0
package/README.md
ADDED
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
# passkey-magic
|
|
2
|
+
|
|
3
|
+
Passkey-first authentication with QR cross-device login and magic link fallback.
|
|
4
|
+
|
|
5
|
+
- **Passkeys (WebAuthn)** — Register and sign in with biometrics, security keys, or platform authenticators
|
|
6
|
+
- **QR Cross-Device** — Scan a QR code on your phone to log in on desktop
|
|
7
|
+
- **Magic Links** — Email-based passwordless fallback
|
|
8
|
+
- **Framework Agnostic** — Works with any JavaScript runtime (Node.js, Bun, Deno, Cloudflare Workers)
|
|
9
|
+
- **better-auth Plugin** — Drop-in integration with [better-auth](https://github.com/better-auth/better-auth)
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install passkey-magic
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
### Server
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
import { createAuth } from 'passkey-magic/server'
|
|
23
|
+
import { memoryAdapter } from 'passkey-magic/adapters/memory'
|
|
24
|
+
|
|
25
|
+
const auth = createAuth({
|
|
26
|
+
rpName: 'My App',
|
|
27
|
+
rpID: 'example.com',
|
|
28
|
+
origin: 'https://example.com',
|
|
29
|
+
storage: memoryAdapter(),
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
// Use as a Web Standard Request handler
|
|
33
|
+
export default {
|
|
34
|
+
fetch: auth.createHandler({ pathPrefix: '/auth' })
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Client
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
import { createClient } from 'passkey-magic/client'
|
|
42
|
+
|
|
43
|
+
const auth = createClient({
|
|
44
|
+
request: (endpoint, body) =>
|
|
45
|
+
fetch(`/auth${endpoint}`, {
|
|
46
|
+
method: 'POST',
|
|
47
|
+
headers: { 'Content-Type': 'application/json' },
|
|
48
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
49
|
+
}).then(r => r.json()),
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
// Register a passkey
|
|
53
|
+
const { user, session } = await auth.registerPasskey({ email: 'user@example.com' })
|
|
54
|
+
|
|
55
|
+
// Sign in
|
|
56
|
+
const result = await auth.signInWithPasskey()
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Features
|
|
60
|
+
|
|
61
|
+
### Passkey Authentication
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
// Server: generate options, verify response
|
|
65
|
+
const { options, userId } = await auth.generateRegistrationOptions({ email: 'user@example.com' })
|
|
66
|
+
const result = await auth.verifyRegistration({ userId, response: browserResponse })
|
|
67
|
+
|
|
68
|
+
// Authentication
|
|
69
|
+
const { options } = await auth.generateAuthenticationOptions()
|
|
70
|
+
const { user, session } = await auth.verifyAuthentication({ response: browserResponse })
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### QR Cross-Device Login
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
// Desktop: create session and display QR code
|
|
77
|
+
const { sessionId } = await auth.createQRSession()
|
|
78
|
+
const qrSvg = client.renderQR(`https://example.com/auth/qr/${sessionId}`)
|
|
79
|
+
|
|
80
|
+
// Desktop: poll for completion
|
|
81
|
+
for await (const status of client.pollQRSession(sessionId)) {
|
|
82
|
+
if (status.state === 'authenticated') {
|
|
83
|
+
// User logged in from their phone
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Mobile: complete the session
|
|
88
|
+
await client.completeQRSession({ sessionId })
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Magic Links
|
|
92
|
+
|
|
93
|
+
Enable by providing an email adapter:
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
const auth = createAuth({
|
|
97
|
+
// ...webauthn config
|
|
98
|
+
storage: memoryAdapter(),
|
|
99
|
+
email: {
|
|
100
|
+
async sendMagicLink(email, url, token) {
|
|
101
|
+
await sendEmail({ to: email, subject: 'Login', html: `<a href="${url}">Log in</a>` })
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
magicLinkURL: 'https://example.com/auth/verify',
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
// Send a magic link
|
|
108
|
+
await auth.sendMagicLink({ email: 'user@example.com' })
|
|
109
|
+
|
|
110
|
+
// Verify (after user clicks the link)
|
|
111
|
+
const { user, session, isNewUser } = await auth.verifyMagicLink({ token })
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Passkey Management
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
// Add a passkey to an existing account
|
|
118
|
+
const { options } = await auth.addPasskey({ userId })
|
|
119
|
+
const { credential } = await auth.verifyAddPasskey({ userId, response: browserResponse })
|
|
120
|
+
|
|
121
|
+
// List, update, remove
|
|
122
|
+
const credentials = await auth.getUserCredentials(userId)
|
|
123
|
+
await auth.updateCredential({ credentialId: 'cred_123', label: 'iPhone' })
|
|
124
|
+
await auth.removeCredential('cred_123')
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Session Management
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
const result = await auth.validateSession(token) // { user, session } | null
|
|
131
|
+
const sessions = await auth.getUserSessions(userId)
|
|
132
|
+
await auth.revokeSession(token)
|
|
133
|
+
await auth.revokeAllSessions(userId)
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Lifecycle Hooks
|
|
137
|
+
|
|
138
|
+
```ts
|
|
139
|
+
const auth = createAuth({
|
|
140
|
+
// ...config
|
|
141
|
+
hooks: {
|
|
142
|
+
async beforeRegister({ email }) {
|
|
143
|
+
if (await isBlocked(email)) return false // abort
|
|
144
|
+
},
|
|
145
|
+
async afterAuthenticate({ user, session }) {
|
|
146
|
+
await logLogin(user.id)
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
})
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Events
|
|
153
|
+
|
|
154
|
+
```ts
|
|
155
|
+
auth.on('session:created', ({ session, user, method }) => { /* ... */ })
|
|
156
|
+
auth.on('credential:created', ({ credential, user }) => { /* ... */ })
|
|
157
|
+
auth.on('user:created', ({ user }) => { /* ... */ })
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Storage Adapters
|
|
161
|
+
|
|
162
|
+
### Memory (development)
|
|
163
|
+
|
|
164
|
+
```ts
|
|
165
|
+
import { memoryAdapter } from 'passkey-magic/adapters/memory'
|
|
166
|
+
|
|
167
|
+
const storage = memoryAdapter()
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Unstorage (production)
|
|
171
|
+
|
|
172
|
+
Works with any [unstorage](https://unstorage.unjs.io) driver (Redis, Vercel KV, Cloudflare KV, filesystem, etc.):
|
|
173
|
+
|
|
174
|
+
```ts
|
|
175
|
+
import { unstorageAdapter } from 'passkey-magic/adapters/unstorage'
|
|
176
|
+
import { createStorage } from 'unstorage'
|
|
177
|
+
import redisDriver from 'unstorage/drivers/redis'
|
|
178
|
+
|
|
179
|
+
const storage = unstorageAdapter(
|
|
180
|
+
createStorage({ driver: redisDriver({ url: 'redis://localhost:6379' }) }),
|
|
181
|
+
{ base: 'auth' }
|
|
182
|
+
)
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Custom Adapter
|
|
186
|
+
|
|
187
|
+
Implement the `StorageAdapter` interface for any database:
|
|
188
|
+
|
|
189
|
+
```ts
|
|
190
|
+
import type { StorageAdapter } from 'passkey-magic/server'
|
|
191
|
+
|
|
192
|
+
const myAdapter: StorageAdapter = {
|
|
193
|
+
createUser(user) { /* ... */ },
|
|
194
|
+
getUserById(id) { /* ... */ },
|
|
195
|
+
// ... see StorageAdapter interface for all methods
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## Integrations
|
|
200
|
+
|
|
201
|
+
### Nitro
|
|
202
|
+
|
|
203
|
+
```ts
|
|
204
|
+
import { passkeyMagic, useAuth } from 'passkey-magic/nitro'
|
|
205
|
+
|
|
206
|
+
export default defineNitroPlugin(() => {
|
|
207
|
+
passkeyMagic({
|
|
208
|
+
rpName: 'My App',
|
|
209
|
+
rpID: 'example.com',
|
|
210
|
+
origin: 'https://example.com',
|
|
211
|
+
pathPrefix: '/auth',
|
|
212
|
+
}).setup(nitroApp)
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
// In route handlers:
|
|
216
|
+
const auth = useAuth()
|
|
217
|
+
const session = await auth.validateSession(token)
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### better-auth
|
|
221
|
+
|
|
222
|
+
Use passkey-magic as a [better-auth](https://www.better-auth.com) plugin. All data is stored in better-auth's database, and sessions are unified with better-auth's session system.
|
|
223
|
+
|
|
224
|
+
#### Server
|
|
225
|
+
|
|
226
|
+
```ts
|
|
227
|
+
import { betterAuth } from 'better-auth'
|
|
228
|
+
import { passkeyMagicPlugin } from 'passkey-magic/better-auth'
|
|
229
|
+
|
|
230
|
+
const auth = betterAuth({
|
|
231
|
+
database: myAdapter,
|
|
232
|
+
plugins: [
|
|
233
|
+
passkeyMagicPlugin({
|
|
234
|
+
rpName: 'My App',
|
|
235
|
+
rpID: 'example.com',
|
|
236
|
+
origin: 'https://example.com',
|
|
237
|
+
}),
|
|
238
|
+
],
|
|
239
|
+
})
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
#### Client
|
|
243
|
+
|
|
244
|
+
```ts
|
|
245
|
+
import { createAuthClient } from 'better-auth/client'
|
|
246
|
+
import { passkeyMagicClientPlugin } from 'passkey-magic/better-auth/client'
|
|
247
|
+
|
|
248
|
+
const auth = createAuthClient({
|
|
249
|
+
plugins: [passkeyMagicClientPlugin()],
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
// All endpoints are type-safe:
|
|
253
|
+
await auth.passkeyMagic.register.options({ email: 'user@example.com' })
|
|
254
|
+
await auth.passkeyMagic.qr.create()
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
#### Plugin Endpoints
|
|
258
|
+
|
|
259
|
+
All endpoints are prefixed with `/passkey-magic/`:
|
|
260
|
+
|
|
261
|
+
| Endpoint | Method | Auth | Description |
|
|
262
|
+
|---|---|---|---|
|
|
263
|
+
| `/register/options` | POST | No | Generate passkey registration options |
|
|
264
|
+
| `/register/verify` | POST | No | Verify registration and create session |
|
|
265
|
+
| `/authenticate/options` | POST | No | Generate authentication options |
|
|
266
|
+
| `/authenticate/verify` | POST | No | Verify authentication and create session |
|
|
267
|
+
| `/add/options` | POST | Yes | Add passkey to existing account |
|
|
268
|
+
| `/add/verify` | POST | Yes | Verify added passkey |
|
|
269
|
+
| `/credentials` | GET | Yes | List user's passkeys |
|
|
270
|
+
| `/credentials/update` | POST | Yes | Update passkey label |
|
|
271
|
+
| `/credentials/remove` | POST | Yes | Remove a passkey |
|
|
272
|
+
| `/qr/create` | POST | No | Create QR login session |
|
|
273
|
+
| `/qr/status` | GET | No | Poll QR session status |
|
|
274
|
+
| `/qr/scanned` | POST | No | Mark QR session as scanned |
|
|
275
|
+
| `/qr/complete` | POST | No | Complete QR auth and create session |
|
|
276
|
+
| `/magic-link/send` | POST | No | Send magic link email |
|
|
277
|
+
| `/magic-link/verify` | POST | No | Verify magic link and create session |
|
|
278
|
+
|
|
279
|
+
The plugin creates 4 database tables (`passkeyCredential`, `qrSession`, `passkeyChallenge`, `magicLinkToken`) and manages them through better-auth's adapter. Authentication endpoints create proper better-auth sessions with cookies.
|
|
280
|
+
|
|
281
|
+
## Configuration
|
|
282
|
+
|
|
283
|
+
```ts
|
|
284
|
+
interface AuthConfig {
|
|
285
|
+
rpName: string // Relying party name (shown in passkey prompts)
|
|
286
|
+
rpID: string // Relying party ID (your domain)
|
|
287
|
+
origin: string | string[] // Expected origin(s) for WebAuthn
|
|
288
|
+
storage: StorageAdapter // Persistence layer
|
|
289
|
+
email?: EmailAdapter // Enables magic links
|
|
290
|
+
magicLinkURL?: string // Base URL for magic link emails
|
|
291
|
+
sessionTTL?: number // Default: 7 days (ms)
|
|
292
|
+
challengeTTL?: number // Default: 60 seconds (ms)
|
|
293
|
+
magicLinkTTL?: number // Default: 15 minutes (ms)
|
|
294
|
+
qrSessionTTL?: number // Default: 5 minutes (ms)
|
|
295
|
+
generateId?: () => string // Default: crypto.randomUUID()
|
|
296
|
+
hooks?: AuthHooks // Lifecycle hooks
|
|
297
|
+
}
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
## License
|
|
301
|
+
|
|
302
|
+
MIT
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { m as StorageAdapter } from "../types-BjM1f6uu.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/adapters/memory.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* In-memory storage adapter for development and testing.
|
|
6
|
+
* Data is not persisted across restarts.
|
|
7
|
+
*/
|
|
8
|
+
declare function memoryAdapter(): StorageAdapter;
|
|
9
|
+
//#endregion
|
|
10
|
+
export { memoryAdapter };
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { i as timingSafeEqual } from "../crypto-KHRNe6EL.mjs";
|
|
2
|
+
//#region src/adapters/memory.ts
|
|
3
|
+
/**
|
|
4
|
+
* In-memory storage adapter for development and testing.
|
|
5
|
+
* Data is not persisted across restarts.
|
|
6
|
+
*/
|
|
7
|
+
function memoryAdapter() {
|
|
8
|
+
const users = /* @__PURE__ */ new Map();
|
|
9
|
+
const credentials = /* @__PURE__ */ new Map();
|
|
10
|
+
const sessions = /* @__PURE__ */ new Map();
|
|
11
|
+
const challenges = /* @__PURE__ */ new Map();
|
|
12
|
+
const magicLinks = /* @__PURE__ */ new Map();
|
|
13
|
+
const qrSessions = /* @__PURE__ */ new Map();
|
|
14
|
+
function isExpired(entry) {
|
|
15
|
+
return !entry || Date.now() > entry.expiresAt;
|
|
16
|
+
}
|
|
17
|
+
return {
|
|
18
|
+
async createUser(user) {
|
|
19
|
+
users.set(user.id, { ...user });
|
|
20
|
+
return { ...user };
|
|
21
|
+
},
|
|
22
|
+
async getUserById(id) {
|
|
23
|
+
const user = users.get(id);
|
|
24
|
+
return user ? { ...user } : null;
|
|
25
|
+
},
|
|
26
|
+
async getUserByEmail(email) {
|
|
27
|
+
for (const user of users.values()) if (user.email === email) return { ...user };
|
|
28
|
+
return null;
|
|
29
|
+
},
|
|
30
|
+
async updateUser(id, update) {
|
|
31
|
+
const user = users.get(id);
|
|
32
|
+
if (!user) throw new Error(`User not found: ${id}`);
|
|
33
|
+
const updated = {
|
|
34
|
+
...user,
|
|
35
|
+
...update
|
|
36
|
+
};
|
|
37
|
+
users.set(id, updated);
|
|
38
|
+
return { ...updated };
|
|
39
|
+
},
|
|
40
|
+
async deleteUser(id) {
|
|
41
|
+
users.delete(id);
|
|
42
|
+
},
|
|
43
|
+
async createCredential(cred) {
|
|
44
|
+
credentials.set(cred.id, { ...cred });
|
|
45
|
+
return { ...cred };
|
|
46
|
+
},
|
|
47
|
+
async getCredentialById(id) {
|
|
48
|
+
const cred = credentials.get(id);
|
|
49
|
+
return cred ? { ...cred } : null;
|
|
50
|
+
},
|
|
51
|
+
async getCredentialsByUserId(userId) {
|
|
52
|
+
const result = [];
|
|
53
|
+
for (const cred of credentials.values()) if (cred.userId === userId) result.push({ ...cred });
|
|
54
|
+
return result;
|
|
55
|
+
},
|
|
56
|
+
async updateCredential(id, update) {
|
|
57
|
+
const cred = credentials.get(id);
|
|
58
|
+
if (!cred) throw new Error(`Credential not found: ${id}`);
|
|
59
|
+
if (update.counter !== void 0) cred.counter = update.counter;
|
|
60
|
+
if (update.label !== void 0) cred.label = update.label;
|
|
61
|
+
},
|
|
62
|
+
async deleteCredential(id) {
|
|
63
|
+
credentials.delete(id);
|
|
64
|
+
},
|
|
65
|
+
async createSession(session) {
|
|
66
|
+
sessions.set(session.id, { ...session });
|
|
67
|
+
return { ...session };
|
|
68
|
+
},
|
|
69
|
+
async getSessionByToken(token) {
|
|
70
|
+
for (const session of sessions.values()) if (await timingSafeEqual(session.token, token)) {
|
|
71
|
+
if (/* @__PURE__ */ new Date() > session.expiresAt) {
|
|
72
|
+
sessions.delete(session.id);
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
return { ...session };
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
},
|
|
79
|
+
async getSessionsByUserId(userId) {
|
|
80
|
+
const result = [];
|
|
81
|
+
for (const session of sessions.values()) if (session.userId === userId) if (/* @__PURE__ */ new Date() > session.expiresAt) sessions.delete(session.id);
|
|
82
|
+
else result.push({ ...session });
|
|
83
|
+
return result;
|
|
84
|
+
},
|
|
85
|
+
async deleteSession(id) {
|
|
86
|
+
sessions.delete(id);
|
|
87
|
+
},
|
|
88
|
+
async deleteSessionsByUserId(userId) {
|
|
89
|
+
for (const [id, session] of sessions) if (session.userId === userId) sessions.delete(id);
|
|
90
|
+
},
|
|
91
|
+
async storeChallenge(key, challenge, ttlMs) {
|
|
92
|
+
challenges.set(key, {
|
|
93
|
+
value: challenge,
|
|
94
|
+
expiresAt: Date.now() + ttlMs
|
|
95
|
+
});
|
|
96
|
+
},
|
|
97
|
+
async getChallenge(key) {
|
|
98
|
+
const entry = challenges.get(key);
|
|
99
|
+
if (isExpired(entry)) {
|
|
100
|
+
challenges.delete(key);
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
return entry.value;
|
|
104
|
+
},
|
|
105
|
+
async deleteChallenge(key) {
|
|
106
|
+
challenges.delete(key);
|
|
107
|
+
},
|
|
108
|
+
async storeMagicLink(token, email, ttlMs) {
|
|
109
|
+
magicLinks.set(token, {
|
|
110
|
+
value: { email },
|
|
111
|
+
expiresAt: Date.now() + ttlMs
|
|
112
|
+
});
|
|
113
|
+
},
|
|
114
|
+
async getMagicLink(token) {
|
|
115
|
+
const entry = magicLinks.get(token);
|
|
116
|
+
if (isExpired(entry)) {
|
|
117
|
+
magicLinks.delete(token);
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
return entry.value;
|
|
121
|
+
},
|
|
122
|
+
async deleteMagicLink(token) {
|
|
123
|
+
magicLinks.delete(token);
|
|
124
|
+
},
|
|
125
|
+
async createQRSession(session) {
|
|
126
|
+
qrSessions.set(session.id, { ...session });
|
|
127
|
+
return { ...session };
|
|
128
|
+
},
|
|
129
|
+
async getQRSession(id) {
|
|
130
|
+
const session = qrSessions.get(id);
|
|
131
|
+
if (!session) return null;
|
|
132
|
+
if (/* @__PURE__ */ new Date() > session.expiresAt && session.state === "pending") session.state = "expired";
|
|
133
|
+
return { ...session };
|
|
134
|
+
},
|
|
135
|
+
async updateQRSession(id, update) {
|
|
136
|
+
const session = qrSessions.get(id);
|
|
137
|
+
if (session) Object.assign(session, update);
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
//#endregion
|
|
142
|
+
export { memoryAdapter };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { m as StorageAdapter } from "../types-BjM1f6uu.mjs";
|
|
2
|
+
import { Storage } from "unstorage";
|
|
3
|
+
|
|
4
|
+
//#region src/adapters/unstorage.d.ts
|
|
5
|
+
/** Options for the unstorage adapter. */
|
|
6
|
+
interface UnstorageAdapterOptions {
|
|
7
|
+
/** Key prefix for all auth data. Defaults to `"auth"`. */
|
|
8
|
+
base?: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Storage adapter backed by [unstorage](https://unstorage.unjs.io).
|
|
12
|
+
* Works with any unstorage driver (memory, redis, fs, cloudflare-kv, etc.).
|
|
13
|
+
*
|
|
14
|
+
* Key schema:
|
|
15
|
+
* ```
|
|
16
|
+
* {base}:user:{id} → User
|
|
17
|
+
* {base}:user-email:{email} → userId (secondary index)
|
|
18
|
+
* {base}:cred:{id} → Credential
|
|
19
|
+
* {base}:user-creds:{userId} → credentialId[] (index)
|
|
20
|
+
* {base}:session:{id} → Session
|
|
21
|
+
* {base}:session-token:{token} → sessionId (secondary index)
|
|
22
|
+
* {base}:user-sessions:{userId} → sessionId[] (index)
|
|
23
|
+
* {base}:challenge:{key} → { challenge, expiresAt }
|
|
24
|
+
* {base}:magic-link:{token} → { email, expiresAt }
|
|
25
|
+
* {base}:qr:{id} → QRSession
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
declare function unstorageAdapter(storage: Storage, options?: UnstorageAdapterOptions): StorageAdapter;
|
|
29
|
+
//#endregion
|
|
30
|
+
export { UnstorageAdapterOptions, unstorageAdapter };
|