create-nuxt-base 1.1.2 → 2.0.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.
Files changed (39) hide show
  1. package/.github/workflows/publish.yml +1 -1
  2. package/AUTH.md +16 -14
  3. package/CHANGELOG.md +46 -11
  4. package/README.md +5 -5
  5. package/nuxt-base-template/README.md +11 -11
  6. package/nuxt-base-template/app/components/Modal/ModalBackupCodes.vue +2 -1
  7. package/nuxt-base-template/app/components/Upload/TusFileUpload.vue +7 -7
  8. package/nuxt-base-template/app/interfaces/user.interface.ts +5 -12
  9. package/nuxt-base-template/app/layouts/default.vue +1 -1
  10. package/nuxt-base-template/app/middleware/admin.global.ts +27 -7
  11. package/nuxt-base-template/app/middleware/auth.global.ts +23 -6
  12. package/nuxt-base-template/app/middleware/guest.global.ts +22 -7
  13. package/nuxt-base-template/app/pages/app/index.vue +4 -18
  14. package/nuxt-base-template/app/pages/app/settings/security.vue +2 -2
  15. package/nuxt-base-template/app/pages/auth/2fa.vue +39 -8
  16. package/nuxt-base-template/app/pages/auth/forgot-password.vue +2 -1
  17. package/nuxt-base-template/app/pages/auth/login.vue +14 -21
  18. package/nuxt-base-template/app/pages/auth/register.vue +5 -8
  19. package/nuxt-base-template/app/pages/auth/reset-password.vue +2 -1
  20. package/nuxt-base-template/docs/pages/docs.vue +1 -1
  21. package/nuxt-base-template/nuxt.config.ts +38 -1
  22. package/nuxt-base-template/package-lock.json +136 -2905
  23. package/nuxt-base-template/package.json +1 -0
  24. package/nuxt-base-template/server/api/iam/[...path].ts +12 -4
  25. package/package.json +2 -2
  26. package/nuxt-base-template/app/components/Transition/TransitionFade.vue +0 -27
  27. package/nuxt-base-template/app/components/Transition/TransitionFadeScale.vue +0 -27
  28. package/nuxt-base-template/app/components/Transition/TransitionSlide.vue +0 -12
  29. package/nuxt-base-template/app/components/Transition/TransitionSlideBottom.vue +0 -12
  30. package/nuxt-base-template/app/components/Transition/TransitionSlideRevert.vue +0 -12
  31. package/nuxt-base-template/app/composables/use-better-auth.ts +0 -415
  32. package/nuxt-base-template/app/composables/use-file.ts +0 -71
  33. package/nuxt-base-template/app/composables/use-share.ts +0 -38
  34. package/nuxt-base-template/app/composables/use-tus-upload.ts +0 -278
  35. package/nuxt-base-template/app/composables/use-tw.ts +0 -1
  36. package/nuxt-base-template/app/interfaces/upload.interface.ts +0 -58
  37. package/nuxt-base-template/app/lib/auth-client.ts +0 -232
  38. package/nuxt-base-template/app/plugins/auth-interceptor.client.ts +0 -143
  39. package/nuxt-base-template/app/utils/crypto.ts +0 -44
@@ -9,7 +9,7 @@ env:
9
9
  SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
10
10
 
11
11
  permissions:
12
- id-token: write # Required for OIDC
12
+ id-token: write # Required for OIDC
13
13
  contents: read
14
14
 
15
15
  jobs:
package/AUTH.md CHANGED
@@ -7,12 +7,12 @@ This document describes the Better Auth integration in the nuxt-base-starter tem
7
7
  The template uses [Better Auth](https://www.better-auth.com/) for authentication with the following features:
8
8
 
9
9
  | Feature | Status | Description |
10
- |-----------------------|--------|----------------------------------------|
11
- | Email & Password | ✅ | Standard email/password authentication |
12
- | Two-Factor Auth (2FA) | ✅ | TOTP-based 2FA with backup codes |
13
- | Passkey (WebAuthn) | ✅ | Passwordless authentication |
14
- | Session Management | ✅ | Cookie-based sessions with SSR support |
15
- | Password Hashing | ✅ | Client-side SHA256 hashing |
10
+ | --------------------- | ------ | -------------------------------------- |
11
+ | Email & Password | ✅ | Standard email/password authentication |
12
+ | Two-Factor Auth (2FA) | ✅ | TOTP-based 2FA with backup codes |
13
+ | Passkey (WebAuthn) | ✅ | Passwordless authentication |
14
+ | Session Management | ✅ | Cookie-based sessions with SSR support |
15
+ | Password Hashing | ✅ | Client-side SHA256 hashing |
16
16
 
17
17
  ## Architecture
18
18
 
@@ -44,7 +44,7 @@ The template uses [Better Auth](https://www.better-auth.com/) for authentication
44
44
  ## Files
45
45
 
46
46
  | File | Purpose |
47
- |--------------------------------------|----------------------------------|
47
+ | ------------------------------------ | -------------------------------- |
48
48
  | `app/lib/auth-client.ts` | Better Auth client configuration |
49
49
  | `app/composables/use-better-auth.ts` | Auth state management composable |
50
50
  | `app/pages/auth/login.vue` | Login page |
@@ -153,8 +153,8 @@ import { createBetterAuthClient } from '~/lib/auth-client';
153
153
  // Create a custom client
154
154
  const customClient = createBetterAuthClient({
155
155
  baseURL: 'https://api.example.com',
156
- basePath: '/auth', // Default: '/iam'
157
- twoFactorRedirectPath: '/login/2fa', // Default: '/auth/2fa'
156
+ basePath: '/auth', // Default: '/iam'
157
+ twoFactorRedirectPath: '/login/2fa', // Default: '/auth/2fa'
158
158
  enableAdmin: false,
159
159
  enableTwoFactor: true,
160
160
  enablePasskey: true,
@@ -174,6 +174,7 @@ const hashedPassword = await sha256(plainPassword);
174
174
  ```
175
175
 
176
176
  **Why client-side hashing?**
177
+
177
178
  1. Prevents plain text passwords in network logs
178
179
  2. Works with nest-server's `normalizePasswordForIam()` which detects SHA256 hashes
179
180
  3. Server re-hashes with bcrypt for storage
@@ -183,7 +184,7 @@ const hashedPassword = await sha256(plainPassword);
183
184
  Sessions are stored in cookies for SSR compatibility:
184
185
 
185
186
  | Cookie | Purpose |
186
- |-----------------------------|----------------------------|
187
+ | --------------------------- | -------------------------- |
187
188
  | `auth-state` | User data (SSR-compatible) |
188
189
  | `token` | Session token |
189
190
  | `better-auth.session_token` | Better Auth native cookie |
@@ -200,6 +201,7 @@ fetchOptions: {
200
201
  ```
201
202
 
202
203
  **Backend CORS Configuration:**
204
+
203
205
  ```typescript
204
206
  // In nest-server config
205
207
  cors: {
@@ -215,7 +217,7 @@ The following endpoints are provided by the nest-server backend:
215
217
  ### Authentication
216
218
 
217
219
  | Endpoint | Method | Description |
218
- |----------------------|--------|-----------------------------|
220
+ | -------------------- | ------ | --------------------------- |
219
221
  | `/iam/sign-in/email` | POST | Email/password sign in |
220
222
  | `/iam/sign-up/email` | POST | Email/password registration |
221
223
  | `/iam/sign-out` | POST | Sign out |
@@ -224,7 +226,7 @@ The following endpoints are provided by the nest-server backend:
224
226
  ### Passkey (WebAuthn)
225
227
 
226
228
  | Endpoint | Method | Description |
227
- |----------------------------------------------|--------|--------------------------|
229
+ | -------------------------------------------- | ------ | ------------------------ |
228
230
  | `/iam/passkey/generate-register-options` | GET | Get registration options |
229
231
  | `/iam/passkey/verify-registration` | POST | Verify registration |
230
232
  | `/iam/passkey/generate-authenticate-options` | GET | Get auth options |
@@ -235,7 +237,7 @@ The following endpoints are provided by the nest-server backend:
235
237
  ### Two-Factor Authentication
236
238
 
237
239
  | Endpoint | Method | Description |
238
- |--------------------------------------|--------|--------------------|
240
+ | ------------------------------------ | ------ | ------------------ |
239
241
  | `/iam/two-factor/enable` | POST | Enable 2FA |
240
242
  | `/iam/two-factor/disable` | POST | Disable 2FA |
241
243
  | `/iam/two-factor/verify-totp` | POST | Verify TOTP code |
@@ -267,7 +269,7 @@ The passkey response only contains the session, not the user. Call `validateSess
267
269
 
268
270
  ```typescript
269
271
  if (result.data?.session) {
270
- await validateSession(); // Fetches user data
272
+ await validateSession(); // Fetches user data
271
273
  }
272
274
  ```
273
275
 
package/CHANGELOG.md CHANGED
@@ -2,29 +2,66 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
+ ## [2.0.0](https://github.com/lenneTech/nuxt-base-starter/compare/v1.2.0...v2.0.0) (2026-01-24)
6
+
7
+
8
+ ### ⚠ BREAKING CHANGES
9
+
10
+ * Local auth, upload, and utility files replaced by package
11
+
12
+ Migration to @lenne.tech/nuxt-extensions v1.0.1:
13
+ - Remove local composables (use-better-auth, use-tus-upload, use-file, use-share, use-tw)
14
+ - Remove local lib files (auth-client, auth-state)
15
+ - Remove local plugins (auth-interceptor.client)
16
+ - Remove local utils (crypto)
17
+ - Remove local interfaces (upload.interface)
18
+ - Remove local Transition components (now provided by package as Lt* prefix)
19
+ - Simplify user.interface.ts to re-export LtUser from package
20
+ - Update all imports to use auto-imported composables (useLtAuth, useLtTusUpload, etc.)
21
+ - Update package.json to use npm package instead of yalc
22
+
23
+ All authentication features now provided by the package:
24
+ - Cookie/JWT dual-mode authentication
25
+ - Passkey/WebAuthn support
26
+ - 2FA/TOTP support
27
+ - Auto-logout on 401
28
+
29
+ Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
30
+
31
+ ### Bug Fixes
32
+
33
+ * use exact version 1.0.1 for [@lenne](https://github.com/lenne).tech/nuxt-extensions ([fe8d4c2](https://github.com/lenneTech/nuxt-base-starter/commit/fe8d4c21575977bd093a304adc2e18c7edc61110))
34
+
35
+
36
+ * migrate to [@lenne](https://github.com/lenne).tech/nuxt-extensions package ([4c8b732](https://github.com/lenneTech/nuxt-base-starter/commit/4c8b732b9d017a0ff7fb21ef09136dcd7fff81f0))
37
+
38
+ ## [1.2.0](https://github.com/lenneTech/nuxt-base-starter/compare/v1.1.2...v1.2.0) (2026-01-22)
39
+
40
+
41
+ ### Features
42
+
43
+ * **auth:** add Cookie/JWT dual-mode authentication with automatic fallback ([8005425](https://github.com/lenneTech/nuxt-base-starter/commit/800542585dc22c1710f8bf587ee8371458454c79))
44
+
5
45
  ### [1.1.2](https://github.com/lenneTech/nuxt-base-starter/compare/v1.1.1...v1.1.2) (2026-01-22)
6
46
 
7
47
  ### [1.1.1](https://github.com/lenneTech/nuxt-base-starter/compare/v1.1.0...v1.1.1) (2026-01-20)
8
48
 
9
-
10
49
  ### Bug Fixes
11
50
 
12
- * **auth:** auto-login after registration and improved passkey handling ([625128b](https://github.com/lenneTech/nuxt-base-starter/commit/625128b18fe812c141859946c196f8efb0738dca))
13
- * **auth:** improve 2FA UX and document dev-mode proxy requirements ([4c4b1f4](https://github.com/lenneTech/nuxt-base-starter/commit/4c4b1f4d8b77fa93469ccc1a31d4f3292cc7c724))
51
+ - **auth:** auto-login after registration and improved passkey handling ([625128b](https://github.com/lenneTech/nuxt-base-starter/commit/625128b18fe812c141859946c196f8efb0738dca))
52
+ - **auth:** improve 2FA UX and document dev-mode proxy requirements ([4c4b1f4](https://github.com/lenneTech/nuxt-base-starter/commit/4c4b1f4d8b77fa93469ccc1a31d4f3292cc7c724))
14
53
 
15
54
  ## [1.1.0](https://github.com/lenneTech/nuxt-base-starter/compare/v1.0.3...v1.1.0) (2026-01-20)
16
55
 
17
-
18
56
  ### Features
19
57
 
20
- * add complete Better-Auth integration with Passkey support and comprehensive documentation ([e0d470c](https://github.com/lenneTech/nuxt-base-starter/commit/e0d470c8229c37bed2948d929676620f344f4878))
58
+ - add complete Better-Auth integration with Passkey support and comprehensive documentation ([e0d470c](https://github.com/lenneTech/nuxt-base-starter/commit/e0d470c8229c37bed2948d929676620f344f4878))
21
59
 
22
60
  ## [1.2.0](https://github.com/lenneTech/nuxt-base-starter/compare/v1.0.3...v1.2.0) (2026-01-20)
23
61
 
24
-
25
62
  ### Features
26
63
 
27
- * add complete Better-Auth integration with Passkey support and comprehensive documentation ([70fbec1](https://github.com/lenneTech/nuxt-base-starter/commit/70fbec14e38673c5185195fe05f0cd82bf72a800))
64
+ - add complete Better-Auth integration with Passkey support and comprehensive documentation ([70fbec1](https://github.com/lenneTech/nuxt-base-starter/commit/70fbec14e38673c5185195fe05f0cd82bf72a800))
28
65
 
29
66
  ## [1.1.0](https://github.com/lenneTech/nuxt-base-starter/compare/v1.0.3...v1.1.0) (2026-01-20)
30
67
 
@@ -32,19 +69,17 @@ All notable changes to this project will be documented in this file. See [standa
32
69
 
33
70
  ### [1.0.2](https://github.com/lenneTech/nuxt-base-starter/compare/v1.0.1...v1.0.2) (2026-01-12)
34
71
 
35
-
36
72
  ### Bug Fixes
37
73
 
38
- * add repository field in package.json ([1f43eae](https://github.com/lenneTech/nuxt-base-starter/commit/1f43eae4445b2f8a54cf4442c79be1bd55cf711c))
74
+ - add repository field in package.json ([1f43eae](https://github.com/lenneTech/nuxt-base-starter/commit/1f43eae4445b2f8a54cf4442c79be1bd55cf711c))
39
75
 
40
76
  ### [1.0.1](https://github.com/lenneTech/nuxt-base-starter/compare/v1.0.0...v1.0.1) (2026-01-12)
41
77
 
42
78
  ## [1.0.0](https://github.com/lenneTech/nuxt-base-starter/compare/v0.3.17...v1.0.0) (2026-01-12)
43
79
 
44
-
45
80
  ### Bug Fixes
46
81
 
47
- * **DEV-609:** removed duplicate public folder inside app ([#10](https://github.com/lenneTech/nuxt-base-starter/issues/10)) ([25fe0fe](https://github.com/lenneTech/nuxt-base-starter/commit/25fe0fe3c53bc3400373d9c3f0a4b6705952171b))
82
+ - **DEV-609:** removed duplicate public folder inside app ([#10](https://github.com/lenneTech/nuxt-base-starter/issues/10)) ([25fe0fe](https://github.com/lenneTech/nuxt-base-starter/commit/25fe0fe3c53bc3400373d9c3f0a4b6705952171b))
48
83
 
49
84
  ### [0.3.17](https://github.com/lenneTech/nuxt-base-starter/compare/v0.3.16...v0.3.17) (2025-10-17)
50
85
 
package/README.md CHANGED
@@ -17,7 +17,7 @@ The development server starts at **http://localhost:3001**
17
17
  ### Core Framework
18
18
 
19
19
  | Technology | Version | Description |
20
- |--------------|---------|---------------------------------------|
20
+ | ------------ | ------- | ------------------------------------- |
21
21
  | Nuxt | 4.x | Vue 3 meta-framework with SSR support |
22
22
  | TypeScript | 5.9.x | Strict type checking enabled |
23
23
  | Tailwind CSS | 4.x | Utility-first CSS with Vite plugin |
@@ -28,7 +28,7 @@ The development server starts at **http://localhost:3001**
28
28
  Complete authentication system using [Better Auth](https://www.better-auth.com/):
29
29
 
30
30
  | Feature | Description |
31
- |--------------------|-------------------------------------------------------|
31
+ | ------------------ | ----------------------------------------------------- |
32
32
  | Email/Password | Standard auth with client-side SHA256 hashing |
33
33
  | Two-Factor (2FA) | TOTP-based 2FA with backup codes |
34
34
  | Passkey/WebAuthn | Passwordless authentication (Touch ID, Face ID, etc.) |
@@ -42,7 +42,7 @@ Pre-built auth pages: login, register, forgot-password, reset-password, 2fa
42
42
  ### State & Data
43
43
 
44
44
  | Package | Purpose |
45
- |-----------------------|-----------------------------|
45
+ | --------------------- | --------------------------- |
46
46
  | Pinia | State management |
47
47
  | VueUse | Vue composition utilities |
48
48
  | @hey-api/client-fetch | Type-safe API client |
@@ -57,7 +57,7 @@ Pre-built auth pages: login, register, forgot-password, reset-password, 2fa
57
57
  ### Developer Experience
58
58
 
59
59
  | Tool | Purpose |
60
- |--------------------|------------------------------------|
60
+ | ------------------ | ---------------------------------- |
61
61
  | OxLint | Fast linting |
62
62
  | OxFmt | Code formatting |
63
63
  | Playwright | E2E testing |
@@ -104,7 +104,7 @@ my-project/
104
104
  ## Available Scripts
105
105
 
106
106
  | Script | Description |
107
- |--------------------------|----------------------------------------|
107
+ | ------------------------ | -------------------------------------- |
108
108
  | `npm run dev` | Start development server |
109
109
  | `npm run build` | Build for production |
110
110
  | `npm run preview` | Preview production build |
@@ -91,17 +91,17 @@ npm run generate-types
91
91
 
92
92
  ## Tech Stack
93
93
 
94
- | Technology | Version | Description |
95
- |------------|---------|-------------|
96
- | Nuxt | 4.2.x | Vue 3 meta-framework with SSR |
97
- | TypeScript | 5.9.x | Strict type checking |
98
- | Tailwind CSS | 4.1.x | Utility-first CSS (Vite plugin) |
99
- | NuxtUI | 4.3.x | Component library with dark mode |
100
- | Pinia | 0.11.x | State management |
101
- | Better Auth | 1.4.x | Authentication framework |
102
- | Playwright | 1.57.x | E2E testing |
103
- | @hey-api/client-fetch | 0.13.x | Type-safe API client |
104
- | Valibot | 1.2.x | Schema validation |
94
+ | Technology | Version | Description |
95
+ | --------------------- | ------- | -------------------------------- |
96
+ | Nuxt | 4.2.x | Vue 3 meta-framework with SSR |
97
+ | TypeScript | 5.9.x | Strict type checking |
98
+ | Tailwind CSS | 4.1.x | Utility-first CSS (Vite plugin) |
99
+ | NuxtUI | 4.3.x | Component library with dark mode |
100
+ | Pinia | 0.11.x | State management |
101
+ | Better Auth | 1.4.x | Authentication framework |
102
+ | Playwright | 1.57.x | E2E testing |
103
+ | @hey-api/client-fetch | 0.13.x | Type-safe API client |
104
+ | Valibot | 1.2.x | Schema validation |
105
105
 
106
106
  ## Key Features
107
107
 
@@ -7,7 +7,8 @@ import type { InferOutput } from 'valibot';
7
7
 
8
8
  import * as v from 'valibot';
9
9
 
10
- import { authClient } from '~/lib/auth-client';
10
+ // Auth client from @lenne.tech/nuxt-extensions (auto-imported as ltAuthClient)
11
+ const authClient = ltAuthClient;
11
12
 
12
13
  // ============================================================================
13
14
  // Props & Emits
@@ -1,5 +1,5 @@
1
1
  <script setup lang="ts">
2
- import type { UploadItem } from '~/interfaces/upload.interface';
2
+ import type { LtUploadItem } from '@lenne.tech/nuxt-extensions';
3
3
 
4
4
  // ============================================================================
5
5
  // Props & Emits
@@ -45,11 +45,11 @@ const props = withDefaults(defineProps<Props>(), {
45
45
 
46
46
  const emit = defineEmits<{
47
47
  /** Alle Uploads abgeschlossen */
48
- complete: [items: UploadItem[]];
48
+ complete: [items: LtUploadItem[]];
49
49
  /** Upload-Fehler */
50
- error: [item: UploadItem, error: Error];
50
+ error: [item: LtUploadItem, error: Error];
51
51
  /** Ein Upload abgeschlossen */
52
- success: [item: UploadItem];
52
+ success: [item: LtUploadItem];
53
53
  /** Files geaendert */
54
54
  'update:modelValue': [files: File[]];
55
55
  }>();
@@ -58,12 +58,12 @@ const emit = defineEmits<{
58
58
  // Composables
59
59
  // ============================================================================
60
60
  const toast = useToast();
61
- const { formatDuration, formatFileSize } = useFile();
61
+ const { formatDuration, formatFileSize } = useLtFile();
62
62
 
63
63
  // ============================================================================
64
64
  // TUS Upload Setup
65
65
  // ============================================================================
66
- const { addFiles, cancelAll, cancelUpload, clearCompleted, isUploading, pauseAll, pauseUpload, resumeAll, resumeUpload, retryUpload, totalProgress, uploads } = useTusUpload({
66
+ const { addFiles, cancelAll, cancelUpload, clearCompleted, isUploading, pauseAll, pauseUpload, resumeAll, resumeUpload, retryUpload, totalProgress, uploads } = useLtTusUpload({
67
67
  autoStart: props.autoStart,
68
68
  chunkSize: props.chunkSize,
69
69
  endpoint: props.endpoint,
@@ -162,7 +162,7 @@ function getStatusLabel(status: string): string {
162
162
  // ============================================================================
163
163
  // Helpers
164
164
  // ============================================================================
165
- function getUploadForFile(file: File, index: number): undefined | UploadItem {
165
+ function getUploadForFile(file: File, index: number): undefined | LtUploadItem {
166
166
  // Versuche ueber Index zu matchen (funktioniert wenn Reihenfolge gleich)
167
167
  const upload = uploads.value[index];
168
168
  if (upload?.file.name === file.name && upload?.file.size === file.size) {
@@ -1,12 +1,5 @@
1
- export interface User {
2
- banExpires?: Date;
3
- banned?: boolean;
4
- banReason?: string;
5
- email: string;
6
- emailVerified: boolean;
7
- id: string;
8
- image?: string;
9
- name?: string;
10
- role?: string;
11
- twoFactorEnabled?: boolean;
12
- }
1
+ /**
2
+ * Re-export LtUser from @lenne.tech/nuxt-extensions as User
3
+ * This provides backwards compatibility while using the package type
4
+ */
5
+ export type { LtUser as User } from '@lenne.tech/nuxt-extensions';
@@ -1,7 +1,7 @@
1
1
  <script setup lang="ts">
2
2
  import type { NavigationMenuItem } from '@nuxt/ui';
3
3
 
4
- const { isAuthenticated, signOut, user } = useBetterAuth();
4
+ const { isAuthenticated, signOut, user } = useLtAuth();
5
5
 
6
6
  async function handleLogout() {
7
7
  await signOut();
@@ -4,20 +4,40 @@ export default defineNuxtRouteMiddleware(async (to) => {
4
4
  return;
5
5
  }
6
6
 
7
- const { isAdmin, isAuthenticated, isLoading } = useBetterAuth();
7
+ let isAuthenticated = false;
8
+ let isAdmin = false;
8
9
 
9
- // Wait for session to load
10
- if (isLoading.value) {
11
- return;
10
+ // On client, read directly from document.cookie for accurate state
11
+ if (import.meta.client) {
12
+ try {
13
+ const cookie = document.cookie.split('; ').find((row) => row.startsWith('lt-auth-state='));
14
+ if (cookie) {
15
+ const parts = cookie.split('=');
16
+ const value = parts.length > 1 ? decodeURIComponent(parts.slice(1).join('=')) : '';
17
+ const state = JSON.parse(value);
18
+ isAuthenticated = !!state?.user;
19
+ isAdmin = state?.user?.role === 'admin';
20
+ }
21
+ } catch {
22
+ // Ignore parse errors
23
+ }
24
+ } else {
25
+ // On server, use useCookie
26
+ const authStateCookie = useCookie<{ user: { role?: string } | null; authMode: string } | null>('lt-auth-state');
27
+ isAuthenticated = !!authStateCookie.value?.user;
28
+ isAdmin = authStateCookie.value?.user?.role === 'admin';
12
29
  }
13
30
 
14
31
  // Redirect to login if not authenticated
15
- if (!isAuthenticated.value) {
16
- return navigateTo('/auth/login');
32
+ if (!isAuthenticated) {
33
+ return navigateTo({
34
+ path: '/auth/login',
35
+ query: { redirect: to.fullPath },
36
+ });
17
37
  }
18
38
 
19
39
  // Redirect to /app if authenticated but not admin
20
- if (!isAdmin.value) {
40
+ if (!isAdmin) {
21
41
  return navigateTo('/app');
22
42
  }
23
43
  });
@@ -4,15 +4,32 @@ export default defineNuxtRouteMiddleware(async (to) => {
4
4
  return;
5
5
  }
6
6
 
7
- const { isAuthenticated, isLoading } = useBetterAuth();
7
+ let isAuthenticated = false;
8
8
 
9
- // Wait for session to load
10
- if (isLoading.value) {
11
- return;
9
+ // On client, read directly from document.cookie for accurate state
10
+ if (import.meta.client) {
11
+ try {
12
+ const cookie = document.cookie.split('; ').find((row) => row.startsWith('lt-auth-state='));
13
+ if (cookie) {
14
+ const parts = cookie.split('=');
15
+ const value = parts.length > 1 ? decodeURIComponent(parts.slice(1).join('=')) : '';
16
+ const state = JSON.parse(value);
17
+ isAuthenticated = !!state?.user;
18
+ }
19
+ } catch {
20
+ // Ignore parse errors
21
+ }
22
+ } else {
23
+ // On server, use useCookie
24
+ const authStateCookie = useCookie<{ user: unknown; authMode: string } | null>('lt-auth-state');
25
+ isAuthenticated = !!authStateCookie.value?.user;
12
26
  }
13
27
 
14
28
  // Redirect to login if not authenticated
15
- if (!isAuthenticated.value) {
16
- return navigateTo('/auth/login');
29
+ if (!isAuthenticated) {
30
+ return navigateTo({
31
+ path: '/auth/login',
32
+ query: { redirect: to.fullPath },
33
+ });
17
34
  }
18
35
  });
@@ -4,15 +4,30 @@ export default defineNuxtRouteMiddleware(async (to) => {
4
4
  return;
5
5
  }
6
6
 
7
- const { isAuthenticated, isLoading } = useBetterAuth();
7
+ let isAuthenticated = false;
8
8
 
9
- // Wait for session to load
10
- if (isLoading.value) {
11
- return;
9
+ // On client, read directly from document.cookie for accurate state
10
+ if (import.meta.client) {
11
+ try {
12
+ const cookie = document.cookie.split('; ').find((row) => row.startsWith('lt-auth-state='));
13
+ if (cookie) {
14
+ const parts = cookie.split('=');
15
+ const value = parts.length > 1 ? decodeURIComponent(parts.slice(1).join('=')) : '';
16
+ const state = JSON.parse(value);
17
+ isAuthenticated = !!state?.user;
18
+ }
19
+ } catch {
20
+ // Ignore parse errors
21
+ }
22
+ } else {
23
+ // On server, use useCookie
24
+ const authStateCookie = useCookie<{ user: unknown; authMode: string } | null>('lt-auth-state');
25
+ isAuthenticated = !!authStateCookie.value?.user;
12
26
  }
13
27
 
14
- // Redirect to /app if already authenticated
15
- if (isAuthenticated.value) {
16
- return navigateTo('/app');
28
+ // Redirect to /app (or redirect query) if already authenticated
29
+ if (isAuthenticated) {
30
+ const redirect = to.query.redirect as string;
31
+ return navigateTo(redirect || '/app');
17
32
  }
18
33
  });
@@ -2,7 +2,7 @@
2
2
  // ============================================================================
3
3
  // Composables
4
4
  // ============================================================================
5
- const { user, signOut } = useBetterAuth();
5
+ const { user, signOut } = useLtAuth();
6
6
 
7
7
  // ============================================================================
8
8
  // Variables
@@ -29,9 +29,7 @@ async function handleSignOut(): Promise<void> {
29
29
  <div class="mx-auto max-w-4xl px-4 py-8">
30
30
  <!-- Welcome Header -->
31
31
  <div class="mb-8">
32
- <h1 class="text-3xl font-bold">
33
- Willkommen{{ user?.name ? `, ${user.name}` : '' }}!
34
- </h1>
32
+ <h1 class="text-3xl font-bold">Willkommen{{ user?.name ? `, ${user.name}` : '' }}!</h1>
35
33
  <p class="mt-2 text-muted">
36
34
  {{ user?.email }}
37
35
  </p>
@@ -41,12 +39,7 @@ async function handleSignOut(): Promise<void> {
41
39
  <div class="mb-8">
42
40
  <h2 class="mb-4 text-xl font-semibold">Schnellzugriff</h2>
43
41
  <div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
44
- <UCard
45
- v-for="page in pages"
46
- :key="page.to"
47
- class="cursor-pointer transition-shadow hover:shadow-lg"
48
- @click="navigateTo(page.to)"
49
- >
42
+ <UCard v-for="page in pages" :key="page.to" class="cursor-pointer transition-shadow hover:shadow-lg" @click="navigateTo(page.to)">
50
43
  <div class="flex items-start gap-4">
51
44
  <div class="rounded-lg bg-primary/10 p-3">
52
45
  <UIcon :name="page.icon" class="size-6 text-primary" />
@@ -87,14 +80,7 @@ async function handleSignOut(): Promise<void> {
87
80
  </div>
88
81
 
89
82
  <template #footer>
90
- <UButton
91
- color="error"
92
- variant="outline"
93
- icon="i-lucide-log-out"
94
- @click="handleSignOut"
95
- >
96
- Abmelden
97
- </UButton>
83
+ <UButton color="error" variant="outline" icon="i-lucide-log-out" @click="handleSignOut"> Abmelden </UButton>
98
84
  </template>
99
85
  </UCard>
100
86
  </div>
@@ -8,7 +8,6 @@ import type { InferOutput } from 'valibot';
8
8
  import * as v from 'valibot';
9
9
 
10
10
  import ModalBackupCodes from '~/components/Modal/ModalBackupCodes.vue';
11
- import { authClient } from '~/lib/auth-client';
12
11
 
13
12
  // ============================================================================
14
13
  // Interfaces
@@ -24,7 +23,8 @@ interface Passkey {
24
23
  // ============================================================================
25
24
  const toast = useToast();
26
25
  const overlay = useOverlay();
27
- const { is2FAEnabled, registerPasskey, setUser, user } = useBetterAuth();
26
+ const { is2FAEnabled, registerPasskey, setUser, user } = useLtAuth();
27
+ const authClient = useLtAuthClient();
28
28
 
29
29
  // ============================================================================
30
30
  // Variables
@@ -7,13 +7,12 @@ import type { InferOutput } from 'valibot';
7
7
 
8
8
  import * as v from 'valibot';
9
9
 
10
- import { authClient } from '~/lib/auth-client';
11
-
12
10
  // ============================================================================
13
11
  // Composables
14
12
  // ============================================================================
15
13
  const toast = useToast();
16
- const { setUser, validateSession } = useBetterAuth();
14
+ const { fetchWithAuth, setUser, switchToJwtMode, jwtToken } = useLtAuth();
15
+ const authClient = useLtAuthClient();
17
16
 
18
17
  // ============================================================================
19
18
  // Page Meta
@@ -76,13 +75,45 @@ async function onSubmit(payload: FormSubmitEvent<Schema>): Promise<void> {
76
75
  }
77
76
  }
78
77
 
79
- // Update auth state with user data from response
78
+ // Extract token and user data from response (JWT mode: cookies: false)
79
+ const token = result?.token || result?.data?.token;
80
80
  const userData = result?.data?.user || result?.user;
81
- if (userData) {
82
- setUser(userData);
81
+
82
+ if (token) {
83
+ // JWT mode: Token is in the response
84
+ jwtToken.value = token;
85
+ if (userData) {
86
+ setUser(userData, 'jwt');
87
+ }
88
+ console.debug('[Auth] JWT token received from 2FA response');
89
+ } else if (userData) {
90
+ // Cookie mode: No token in response, use cookies
91
+ setUser(userData, 'cookie');
92
+ // Try to get JWT token for fallback
93
+ switchToJwtMode().catch(() => {});
83
94
  } else {
84
- // Fallback: validate session to get user data
85
- await validateSession();
95
+ // Fallback: fetch session data from API using authenticated fetch
96
+ try {
97
+ const isDev = import.meta.dev;
98
+ const runtimeConfig = useRuntimeConfig();
99
+ const apiBase = isDev ? '/api/iam' : `${runtimeConfig.public.apiUrl || 'http://localhost:3000'}/iam`;
100
+ const sessionResponse = await fetchWithAuth(`${apiBase}/get-session`);
101
+ if (sessionResponse.ok) {
102
+ const sessionData = await sessionResponse.json();
103
+ const sessionToken = sessionData?.token;
104
+ if (sessionToken) {
105
+ jwtToken.value = sessionToken;
106
+ if (sessionData?.user) {
107
+ setUser(sessionData.user, 'jwt');
108
+ }
109
+ } else if (sessionData?.user) {
110
+ setUser(sessionData.user, 'cookie');
111
+ switchToJwtMode().catch(() => {});
112
+ }
113
+ }
114
+ } catch {
115
+ // Ignore session fetch errors
116
+ }
86
117
  }
87
118
 
88
119
  await navigateTo('/app');
@@ -7,7 +7,8 @@ import type { InferOutput } from 'valibot';
7
7
 
8
8
  import * as v from 'valibot';
9
9
 
10
- import { authClient } from '~/lib/auth-client';
10
+ // Auth client from @lenne.tech/nuxt-extensions (auto-imported as ltAuthClient)
11
+ const authClient = ltAuthClient;
11
12
 
12
13
  // ============================================================================
13
14
  // Composables