@lenne.tech/nuxt-extensions 1.14.0 → 1.15.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/CLAUDE.md +10 -1
- package/README.md +34 -2
- package/dist/module.d.mts +1 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +11 -7
- package/dist/runtime/lib/auth-client.d.ts +35 -104
- package/dist/runtime/lib/auth-client.js +11 -22
- package/dist/runtime/lib/passkey-stub.d.ts +93 -0
- package/dist/runtime/lib/passkey-stub.js +46 -6
- package/package.json +3 -3
package/CLAUDE.md
CHANGED
|
@@ -259,5 +259,14 @@ fields at `app:beforeMount` and restores them at `app:mounted`, dispatching a sy
|
|
|
259
259
|
1. **ALWAYS read source code** in `dist/runtime/` to understand available composables and components
|
|
260
260
|
2. **Use `useLtAuth()`** for authentication — never implement auth manually
|
|
261
261
|
3. **Check existing composables** before creating new ones — this module may already provide what you need
|
|
262
|
-
4. **Peer dependencies** (`better-auth`, `@better-auth/passkey`, `tus-js-client`) must be installed in the consuming project
|
|
262
|
+
4. **Peer dependencies** (`better-auth`, `@better-auth/passkey`, `tus-js-client`) must be installed in the consuming project.
|
|
263
|
+
`better-auth` and `@better-auth/passkey` are pinned to **`>=1.7.1 <1.8.0`** — deliberately not a caret.
|
|
264
|
+
**They move in lock-step with `@lenne.tech/nest-server`.** better-auth is one protocol with two ends;
|
|
265
|
+
this module is the client end, nest-server the server end, and both declare the *same* narrow range.
|
|
266
|
+
Raising it means raising it in **both** framework repos and pinning the new version in **both** starters,
|
|
267
|
+
in one release. Never half of it, not even "just to unblock the frontend" — a split resolves as api on one
|
|
268
|
+
minor and app on another, 2FA activation fails at runtime, and *both* repos' `check` stays green because
|
|
269
|
+
each is internally consistent and only the assembled workspace has both halves.
|
|
270
|
+
`test/peer-dependency-ranges.test.ts` guards the declared range against the version actually tested;
|
|
271
|
+
`test/better-auth-contract.test.ts` pins the consumed better-auth surface.
|
|
263
272
|
5. **AI types collision pitfall:** `LtAiPromptInput` is the CRUD input for the user-facing `LtAiPrompt` entity (used by `useLtAiPrompts().create/update`). The execution payload for `useLtAi.prompt()` / `.promptStream()` is `LtAiPromptRunInput`. Do not conflate them.
|
package/README.md
CHANGED
|
@@ -23,16 +23,48 @@ Reusable Nuxt 4 composables, components, and Better-Auth integration for lenne.t
|
|
|
23
23
|
> npx @lenne.tech/cli fullstack init my-project
|
|
24
24
|
> ```
|
|
25
25
|
|
|
26
|
+
## Requirements
|
|
27
|
+
|
|
28
|
+
| Dependency | Required version | Notes |
|
|
29
|
+
|---|---|---|
|
|
30
|
+
| Node | `>= 22` | |
|
|
31
|
+
| Nuxt | `^4.0.0` | |
|
|
32
|
+
| `better-auth` | `>=1.7.1 <1.8.0` | **required peer** — pin it, see below |
|
|
33
|
+
| `@better-auth/passkey` | `>=1.7.1 <1.8.0` | optional peer, passkey/WebAuthn only |
|
|
34
|
+
| `tus-js-client` | `>=4.0.0` | optional peer, resumable uploads only |
|
|
35
|
+
| `@lenne.tech/nest-server` | `>= 11.37.0` | **only if you use the backend** — see the lock-step rule |
|
|
36
|
+
|
|
37
|
+
**Why `better-auth` is pinned to one minor line, and why you must not widen it:**
|
|
38
|
+
better-auth is one protocol with two ends. This module is the client end and
|
|
39
|
+
`@lenne.tech/nest-server` is the server end, and they must resolve the **same**
|
|
40
|
+
better-auth minor. better-auth breaks in *minor* releases — 1.7 removed subpath
|
|
41
|
+
exports and changed the 2FA response shape — so a caret (`^1.7.1`) would re-open the
|
|
42
|
+
hole at 1.8. If the api and the app drift apart, 2FA activation fails at runtime with a
|
|
43
|
+
generic client error while **both** projects' checks stay green: each is internally
|
|
44
|
+
consistent, and only the assembled workspace has both halves.
|
|
45
|
+
|
|
46
|
+
Raising better-auth therefore means raising it in `@lenne.tech/nest-server` **and**
|
|
47
|
+
`@lenne.tech/nuxt-extensions` in one step.
|
|
48
|
+
|
|
26
49
|
## Installation
|
|
27
50
|
|
|
28
51
|
```bash
|
|
29
|
-
npm install @lenne.tech/nuxt-extensions better-auth
|
|
52
|
+
npm install @lenne.tech/nuxt-extensions better-auth@1.7.1
|
|
30
53
|
# Optional: For passkey support
|
|
31
|
-
npm install @better-auth/passkey
|
|
54
|
+
npm install @better-auth/passkey@1.7.1
|
|
32
55
|
# Optional: For TUS file uploads
|
|
33
56
|
npm install tus-js-client
|
|
34
57
|
```
|
|
35
58
|
|
|
59
|
+
In a fullstack monorepo, pin it workspace-wide so the api and the app cannot drift:
|
|
60
|
+
|
|
61
|
+
```yaml
|
|
62
|
+
# pnpm-workspace.yaml
|
|
63
|
+
overrides:
|
|
64
|
+
better-auth: 1.7.1
|
|
65
|
+
'@better-auth/passkey': 1.7.1
|
|
66
|
+
```
|
|
67
|
+
|
|
36
68
|
## Features
|
|
37
69
|
|
|
38
70
|
- **Better-Auth Integration** - Login, 2FA, Passkey/WebAuthn support
|
package/dist/module.d.mts
CHANGED
|
@@ -3,7 +3,7 @@ import { LtExtensionsModuleOptions } from '../dist/runtime/types/index.js';
|
|
|
3
3
|
export * from '../dist/runtime/types/index.js';
|
|
4
4
|
|
|
5
5
|
declare const name = "@lenne.tech/nuxt-extensions";
|
|
6
|
-
declare const version = "1.
|
|
6
|
+
declare const version = "1.15.0";
|
|
7
7
|
declare const configKey = "ltExtensions";
|
|
8
8
|
declare const DEFAULT_LT_AUTH_STATE_COOKIE = "lt-auth-state";
|
|
9
9
|
declare const DEFAULT_LT_JWT_TOKEN_COOKIE = "lt-jwt-token";
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { defineNuxtModule, createResolver, tryResolveModule, addImports, addComponent, addPlugin, addRouteMiddleware } from '@nuxt/kit';
|
|
2
|
+
import { pathToFileURL } from 'node:url';
|
|
2
3
|
|
|
3
4
|
const name = "@lenne.tech/nuxt-extensions";
|
|
4
|
-
const version = "1.
|
|
5
|
+
const version = "1.15.0";
|
|
5
6
|
const configKey = "ltExtensions";
|
|
6
7
|
const DEFAULT_LT_AUTH_STATE_COOKIE = "lt-auth-state";
|
|
7
8
|
const DEFAULT_LT_JWT_TOKEN_COOKIE = "lt-jwt-token";
|
|
@@ -74,7 +75,7 @@ const module$1 = defineNuxtModule({
|
|
|
74
75
|
preHydrationInput: { ...defaultOptions.preHydrationInput, ...options.preHydrationInput },
|
|
75
76
|
tus: { ...defaultOptions.tus, ...options.tus }
|
|
76
77
|
};
|
|
77
|
-
const passkeyAvailable = !!await tryResolveModule("@better-auth/passkey/client", new URL(import.meta.url));
|
|
78
|
+
const passkeyAvailable = !!await tryResolveModule("@better-auth/passkey/client", [new URL(import.meta.url), pathToFileURL(`${nuxt.options.rootDir}/`)]);
|
|
78
79
|
if (!passkeyAvailable) {
|
|
79
80
|
const passkeyStub = resolve("./runtime/lib/passkey-stub");
|
|
80
81
|
nuxt.options.alias["@better-auth/passkey/client"] = passkeyStub;
|
|
@@ -233,11 +234,14 @@ const module$1 = defineNuxtModule({
|
|
|
233
234
|
}
|
|
234
235
|
nuxt.options.build.transpile.push(resolve("./runtime"));
|
|
235
236
|
nuxt.options.build.transpile.push("tus-js-client");
|
|
236
|
-
console.log(`[${name}]
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
237
|
+
console.log(`[${name}] v${version}`);
|
|
238
|
+
if (nuxt.options.dev) {
|
|
239
|
+
console.log(`[${name}] Module loaded with config:`, {
|
|
240
|
+
auth: resolvedOptions.auth?.enabled ? "enabled" : "disabled",
|
|
241
|
+
i18nAutoMerge: resolvedOptions.i18n?.autoMerge,
|
|
242
|
+
tusEndpoint: resolvedOptions.tus?.defaultEndpoint
|
|
243
|
+
});
|
|
244
|
+
}
|
|
241
245
|
}
|
|
242
246
|
});
|
|
243
247
|
|
|
@@ -184,8 +184,8 @@ export declare function createLtAuthClient(config?: LtAuthClientConfig): {
|
|
|
184
184
|
}>;
|
|
185
185
|
}>;
|
|
186
186
|
};
|
|
187
|
-
passkey:
|
|
188
|
-
admin:
|
|
187
|
+
passkey: Record<string, unknown> | undefined;
|
|
188
|
+
admin: Record<string, unknown> | undefined;
|
|
189
189
|
$Infer: {
|
|
190
190
|
Session: {
|
|
191
191
|
user: import("better-auth").StripEmptyObjects<{
|
|
@@ -302,36 +302,7 @@ export declare function createLtAuthClient(config?: LtAuthClientConfig): {
|
|
|
302
302
|
changePassword: (params: {
|
|
303
303
|
currentPassword: string;
|
|
304
304
|
newPassword: string;
|
|
305
|
-
}, options?:
|
|
306
|
-
token: string | null;
|
|
307
|
-
user: {
|
|
308
|
-
id: string;
|
|
309
|
-
createdAt: Date;
|
|
310
|
-
updatedAt: Date;
|
|
311
|
-
email: string;
|
|
312
|
-
emailVerified: boolean;
|
|
313
|
-
name: string;
|
|
314
|
-
image?: string | null | undefined;
|
|
315
|
-
} & Record<string, any> & {
|
|
316
|
-
id: string;
|
|
317
|
-
createdAt: Date;
|
|
318
|
-
updatedAt: Date;
|
|
319
|
-
email: string;
|
|
320
|
-
emailVerified: boolean;
|
|
321
|
-
name: string;
|
|
322
|
-
image?: string | null | undefined;
|
|
323
|
-
};
|
|
324
|
-
}, "user"> & {
|
|
325
|
-
user: import("better-auth").StripEmptyObjects<{
|
|
326
|
-
id: string;
|
|
327
|
-
createdAt: Date;
|
|
328
|
-
updatedAt: Date;
|
|
329
|
-
email: string;
|
|
330
|
-
emailVerified: boolean;
|
|
331
|
-
name: string;
|
|
332
|
-
image?: string | null | undefined;
|
|
333
|
-
}>;
|
|
334
|
-
}) | {
|
|
305
|
+
}, options?: unknown) => Promise<{
|
|
335
306
|
data: Omit<{
|
|
336
307
|
token: string | null;
|
|
337
308
|
user: {
|
|
@@ -378,9 +349,7 @@ export declare function createLtAuthClient(config?: LtAuthClientConfig): {
|
|
|
378
349
|
resetPassword: (params: {
|
|
379
350
|
newPassword: string;
|
|
380
351
|
token: string;
|
|
381
|
-
}, options?:
|
|
382
|
-
status: boolean;
|
|
383
|
-
} | {
|
|
352
|
+
}, options?: unknown) => Promise<{
|
|
384
353
|
data: {
|
|
385
354
|
status: boolean;
|
|
386
355
|
};
|
|
@@ -402,30 +371,7 @@ export declare function createLtAuthClient(config?: LtAuthClientConfig): {
|
|
|
402
371
|
email: string;
|
|
403
372
|
password: string;
|
|
404
373
|
rememberMe?: boolean;
|
|
405
|
-
}, options?:
|
|
406
|
-
redirect: boolean;
|
|
407
|
-
token: string;
|
|
408
|
-
url?: string | undefined;
|
|
409
|
-
user: {
|
|
410
|
-
id: string;
|
|
411
|
-
createdAt: Date;
|
|
412
|
-
updatedAt: Date;
|
|
413
|
-
email: string;
|
|
414
|
-
emailVerified: boolean;
|
|
415
|
-
name: string;
|
|
416
|
-
image?: string | null | undefined | undefined;
|
|
417
|
-
};
|
|
418
|
-
}, "user"> & {
|
|
419
|
-
user: import("better-auth").StripEmptyObjects<{
|
|
420
|
-
id: string;
|
|
421
|
-
createdAt: Date;
|
|
422
|
-
updatedAt: Date;
|
|
423
|
-
email: string;
|
|
424
|
-
emailVerified: boolean;
|
|
425
|
-
name: string;
|
|
426
|
-
image?: string | null | undefined;
|
|
427
|
-
}>;
|
|
428
|
-
}) | {
|
|
374
|
+
}, options?: unknown) => Promise<{
|
|
429
375
|
data: Omit<{
|
|
430
376
|
redirect: boolean;
|
|
431
377
|
token: string;
|
|
@@ -464,7 +410,7 @@ export declare function createLtAuthClient(config?: LtAuthClientConfig): {
|
|
|
464
410
|
* Sign in with passkey (pass through to base client - provided by passkeyClient plugin)
|
|
465
411
|
* @see https://www.better-auth.com/docs/plugins/passkey
|
|
466
412
|
*/
|
|
467
|
-
passkey:
|
|
413
|
+
passkey: ((...args: unknown[]) => Promise<unknown>) | undefined;
|
|
468
414
|
social: <FetchOptions extends import("better-auth").ClientFetchOption<Partial<{
|
|
469
415
|
provider: (string & {}) | "linear" | "huggingface" | "github" | "apple" | "atlassian" | "cognito" | "discord" | "facebook" | "figma" | "microsoft" | "google" | "slack" | "spotify" | "twitch" | "twitter" | "dropbox" | "kick" | "linkedin" | "gitlab" | "tiktok" | "reddit" | "roblox" | "salesforce" | "vk" | "zoom" | "notion" | "kakao" | "naver" | "line" | "paybin" | "paypal" | "polar" | "railway" | "vercel" | "wechat";
|
|
470
416
|
callbackURL?: string | undefined;
|
|
@@ -577,49 +523,7 @@ export declare function createLtAuthClient(config?: LtAuthClientConfig): {
|
|
|
577
523
|
email: string;
|
|
578
524
|
name: string;
|
|
579
525
|
password: string;
|
|
580
|
-
} & Record<string, unknown>, options?:
|
|
581
|
-
token: null;
|
|
582
|
-
user: {
|
|
583
|
-
id: string;
|
|
584
|
-
createdAt: Date;
|
|
585
|
-
updatedAt: Date;
|
|
586
|
-
email: string;
|
|
587
|
-
emailVerified: boolean;
|
|
588
|
-
name: string;
|
|
589
|
-
image?: string | null | undefined | undefined;
|
|
590
|
-
};
|
|
591
|
-
}, "user"> & {
|
|
592
|
-
user: import("better-auth").StripEmptyObjects<{
|
|
593
|
-
id: string;
|
|
594
|
-
createdAt: Date;
|
|
595
|
-
updatedAt: Date;
|
|
596
|
-
email: string;
|
|
597
|
-
emailVerified: boolean;
|
|
598
|
-
name: string;
|
|
599
|
-
image?: string | null | undefined;
|
|
600
|
-
}>;
|
|
601
|
-
}) | (Omit<{
|
|
602
|
-
token: string;
|
|
603
|
-
user: {
|
|
604
|
-
id: string;
|
|
605
|
-
createdAt: Date;
|
|
606
|
-
updatedAt: Date;
|
|
607
|
-
email: string;
|
|
608
|
-
emailVerified: boolean;
|
|
609
|
-
name: string;
|
|
610
|
-
image?: string | null | undefined | undefined;
|
|
611
|
-
};
|
|
612
|
-
}, "user"> & {
|
|
613
|
-
user: import("better-auth").StripEmptyObjects<{
|
|
614
|
-
id: string;
|
|
615
|
-
createdAt: Date;
|
|
616
|
-
updatedAt: Date;
|
|
617
|
-
email: string;
|
|
618
|
-
emailVerified: boolean;
|
|
619
|
-
name: string;
|
|
620
|
-
image?: string | null | undefined;
|
|
621
|
-
}>;
|
|
622
|
-
}) | {
|
|
526
|
+
} & Record<string, unknown>, options?: unknown) => Promise<{
|
|
623
527
|
data: (Omit<{
|
|
624
528
|
token: null;
|
|
625
529
|
user: {
|
|
@@ -674,7 +578,34 @@ export declare function createLtAuthClient(config?: LtAuthClientConfig): {
|
|
|
674
578
|
};
|
|
675
579
|
}>;
|
|
676
580
|
};
|
|
677
|
-
twoFactor:
|
|
581
|
+
twoFactor: {
|
|
582
|
+
/**
|
|
583
|
+
* Disable 2FA (password is hashed before sending)
|
|
584
|
+
*/
|
|
585
|
+
disable: (params: {
|
|
586
|
+
password: string;
|
|
587
|
+
}, options?: unknown) => Promise<unknown>;
|
|
588
|
+
/**
|
|
589
|
+
* Enable 2FA (password is hashed before sending)
|
|
590
|
+
*/
|
|
591
|
+
enable: (params: {
|
|
592
|
+
password: string;
|
|
593
|
+
}, options?: unknown) => Promise<unknown>;
|
|
594
|
+
/**
|
|
595
|
+
* Generate backup codes (password is hashed before sending)
|
|
596
|
+
*/
|
|
597
|
+
generateBackupCodes: (params: {
|
|
598
|
+
password: string;
|
|
599
|
+
}, options?: unknown) => Promise<unknown>;
|
|
600
|
+
/**
|
|
601
|
+
* Verify TOTP code (pass through to base client)
|
|
602
|
+
*/
|
|
603
|
+
verifyTotp: ((...args: unknown[]) => Promise<unknown>) | undefined;
|
|
604
|
+
/**
|
|
605
|
+
* Verify backup code (pass through to base client)
|
|
606
|
+
*/
|
|
607
|
+
verifyBackupCode: ((...args: unknown[]) => Promise<unknown>) | undefined;
|
|
608
|
+
};
|
|
678
609
|
verifyEmail: <FetchOptions extends import("better-auth").ClientFetchOption<never, Partial<{
|
|
679
610
|
token: string;
|
|
680
611
|
callbackURL?: string | undefined;
|
|
@@ -78,15 +78,15 @@ export function createLtAuthClient(config = {}) {
|
|
|
78
78
|
},
|
|
79
79
|
plugins
|
|
80
80
|
});
|
|
81
|
+
const withPlugins = baseClient;
|
|
82
|
+
const signInWithPlugins = baseClient.signIn;
|
|
81
83
|
return {
|
|
82
84
|
// Spread all base client properties and methods
|
|
83
85
|
...baseClient,
|
|
84
86
|
// Explicitly pass through methods not captured by spread operator
|
|
85
87
|
useSession: baseClient.useSession,
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
89
|
-
admin: baseClient.admin,
|
|
88
|
+
passkey: withPlugins.passkey,
|
|
89
|
+
admin: withPlugins.admin,
|
|
90
90
|
$Infer: baseClient.$Infer,
|
|
91
91
|
$fetch: baseClient.$fetch,
|
|
92
92
|
$store: baseClient.$store,
|
|
@@ -94,7 +94,6 @@ export function createLtAuthClient(config = {}) {
|
|
|
94
94
|
/**
|
|
95
95
|
* Change password for an authenticated user (both passwords are hashed)
|
|
96
96
|
*/
|
|
97
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
98
97
|
changePassword: async (params, options) => {
|
|
99
98
|
const [hashedCurrent, hashedNew] = await Promise.all([ltSha256(params.currentPassword), ltSha256(params.newPassword)]);
|
|
100
99
|
return baseClient.changePassword?.({ currentPassword: hashedCurrent, newPassword: hashedNew }, options);
|
|
@@ -102,7 +101,6 @@ export function createLtAuthClient(config = {}) {
|
|
|
102
101
|
/**
|
|
103
102
|
* Reset password with token (new password is hashed before sending)
|
|
104
103
|
*/
|
|
105
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
106
104
|
resetPassword: async (params, options) => {
|
|
107
105
|
const hashedPassword = await ltSha256(params.newPassword);
|
|
108
106
|
return baseClient.resetPassword?.({ newPassword: hashedPassword, token: params.token }, options);
|
|
@@ -113,7 +111,6 @@ export function createLtAuthClient(config = {}) {
|
|
|
113
111
|
/**
|
|
114
112
|
* Sign in with email and password (password is hashed before sending)
|
|
115
113
|
*/
|
|
116
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
117
114
|
email: async (params, options) => {
|
|
118
115
|
const hashedPassword = await ltSha256(params.password);
|
|
119
116
|
return baseClient.signIn.email({ ...params, password: hashedPassword }, options);
|
|
@@ -122,8 +119,7 @@ export function createLtAuthClient(config = {}) {
|
|
|
122
119
|
* Sign in with passkey (pass through to base client - provided by passkeyClient plugin)
|
|
123
120
|
* @see https://www.better-auth.com/docs/plugins/passkey
|
|
124
121
|
*/
|
|
125
|
-
|
|
126
|
-
passkey: baseClient.signIn.passkey
|
|
122
|
+
passkey: signInWithPlugins.passkey
|
|
127
123
|
},
|
|
128
124
|
// Explicitly pass through signOut (not captured by spread operator)
|
|
129
125
|
signOut: baseClient.signOut,
|
|
@@ -133,7 +129,6 @@ export function createLtAuthClient(config = {}) {
|
|
|
133
129
|
/**
|
|
134
130
|
* Sign up with email and password (password is hashed before sending)
|
|
135
131
|
*/
|
|
136
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
137
132
|
email: async (params, options) => {
|
|
138
133
|
const hashedPassword = await ltSha256(params.password);
|
|
139
134
|
return baseClient.signUp.email({ ...params, password: hashedPassword }, options);
|
|
@@ -141,42 +136,36 @@ export function createLtAuthClient(config = {}) {
|
|
|
141
136
|
},
|
|
142
137
|
// Override twoFactor to hash passwords (provided by twoFactorClient plugin)
|
|
143
138
|
twoFactor: {
|
|
144
|
-
|
|
145
|
-
...baseClient.twoFactor,
|
|
139
|
+
...withPlugins.twoFactor,
|
|
146
140
|
/**
|
|
147
141
|
* Disable 2FA (password is hashed before sending)
|
|
148
142
|
*/
|
|
149
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
150
143
|
disable: async (params, options) => {
|
|
151
144
|
const hashedPassword = await ltSha256(params.password);
|
|
152
|
-
return
|
|
145
|
+
return withPlugins.twoFactor?.disable({ password: hashedPassword }, options);
|
|
153
146
|
},
|
|
154
147
|
/**
|
|
155
148
|
* Enable 2FA (password is hashed before sending)
|
|
156
149
|
*/
|
|
157
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
158
150
|
enable: async (params, options) => {
|
|
159
151
|
const hashedPassword = await ltSha256(params.password);
|
|
160
|
-
return
|
|
152
|
+
return withPlugins.twoFactor?.enable({ password: hashedPassword }, options);
|
|
161
153
|
},
|
|
162
154
|
/**
|
|
163
155
|
* Generate backup codes (password is hashed before sending)
|
|
164
156
|
*/
|
|
165
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
166
157
|
generateBackupCodes: async (params, options) => {
|
|
167
158
|
const hashedPassword = await ltSha256(params.password);
|
|
168
|
-
return
|
|
159
|
+
return withPlugins.twoFactor?.generateBackupCodes({ password: hashedPassword }, options);
|
|
169
160
|
},
|
|
170
161
|
/**
|
|
171
162
|
* Verify TOTP code (pass through to base client)
|
|
172
163
|
*/
|
|
173
|
-
|
|
174
|
-
verifyTotp: baseClient.twoFactor.verifyTotp,
|
|
164
|
+
verifyTotp: withPlugins.twoFactor?.verifyTotp,
|
|
175
165
|
/**
|
|
176
166
|
* Verify backup code (pass through to base client)
|
|
177
167
|
*/
|
|
178
|
-
|
|
179
|
-
verifyBackupCode: baseClient.twoFactor.verifyBackupCode
|
|
168
|
+
verifyBackupCode: withPlugins.twoFactor?.verifyBackupCode
|
|
180
169
|
}
|
|
181
170
|
};
|
|
182
171
|
}
|
|
@@ -11,6 +11,16 @@
|
|
|
11
11
|
* so the factory below is normally never called. It stays a no-op (rather than
|
|
12
12
|
* throwing) to keep the direct `createLtAuthClient()` entry point working, whose
|
|
13
13
|
* own `enablePasskey` default is `true`.
|
|
14
|
+
*
|
|
15
|
+
* WHY this file exports more than `passkeyClient`:
|
|
16
|
+
* the alias is installed into `nuxt.options.alias` and `nuxt.options.nitro.alias`,
|
|
17
|
+
* which rewrites the specifier for the WHOLE consumer app — not just for our
|
|
18
|
+
* `auth-client.ts`. A consumer (or another dependency) importing any other name
|
|
19
|
+
* from `@better-auth/passkey/client` would then hit
|
|
20
|
+
* `"PASSKEY_ERROR_CODES" is not exported by …/passkey-stub` — the same class of
|
|
21
|
+
* unresolved-export build failure this stub exists to prevent, merely relocated.
|
|
22
|
+
* So the stub mirrors the real package's full export surface.
|
|
23
|
+
* `test/optional-peers.test.ts` pins that surface against the real package.
|
|
14
24
|
*/
|
|
15
25
|
/**
|
|
16
26
|
* No-op replacement for the real `passkeyClient()` Better-Auth client plugin.
|
|
@@ -21,3 +31,86 @@
|
|
|
21
31
|
export declare function passkeyClient(): {
|
|
22
32
|
id: "lt-passkey-unavailable";
|
|
23
33
|
};
|
|
34
|
+
/**
|
|
35
|
+
* Mirror of the real package's error-code table.
|
|
36
|
+
*
|
|
37
|
+
* Frozen and value-complete so a consumer that reads a code for a message or an
|
|
38
|
+
* equality check still gets a defined value instead of a `TypeError` on
|
|
39
|
+
* `undefined`. The codes are the contract; the messages are best-effort copies.
|
|
40
|
+
*/
|
|
41
|
+
export declare const PASSKEY_ERROR_CODES: Readonly<{
|
|
42
|
+
AUTH_CANCELLED: {
|
|
43
|
+
code: string;
|
|
44
|
+
message: string;
|
|
45
|
+
};
|
|
46
|
+
AUTHENTICATION_FAILED: {
|
|
47
|
+
code: string;
|
|
48
|
+
message: string;
|
|
49
|
+
};
|
|
50
|
+
CHALLENGE_NOT_FOUND: {
|
|
51
|
+
code: string;
|
|
52
|
+
message: string;
|
|
53
|
+
};
|
|
54
|
+
FAILED_TO_UPDATE_PASSKEY: {
|
|
55
|
+
code: string;
|
|
56
|
+
message: string;
|
|
57
|
+
};
|
|
58
|
+
FAILED_TO_VERIFY_REGISTRATION: {
|
|
59
|
+
code: string;
|
|
60
|
+
message: string;
|
|
61
|
+
};
|
|
62
|
+
PASSKEY_NOT_FOUND: {
|
|
63
|
+
code: string;
|
|
64
|
+
message: string;
|
|
65
|
+
};
|
|
66
|
+
PREVIOUSLY_REGISTERED: {
|
|
67
|
+
code: string;
|
|
68
|
+
message: string;
|
|
69
|
+
};
|
|
70
|
+
REGISTRATION_CANCELLED: {
|
|
71
|
+
code: string;
|
|
72
|
+
message: string;
|
|
73
|
+
};
|
|
74
|
+
RESOLVE_USER_REQUIRED: {
|
|
75
|
+
code: string;
|
|
76
|
+
message: string;
|
|
77
|
+
};
|
|
78
|
+
RESOLVED_USER_INVALID: {
|
|
79
|
+
code: string;
|
|
80
|
+
message: string;
|
|
81
|
+
};
|
|
82
|
+
SESSION_REQUIRED: {
|
|
83
|
+
code: string;
|
|
84
|
+
message: string;
|
|
85
|
+
};
|
|
86
|
+
UNABLE_TO_CREATE_SESSION: {
|
|
87
|
+
code: string;
|
|
88
|
+
message: string;
|
|
89
|
+
};
|
|
90
|
+
UNKNOWN_ERROR: {
|
|
91
|
+
code: string;
|
|
92
|
+
message: string;
|
|
93
|
+
};
|
|
94
|
+
USER_NOT_FOUND: {
|
|
95
|
+
code: string;
|
|
96
|
+
message: string;
|
|
97
|
+
};
|
|
98
|
+
YOU_ARE_NOT_ALLOWED_TO_REGISTER_THIS_PASSKEY: {
|
|
99
|
+
code: string;
|
|
100
|
+
message: string;
|
|
101
|
+
};
|
|
102
|
+
}>;
|
|
103
|
+
/**
|
|
104
|
+
* No-op replacement for the real `getPasskeyActions()`.
|
|
105
|
+
*
|
|
106
|
+
* Every action rejects rather than resolving falsely: a passkey ceremony that
|
|
107
|
+
* silently "succeeds" without the package would leave the caller believing a
|
|
108
|
+
* credential exists. The rejection names the missing dependency.
|
|
109
|
+
*/
|
|
110
|
+
export declare function getPasskeyActions(..._args: unknown[]): {
|
|
111
|
+
addPasskey: () => Promise<never>;
|
|
112
|
+
deletePasskey: () => Promise<never>;
|
|
113
|
+
listUserPasskeys: () => Promise<never>;
|
|
114
|
+
signInPasskey: () => Promise<never>;
|
|
115
|
+
updatePasskey: () => Promise<never>;
|
|
116
|
+
};
|
|
@@ -1,10 +1,50 @@
|
|
|
1
1
|
let warned = false;
|
|
2
|
-
|
|
3
|
-
if (
|
|
4
|
-
|
|
5
|
-
console.warn(
|
|
6
|
-
'[@lenne.tech/nuxt-extensions] Passkey support is unavailable: the optional peer dependency "@better-auth/passkey" is not installed. Install it to use passkeys, or pass `enablePasskey: false` to createLtAuthClient() to silence this warning.'
|
|
7
|
-
);
|
|
2
|
+
function warnOnce(entryPoint) {
|
|
3
|
+
if (warned) {
|
|
4
|
+
return;
|
|
8
5
|
}
|
|
6
|
+
warned = true;
|
|
7
|
+
console.warn(
|
|
8
|
+
`[@lenne.tech/nuxt-extensions] Passkey support is unavailable: the optional peer dependency "@better-auth/passkey" is not installed. Install it to use passkeys, or pass \`enablePasskey: false\` to createLtAuthClient() to silence this warning. (reached via ${entryPoint})`
|
|
9
|
+
);
|
|
10
|
+
}
|
|
11
|
+
export function passkeyClient() {
|
|
12
|
+
warnOnce("passkeyClient()");
|
|
9
13
|
return { id: "lt-passkey-unavailable" };
|
|
10
14
|
}
|
|
15
|
+
export const PASSKEY_ERROR_CODES = Object.freeze({
|
|
16
|
+
AUTH_CANCELLED: { code: "AUTH_CANCELLED", message: "Auth cancelled" },
|
|
17
|
+
AUTHENTICATION_FAILED: { code: "AUTHENTICATION_FAILED", message: "Authentication failed" },
|
|
18
|
+
CHALLENGE_NOT_FOUND: { code: "CHALLENGE_NOT_FOUND", message: "Challenge not found" },
|
|
19
|
+
FAILED_TO_UPDATE_PASSKEY: { code: "FAILED_TO_UPDATE_PASSKEY", message: "Failed to update passkey" },
|
|
20
|
+
FAILED_TO_VERIFY_REGISTRATION: { code: "FAILED_TO_VERIFY_REGISTRATION", message: "Failed to verify registration" },
|
|
21
|
+
PASSKEY_NOT_FOUND: { code: "PASSKEY_NOT_FOUND", message: "Passkey not found" },
|
|
22
|
+
PREVIOUSLY_REGISTERED: { code: "PREVIOUSLY_REGISTERED", message: "Previously registered" },
|
|
23
|
+
REGISTRATION_CANCELLED: { code: "REGISTRATION_CANCELLED", message: "Registration cancelled" },
|
|
24
|
+
RESOLVE_USER_REQUIRED: {
|
|
25
|
+
code: "RESOLVE_USER_REQUIRED",
|
|
26
|
+
message: "Passkey registration requires either an authenticated session or a resolveUser callback when requireSession is false"
|
|
27
|
+
},
|
|
28
|
+
RESOLVED_USER_INVALID: { code: "RESOLVED_USER_INVALID", message: "Resolved user is invalid" },
|
|
29
|
+
SESSION_REQUIRED: { code: "SESSION_REQUIRED", message: "Passkey registration requires an authenticated session" },
|
|
30
|
+
UNABLE_TO_CREATE_SESSION: { code: "UNABLE_TO_CREATE_SESSION", message: "Unable to create session" },
|
|
31
|
+
UNKNOWN_ERROR: { code: "UNKNOWN_ERROR", message: "Unknown error" },
|
|
32
|
+
USER_NOT_FOUND: { code: "USER_NOT_FOUND", message: "User not found" },
|
|
33
|
+
YOU_ARE_NOT_ALLOWED_TO_REGISTER_THIS_PASSKEY: {
|
|
34
|
+
code: "YOU_ARE_NOT_ALLOWED_TO_REGISTER_THIS_PASSKEY",
|
|
35
|
+
message: "You are not allowed to register this passkey"
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
export function getPasskeyActions(..._args) {
|
|
39
|
+
warnOnce("getPasskeyActions()");
|
|
40
|
+
const unavailable = async () => {
|
|
41
|
+
throw new Error('[@lenne.tech/nuxt-extensions] Passkey action unavailable: the optional peer dependency "@better-auth/passkey" is not installed.');
|
|
42
|
+
};
|
|
43
|
+
return {
|
|
44
|
+
addPasskey: unavailable,
|
|
45
|
+
deletePasskey: unavailable,
|
|
46
|
+
listUserPasskeys: unavailable,
|
|
47
|
+
signInPasskey: unavailable,
|
|
48
|
+
updatePasskey: unavailable
|
|
49
|
+
};
|
|
50
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lenne.tech/nuxt-extensions",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.15.0",
|
|
4
4
|
"description": "Reusable Nuxt 4 composables, components, and Better-Auth integration for lenne.tech projects",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -78,9 +78,9 @@
|
|
|
78
78
|
"@nuxt/kit": "^4.0.0"
|
|
79
79
|
},
|
|
80
80
|
"peerDependencies": {
|
|
81
|
-
"@better-auth/passkey": ">=1.
|
|
81
|
+
"@better-auth/passkey": ">=1.7.1 <1.8.0",
|
|
82
82
|
"@playwright/test": ">=1.0.0",
|
|
83
|
-
"better-auth": ">=1.
|
|
83
|
+
"better-auth": ">=1.7.1 <1.8.0",
|
|
84
84
|
"nuxt": "^4.0.0",
|
|
85
85
|
"tus-js-client": ">=4.0.0"
|
|
86
86
|
},
|