create-nuxt-base 1.0.2 → 1.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/AUTH.md ADDED
@@ -0,0 +1,289 @@
1
+ # Better Auth Integration
2
+
3
+ This document describes the Better Auth integration in the nuxt-base-starter template.
4
+
5
+ ## Overview
6
+
7
+ The template uses [Better Auth](https://www.better-auth.com/) for authentication with the following features:
8
+
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 |
16
+
17
+ ## Architecture
18
+
19
+ ```
20
+ ┌─────────────────────────────────────────────────────────────────┐
21
+ │ FRONTEND (Nuxt) │
22
+ ├─────────────────────────────────────────────────────────────────┤
23
+ │ │
24
+ │ ┌─────────────────┐ ┌─────────────────┐ │
25
+ │ │ auth-client │───▶│ useBetterAuth │ │
26
+ │ │ (lib/) │ │ (composable) │ │
27
+ │ └────────┬────────┘ └────────┬────────┘ │
28
+ │ │ │ │
29
+ │ │ SHA256 Hashing │ Cookie-based State │
30
+ │ │ Plugin Config │ Session Validation │
31
+ │ │ │ │
32
+ └───────────┼──────────────────────┼──────────────────────────────┘
33
+ │ │
34
+ ▼ ▼
35
+ ┌─────────────────────────────────────────────────────────────────┐
36
+ │ BACKEND (nest-server) │
37
+ ├─────────────────────────────────────────────────────────────────┤
38
+ │ /iam/sign-in/email /iam/session │
39
+ │ /iam/sign-up/email /iam/sign-out │
40
+ │ /iam/passkey/* /iam/two-factor/* │
41
+ └─────────────────────────────────────────────────────────────────┘
42
+ ```
43
+
44
+ ## Files
45
+
46
+ | File | Purpose |
47
+ |--------------------------------------|----------------------------------|
48
+ | `app/lib/auth-client.ts` | Better Auth client configuration |
49
+ | `app/composables/use-better-auth.ts` | Auth state management composable |
50
+ | `app/pages/auth/login.vue` | Login page |
51
+ | `app/pages/auth/register.vue` | Registration page |
52
+ | `app/pages/auth/2fa.vue` | Two-factor authentication page |
53
+ | `app/pages/auth/forgot-password.vue` | Password reset request |
54
+ | `app/pages/auth/reset-password.vue` | Password reset form |
55
+ | `app/utils/crypto.ts` | SHA256 hashing utility |
56
+
57
+ ## Usage
58
+
59
+ ### Basic Authentication
60
+
61
+ ```typescript
62
+ // In a Vue component
63
+ const { signIn, signUp, signOut, user, isAuthenticated } = useBetterAuth();
64
+
65
+ // Sign in
66
+ const result = await signIn.email({
67
+ email: 'user@example.com',
68
+ password: 'password123',
69
+ });
70
+
71
+ // Sign up
72
+ const result = await signUp.email({
73
+ email: 'user@example.com',
74
+ name: 'John Doe',
75
+ password: 'password123',
76
+ });
77
+
78
+ // Sign out
79
+ await signOut();
80
+
81
+ // Check auth state
82
+ if (isAuthenticated.value) {
83
+ console.log('User:', user.value);
84
+ }
85
+ ```
86
+
87
+ ### Passkey Authentication
88
+
89
+ ```typescript
90
+ import { authClient } from '~/lib/auth-client';
91
+
92
+ // Sign in with passkey
93
+ const result = await authClient.signIn.passkey();
94
+
95
+ if (result.error) {
96
+ console.error('Passkey login failed:', result.error.message);
97
+ } else {
98
+ // Validate session to get user data (passkey returns session only)
99
+ await validateSession();
100
+ navigateTo('/app');
101
+ }
102
+ ```
103
+
104
+ ### Two-Factor Authentication
105
+
106
+ ```typescript
107
+ import { authClient } from '~/lib/auth-client';
108
+
109
+ // Verify TOTP code
110
+ const result = await authClient.twoFactor.verifyTotp({
111
+ code: '123456',
112
+ trustDevice: true,
113
+ });
114
+
115
+ // Verify backup code
116
+ const result = await authClient.twoFactor.verifyBackupCode({
117
+ code: 'backup-code-here',
118
+ });
119
+ ```
120
+
121
+ ### Session Validation
122
+
123
+ ```typescript
124
+ const { validateSession, user } = useBetterAuth();
125
+
126
+ // On app init, validate the session
127
+ const isValid = await validateSession();
128
+
129
+ if (isValid) {
130
+ console.log('Session valid, user:', user.value);
131
+ } else {
132
+ console.log('No valid session');
133
+ }
134
+ ```
135
+
136
+ ## Configuration
137
+
138
+ ### Environment Variables
139
+
140
+ ```env
141
+ # API URL (required)
142
+ API_URL=http://localhost:3000
143
+
144
+ # Or via Vite
145
+ VITE_API_URL=http://localhost:3000
146
+ ```
147
+
148
+ ### Custom Configuration
149
+
150
+ ```typescript
151
+ import { createBetterAuthClient } from '~/lib/auth-client';
152
+
153
+ // Create a custom client
154
+ const customClient = createBetterAuthClient({
155
+ baseURL: 'https://api.example.com',
156
+ basePath: '/auth', // Default: '/iam'
157
+ twoFactorRedirectPath: '/login/2fa', // Default: '/auth/2fa'
158
+ enableAdmin: false,
159
+ enableTwoFactor: true,
160
+ enablePasskey: true,
161
+ });
162
+ ```
163
+
164
+ ## Security
165
+
166
+ ### Password Hashing
167
+
168
+ Passwords are hashed with SHA256 on the client-side before transmission:
169
+
170
+ ```typescript
171
+ // This happens automatically in auth-client.ts
172
+ const hashedPassword = await sha256(plainPassword);
173
+ // Result: 64-character hex string
174
+ ```
175
+
176
+ **Why client-side hashing?**
177
+ 1. Prevents plain text passwords in network logs
178
+ 2. Works with nest-server's `normalizePasswordForIam()` which detects SHA256 hashes
179
+ 3. Server re-hashes with bcrypt for storage
180
+
181
+ ### Cookie-Based Sessions
182
+
183
+ Sessions are stored in cookies for SSR compatibility:
184
+
185
+ | Cookie | Purpose |
186
+ |-----------------------------|----------------------------|
187
+ | `auth-state` | User data (SSR-compatible) |
188
+ | `token` | Session token |
189
+ | `better-auth.session_token` | Better Auth native cookie |
190
+
191
+ ### Cross-Origin Requests
192
+
193
+ The client is configured with `credentials: 'include'` for cross-origin cookie handling:
194
+
195
+ ```typescript
196
+ // In auth-client.ts
197
+ fetchOptions: {
198
+ credentials: 'include',
199
+ }
200
+ ```
201
+
202
+ **Backend CORS Configuration:**
203
+ ```typescript
204
+ // In nest-server config
205
+ cors: {
206
+ origin: 'http://localhost:3001', // Not '*'
207
+ credentials: true,
208
+ }
209
+ ```
210
+
211
+ ## Better Auth Endpoints
212
+
213
+ The following endpoints are provided by the nest-server backend:
214
+
215
+ ### Authentication
216
+
217
+ | Endpoint | Method | Description |
218
+ |----------------------|--------|-----------------------------|
219
+ | `/iam/sign-in/email` | POST | Email/password sign in |
220
+ | `/iam/sign-up/email` | POST | Email/password registration |
221
+ | `/iam/sign-out` | POST | Sign out |
222
+ | `/iam/session` | GET | Get current session |
223
+
224
+ ### Passkey (WebAuthn)
225
+
226
+ | Endpoint | Method | Description |
227
+ |----------------------------------------------|--------|--------------------------|
228
+ | `/iam/passkey/generate-register-options` | GET | Get registration options |
229
+ | `/iam/passkey/verify-registration` | POST | Verify registration |
230
+ | `/iam/passkey/generate-authenticate-options` | GET | Get auth options |
231
+ | `/iam/passkey/verify-authentication` | POST | Verify authentication |
232
+ | `/iam/passkey/list-user-passkeys` | GET | List user's passkeys |
233
+ | `/iam/passkey/delete-passkey` | POST | Delete a passkey |
234
+
235
+ ### Two-Factor Authentication
236
+
237
+ | Endpoint | Method | Description |
238
+ |--------------------------------------|--------|--------------------|
239
+ | `/iam/two-factor/enable` | POST | Enable 2FA |
240
+ | `/iam/two-factor/disable` | POST | Disable 2FA |
241
+ | `/iam/two-factor/verify-totp` | POST | Verify TOTP code |
242
+ | `/iam/two-factor/verify-backup-code` | POST | Verify backup code |
243
+
244
+ ## Troubleshooting
245
+
246
+ ### "Passkey not found" Error
247
+
248
+ 1. Ensure the user has registered a passkey first
249
+ 2. Check that cookies are being sent (`credentials: 'include'`)
250
+ 3. Verify CORS is configured correctly on the backend
251
+
252
+ ### 2FA Redirect Not Working
253
+
254
+ Ensure the 2FA redirect is handled in the login page:
255
+
256
+ ```typescript
257
+ // Check for 2FA redirect in login response
258
+ if (result.data?.twoFactorRedirect) {
259
+ await navigateTo('/auth/2fa');
260
+ return;
261
+ }
262
+ ```
263
+
264
+ ### Session Not Persisting After Passkey Login
265
+
266
+ The passkey response only contains the session, not the user. Call `validateSession()`:
267
+
268
+ ```typescript
269
+ if (result.data?.session) {
270
+ await validateSession(); // Fetches user data
271
+ }
272
+ ```
273
+
274
+ ### Form Not Submitting (Nuxt UI)
275
+
276
+ Ensure UForm has the `:state` binding:
277
+
278
+ ```vue
279
+ <UForm :schema="schema" :state="formState" @submit="onSubmit">
280
+ <UInput v-model="formState.field" />
281
+ </UForm>
282
+ ```
283
+
284
+ ## References
285
+
286
+ - [Better Auth Documentation](https://www.better-auth.com/docs)
287
+ - [Better Auth Passkey Plugin](https://www.better-auth.com/docs/plugins/passkey)
288
+ - [Better Auth Two-Factor Plugin](https://www.better-auth.com/docs/plugins/two-factor)
289
+ - [nest-server Better Auth Integration](https://github.com/lenneTech/nest-server)
package/CHANGELOG.md CHANGED
@@ -2,6 +2,24 @@
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
+ ## [1.1.0](https://github.com/lenneTech/nuxt-base-starter/compare/v1.0.3...v1.1.0) (2026-01-20)
6
+
7
+
8
+ ### Features
9
+
10
+ * add complete Better-Auth integration with Passkey support and comprehensive documentation ([e0d470c](https://github.com/lenneTech/nuxt-base-starter/commit/e0d470c8229c37bed2948d929676620f344f4878))
11
+
12
+ ## [1.2.0](https://github.com/lenneTech/nuxt-base-starter/compare/v1.0.3...v1.2.0) (2026-01-20)
13
+
14
+
15
+ ### Features
16
+
17
+ * add complete Better-Auth integration with Passkey support and comprehensive documentation ([70fbec1](https://github.com/lenneTech/nuxt-base-starter/commit/70fbec14e38673c5185195fe05f0cd82bf72a800))
18
+
19
+ ## [1.1.0](https://github.com/lenneTech/nuxt-base-starter/compare/v1.0.3...v1.1.0) (2026-01-20)
20
+
21
+ ### [1.0.3](https://github.com/lenneTech/nuxt-base-starter/compare/v1.0.2...v1.0.3) (2026-01-12)
22
+
5
23
  ### [1.0.2](https://github.com/lenneTech/nuxt-base-starter/compare/v1.0.1...v1.0.2) (2026-01-12)
6
24
 
7
25
 
package/README.md CHANGED
@@ -1,9 +1,144 @@
1
- # Nuxt Starter
1
+ # create-nuxt-base
2
2
 
3
- This is a starter template to create a Nuxt 3 development environment including Tailwind, Pinia, VueUse, Nuxt Base, Eslint, Cypress with Cucumber, Unit Tests.
3
+ A CLI tool to scaffold a production-ready **Nuxt 4** application with TypeScript, Tailwind CSS v4, NuxtUI v4, and modern tooling.
4
4
 
5
- Just run the following command to create your Nuxt environment:
5
+ ## Quick Start
6
6
 
7
7
  ```bash
8
8
  npx create-nuxt-base my-awesome-project
9
+ cd my-awesome-project
10
+ npm run dev
9
11
  ```
12
+
13
+ The development server starts at **http://localhost:3001**
14
+
15
+ ## What's Included
16
+
17
+ ### Core Framework
18
+
19
+ | Technology | Version | Description |
20
+ |--------------|---------|---------------------------------------|
21
+ | Nuxt | 4.x | Vue 3 meta-framework with SSR support |
22
+ | TypeScript | 5.9.x | Strict type checking enabled |
23
+ | Tailwind CSS | 4.x | Utility-first CSS with Vite plugin |
24
+ | NuxtUI | 4.x | Component library with dark mode |
25
+
26
+ ### Authentication (Better Auth)
27
+
28
+ Complete authentication system using [Better Auth](https://www.better-auth.com/):
29
+
30
+ | Feature | Description |
31
+ |--------------------|-------------------------------------------------------|
32
+ | Email/Password | Standard auth with client-side SHA256 hashing |
33
+ | Two-Factor (2FA) | TOTP-based 2FA with backup codes |
34
+ | Passkey/WebAuthn | Passwordless authentication (Touch ID, Face ID, etc.) |
35
+ | Password Reset | Email-based password reset flow |
36
+ | Session Management | SSR-compatible cookie-based sessions |
37
+
38
+ Pre-built auth pages: login, register, forgot-password, reset-password, 2fa
39
+
40
+ 📖 **See [AUTH.md](./AUTH.md) for detailed documentation**
41
+
42
+ ### State & Data
43
+
44
+ | Package | Purpose |
45
+ |-----------------------|-----------------------------|
46
+ | Pinia | State management |
47
+ | VueUse | Vue composition utilities |
48
+ | @hey-api/client-fetch | Type-safe API client |
49
+ | Valibot | Schema validation for forms |
50
+
51
+ ### SEO & Analytics
52
+
53
+ - **@nuxtjs/seo** - Sitemap, robots.txt, OG images
54
+ - **@nuxtjs/plausible** - Privacy-friendly analytics
55
+ - **@nuxt/image** - Image optimization with IPX
56
+
57
+ ### Developer Experience
58
+
59
+ | Tool | Purpose |
60
+ |--------------------|------------------------------------|
61
+ | OxLint | Fast linting |
62
+ | OxFmt | Code formatting |
63
+ | Playwright | E2E testing |
64
+ | @lenne.tech/bug.lt | Bug reporting to Linear (dev only) |
65
+ | dayjs-nuxt | Date/time handling |
66
+
67
+ ### File Upload
68
+
69
+ - TUS resumable upload support (`tus-js-client`)
70
+ - Pre-built `TusFileUpload.vue` component
71
+
72
+ ### Docker Support
73
+
74
+ - `Dockerfile.dev` for containerized development
75
+ - Hot reload enabled
76
+
77
+ ## Project Structure
78
+
79
+ ```
80
+ my-project/
81
+ ├── app/
82
+ │ ├── assets/css/ # Tailwind CSS
83
+ │ ├── components/ # Vue components (auto-imported)
84
+ │ │ ├── Modal/ # Modal components
85
+ │ │ ├── Transition/ # Transition components
86
+ │ │ └── Upload/ # File upload components
87
+ │ ├── composables/ # Composables (auto-imported)
88
+ │ ├── interfaces/ # TypeScript interfaces
89
+ │ ├── layouts/ # Nuxt layouts
90
+ │ ├── lib/ # Auth client configuration
91
+ │ ├── middleware/ # Route middleware (auth, admin, guest)
92
+ │ ├── pages/ # File-based routing
93
+ │ │ ├── auth/ # Authentication pages
94
+ │ │ └── app/ # Protected app pages
95
+ │ ├── utils/ # Utility functions
96
+ │ └── app.config.ts # NuxtUI configuration
97
+ ├── docs/ # Dev-only documentation layer
98
+ ├── tests/ # Playwright E2E tests
99
+ ├── nuxt.config.ts # Nuxt configuration
100
+ ├── openapi-ts.config.ts # API type generation config
101
+ └── playwright.config.ts # E2E test configuration
102
+ ```
103
+
104
+ ## Available Scripts
105
+
106
+ | Script | Description |
107
+ |--------------------------|----------------------------------------|
108
+ | `npm run dev` | Start development server |
109
+ | `npm run build` | Build for production |
110
+ | `npm run preview` | Preview production build |
111
+ | `npm run generate-types` | Generate TypeScript types from OpenAPI |
112
+ | `npm run test` | Run Playwright E2E tests |
113
+ | `npm run lint` | Run OxLint |
114
+ | `npm run format` | Run OxFmt |
115
+ | `npm run check` | Run lint + format check |
116
+ | `npm run fix` | Auto-fix lint + format issues |
117
+
118
+ ## Environment Variables
119
+
120
+ Create a `.env` file based on `.env.example`:
121
+
122
+ ```env
123
+ # Required
124
+ SITE_URL=http://localhost:3001
125
+ API_URL=http://localhost:3000
126
+ APP_ENV=development
127
+ NODE_ENV=development
128
+
129
+ # Optional
130
+ WEB_PUSH_KEY= # Web push notifications
131
+ LINEAR_API_KEY= # Bug reporting
132
+ LINEAR_TEAM_NAME= # Bug reporting
133
+ LINEAR_PROJECT_NAME= # Bug reporting
134
+ STORAGE_PREFIX=base-dev # Local storage prefix
135
+ ```
136
+
137
+ ## Requirements
138
+
139
+ - Node.js >= 22
140
+ - npm >= 10
141
+
142
+ ## License
143
+
144
+ MIT
@@ -3,9 +3,7 @@ APP_ENV=development
3
3
  NODE_ENV=development
4
4
  API_URL=http://localhost:3000
5
5
  WEB_PUSH_KEY=
6
- API_SCHEMA=../api/schema.gql
7
6
  STORAGE_PREFIX=base-dev
8
- GENERATE_TYPES=0
9
7
 
10
8
  LINEAR_API_KEY=
11
9
  LINEAR_TEAM_NAME=
@@ -1,6 +1,6 @@
1
1
  # Nuxt Base Template
2
2
 
3
- A modern Nuxt 4 SSR starter template with TypeScript, Tailwind CSS v4, and NuxtUI.
3
+ A production-ready Nuxt 4 SSR starter with TypeScript, Tailwind CSS v4, NuxtUI v4, and Better Auth.
4
4
 
5
5
  ## Requirements
6
6
 
@@ -33,6 +33,13 @@ Start the development server on http://localhost:3001
33
33
  npm run dev
34
34
  ```
35
35
 
36
+ ### Docker Development
37
+
38
+ ```bash
39
+ docker build -f Dockerfile.dev -t nuxt-app-dev .
40
+ docker run -p 3001:3001 -v $(pwd):/app nuxt-app-dev
41
+ ```
42
+
36
43
  ## Production
37
44
 
38
45
  Build the application for production:
@@ -62,8 +69,8 @@ Run linting and formatting checks before committing:
62
69
  ```bash
63
70
  npm run check # Run lint + format check
64
71
  npm run fix # Auto-fix lint + format issues
65
- npm run lint # ESLint only
66
- npm run format # Prettier format only
72
+ npm run lint # OxLint only
73
+ npm run format # OxFmt format only
67
74
  ```
68
75
 
69
76
  ## Testing
@@ -84,33 +91,64 @@ npm run generate-types
84
91
 
85
92
  ## Tech Stack
86
93
 
87
- - **Framework:** Nuxt 4.1.3 (Vue 3 Composition API)
88
- - **Language:** TypeScript 5.9.3
89
- - **Styling:** Tailwind CSS 4.1.14
90
- - **UI Library:** NuxtUI 4.0.1
91
- - **State Management:** Pinia
92
- - **Testing:** Playwright
93
- - **API Client:** @hey-api/client-fetch
94
- - **Form Validation:** Valibot
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 |
95
105
 
96
106
  ## Key Features
97
107
 
98
- - Full TypeScript support with strict typing
99
- - ✅ NuxtUI component library with semantic colors
100
- - ✅ Dark/light mode support
101
- - SEO optimization (sitemap, robots.txt, OG images)
102
- - Auto-generated API client from OpenAPI schema
103
- - E2E testing with Playwright
104
- - ESLint + Prettier configuration
105
- - Plausible Analytics integration
106
- - ✅ Image optimization with NuxtImage
107
- - Bug reporting to Linear (dev only)
108
+ ### Authentication (Better Auth)
109
+
110
+ - Email/password authentication with client-side SHA256 password hashing
111
+ - Two-factor authentication (2FA/TOTP) with backup codes
112
+ - Passkey/WebAuthn support
113
+ - Password reset flow
114
+ - Pre-built pages: login, register, forgot-password, reset-password, 2fa
115
+ - Route middleware: `auth.global.ts`, `admin.global.ts`, `guest.global.ts`
116
+
117
+ ### UI & Styling
118
+
119
+ - NuxtUI v4 component library
120
+ - Dark/light mode support
121
+ - Transition components (Fade, Slide, FadeScale)
122
+ - Modal components with `useOverlay` pattern
123
+
124
+ ### SEO & Analytics
125
+
126
+ - Sitemap generation (`@nuxtjs/seo`)
127
+ - robots.txt configuration
128
+ - OG image generation
129
+ - Plausible Analytics integration
130
+
131
+ ### File Upload
132
+
133
+ - TUS resumable uploads (`tus-js-client`)
134
+ - Pre-built `TusFileUpload.vue` component
135
+ - Progress tracking and error handling
136
+
137
+ ### Developer Experience
138
+
139
+ - OxLint for fast linting
140
+ - OxFmt for code formatting
141
+ - Auto-generated API client from OpenAPI
142
+ - Bug reporting to Linear (dev only via `@lenne.tech/bug.lt`)
143
+ - VueUse composition utilities
144
+ - dayjs for date/time handling
108
145
 
109
146
  ## Environment Variables
110
147
 
111
148
  Create a `.env` file with the following variables:
112
149
 
113
150
  ```env
151
+ # Required
114
152
  SITE_URL=http://localhost:3001
115
153
  API_URL=http://localhost:3000
116
154
  APP_ENV=development
@@ -124,7 +162,7 @@ WEB_PUSH_KEY= # Web push notifications
124
162
  LINEAR_API_KEY= # Bug reporting
125
163
  LINEAR_TEAM_NAME= # Bug reporting
126
164
  LINEAR_PROJECT_NAME= # Bug reporting
127
- API_SCHEMA=../api/schema.gql # API schema path
165
+ API_SCHEMA=../api/schema.gql # OpenAPI schema path
128
166
  STORAGE_PREFIX=base-dev # Local storage prefix
129
167
  ```
130
168
 
@@ -132,25 +170,34 @@ STORAGE_PREFIX=base-dev # Local storage prefix
132
170
 
133
171
  ```
134
172
  app/
135
- ├── assets/ # Tailwind CSS configuration
173
+ ├── assets/css/ # Tailwind CSS styles
136
174
  ├── components/ # Vue components (auto-imported)
175
+ │ ├── Modal/ # Modal components
176
+ │ ├── Transition/ # Transition animations
177
+ │ └── Upload/ # File upload components
137
178
  ├── composables/ # Composables (auto-imported)
138
- ├── interfaces/ # TypeScript interfaces (auto-imported)
139
- ├── layouts/ # Nuxt layouts
179
+ ├── use-better-auth.ts # Auth session helpers
180
+ ├── use-file.ts # File utilities
181
+ │ ├── use-share.ts # Share API
182
+ │ └── use-tus-upload.ts # TUS upload logic
183
+ ├── interfaces/ # TypeScript interfaces
184
+ ├── layouts/ # Nuxt layouts (default, slim)
185
+ ├── lib/ # Auth client configuration
186
+ ├── middleware/ # Route guards (auth, admin, guest)
140
187
  ├── pages/ # File-based routing
188
+ │ ├── auth/ # Authentication pages
189
+ │ └── app/ # Protected app pages
190
+ ├── utils/ # Utility functions
141
191
  └── app.config.ts # NuxtUI configuration
142
192
 
143
193
  docs/ # Dev-only documentation layer
144
194
  tests/ # Playwright E2E tests
145
195
  ```
146
196
 
147
- ## Development Guidelines
148
-
149
- For detailed coding standards and architecture information, see [CLAUDE.md](./CLAUDE.md).
150
-
151
197
  ## Documentation
152
198
 
153
199
  - [Nuxt Documentation](https://nuxt.com/docs)
154
200
  - [NuxtUI Documentation](https://ui.nuxt.com)
201
+ - [Better Auth Documentation](https://www.better-auth.com)
155
202
  - [Tailwind CSS Documentation](https://tailwindcss.com/docs)
156
203
  - [Vue 3 Documentation](https://vuejs.org)
@@ -1,25 +1,192 @@
1
1
  import { authClient } from '~/lib/auth-client';
2
2
 
3
+ /**
4
+ * User type for Better Auth session
5
+ */
6
+ interface BetterAuthUser {
7
+ email: string;
8
+ emailVerified?: boolean;
9
+ id: string;
10
+ name?: string;
11
+ role?: string;
12
+ twoFactorEnabled?: boolean;
13
+ }
14
+
15
+ /**
16
+ * Stored auth state (persisted in cookie for SSR compatibility)
17
+ */
18
+ interface StoredAuthState {
19
+ user: BetterAuthUser | null;
20
+ }
21
+
22
+ /**
23
+ * Better Auth composable with client-side state management
24
+ *
25
+ * This composable manages auth state using:
26
+ * 1. Client-side state stored in a cookie (for SSR compatibility)
27
+ * 2. Better Auth's session endpoint as a validation check
28
+ *
29
+ * The state is populated after login and cleared on logout.
30
+ */
3
31
  export function useBetterAuth() {
4
- const session = authClient.useSession();
32
+ // Use useCookie for SSR-compatible persistent state
33
+ const authState = useCookie<StoredAuthState>('auth-state', {
34
+ default: () => ({ user: null }),
35
+ maxAge: 60 * 60 * 24 * 7, // 7 days
36
+ sameSite: 'lax',
37
+ });
38
+
39
+ // Loading state
40
+ const isLoading = ref<boolean>(false);
5
41
 
6
- const user = computed<null | User>(() => (session.value.data?.user as User) ?? null);
42
+ // Computed properties based on stored state
43
+ const user = computed<BetterAuthUser | null>(() => authState.value?.user ?? null);
7
44
  const isAuthenticated = computed<boolean>(() => !!user.value);
8
45
  const isAdmin = computed<boolean>(() => user.value?.role === 'admin');
9
46
  const is2FAEnabled = computed<boolean>(() => user.value?.twoFactorEnabled ?? false);
10
- const isLoading = computed<boolean>(() => session.value.isPending);
47
+
48
+ /**
49
+ * Set user data after successful login/signup
50
+ */
51
+ function setUser(userData: BetterAuthUser | null): void {
52
+ authState.value = { user: userData };
53
+ }
54
+
55
+ /**
56
+ * Clear user data on logout
57
+ */
58
+ function clearUser(): void {
59
+ authState.value = { user: null };
60
+ }
61
+
62
+ /**
63
+ * Validate session with backend (called on app init)
64
+ * If session is invalid, clear the stored state
65
+ */
66
+ async function validateSession(): Promise<boolean> {
67
+ try {
68
+ // Try to get session from Better Auth
69
+ const session = authClient.useSession();
70
+
71
+ // Wait for session to load
72
+ if (session.value.isPending) {
73
+ await new Promise((resolve) => {
74
+ const unwatch = watch(
75
+ () => session.value.isPending,
76
+ (isPending) => {
77
+ if (!isPending) {
78
+ unwatch();
79
+ resolve(true);
80
+ }
81
+ },
82
+ { immediate: true },
83
+ );
84
+ });
85
+ }
86
+
87
+ // If session has user data, update our state
88
+ if (session.value.data?.user) {
89
+ setUser(session.value.data.user as BetterAuthUser);
90
+ return true;
91
+ }
92
+
93
+ // Session not found - check if we have a stored token cookie
94
+ // If we have auth-state but no session, it might be a mismatch
95
+ // For now, trust the stored state if token cookie exists
96
+ const tokenCookie = useCookie('token');
97
+ if (tokenCookie.value && authState.value?.user) {
98
+ // We have both token and stored user - trust it
99
+ return true;
100
+ }
101
+
102
+ // No valid session found - clear state
103
+ if (authState.value?.user) {
104
+ clearUser();
105
+ }
106
+ return false;
107
+ } catch (error) {
108
+ console.debug('Session validation failed:', error);
109
+ return !!authState.value?.user;
110
+ }
111
+ }
112
+
113
+ /**
114
+ * Sign in with email and password
115
+ */
116
+ const signIn = {
117
+ ...authClient.signIn,
118
+ email: async (params: { email: string; password: string; rememberMe?: boolean }, options?: any) => {
119
+ isLoading.value = true;
120
+ try {
121
+ const result = await authClient.signIn.email(params, options);
122
+
123
+ // Check for successful response with user data
124
+ if (result && 'user' in result && result.user) {
125
+ setUser(result.user as BetterAuthUser);
126
+ } else if (result && 'data' in result && result.data?.user) {
127
+ setUser(result.data.user as BetterAuthUser);
128
+ }
129
+
130
+ return result;
131
+ } finally {
132
+ isLoading.value = false;
133
+ }
134
+ },
135
+ };
136
+
137
+ /**
138
+ * Sign up with email and password
139
+ */
140
+ const signUp = {
141
+ ...authClient.signUp,
142
+ email: async (params: { email: string; name: string; password: string }, options?: any) => {
143
+ isLoading.value = true;
144
+ try {
145
+ const result = await authClient.signUp.email(params, options);
146
+
147
+ // Check for successful response with user data
148
+ if (result && 'user' in result && result.user) {
149
+ setUser(result.user as BetterAuthUser);
150
+ } else if (result && 'data' in result && result.data?.user) {
151
+ setUser(result.data.user as BetterAuthUser);
152
+ }
153
+
154
+ return result;
155
+ } finally {
156
+ isLoading.value = false;
157
+ }
158
+ },
159
+ };
160
+
161
+ /**
162
+ * Sign out
163
+ */
164
+ const signOut = async (options?: any) => {
165
+ isLoading.value = true;
166
+ try {
167
+ const result = await authClient.signOut(options);
168
+ // Clear user data on logout
169
+ clearUser();
170
+ return result;
171
+ } finally {
172
+ isLoading.value = false;
173
+ }
174
+ };
11
175
 
12
176
  return {
177
+ changePassword: authClient.changePassword,
178
+ clearUser,
13
179
  is2FAEnabled,
14
180
  isAdmin,
15
181
  isAuthenticated,
16
- isLoading,
182
+ isLoading: computed(() => isLoading.value),
17
183
  passkey: authClient.passkey,
18
- session,
19
- signIn: authClient.signIn,
20
- signOut: authClient.signOut,
21
- signUp: authClient.signUp,
184
+ setUser,
185
+ signIn,
186
+ signOut,
187
+ signUp,
22
188
  twoFactor: authClient.twoFactor,
23
189
  user,
190
+ validateSession,
24
191
  };
25
192
  }
@@ -34,102 +34,190 @@ export interface AuthResponse {
34
34
  };
35
35
  }
36
36
 
37
- // =============================================================================
38
- // Base Client Configuration
39
- // =============================================================================
40
-
41
- const baseClient = createAuthClient({
42
- basePath: '/iam', // IMPORTANT: Must match nest-server betterAuth.basePath, default: '/iam'
43
- baseURL: import.meta.env?.VITE_API_URL || process.env.API_URL || 'http://localhost:3000',
44
- plugins: [
45
- adminClient(),
46
- twoFactorClient({
47
- onTwoFactorRedirect() {
48
- navigateTo('/auth/2fa');
49
- },
50
- }),
51
- passkeyClient(),
52
- ],
53
- });
37
+ /**
38
+ * Configuration options for the auth client factory
39
+ * All options have sensible defaults for nest-server compatibility
40
+ */
41
+ export interface AuthClientConfig {
42
+ /** API base URL (default: from env or http://localhost:3000) */
43
+ baseURL?: string;
44
+ /** Auth API base path (default: '/iam' - must match nest-server betterAuth.basePath) */
45
+ basePath?: string;
46
+ /** 2FA redirect path (default: '/auth/2fa') */
47
+ twoFactorRedirectPath?: string;
48
+ /** Enable admin plugin (default: true) */
49
+ enableAdmin?: boolean;
50
+ /** Enable 2FA plugin (default: true) */
51
+ enableTwoFactor?: boolean;
52
+ /** Enable passkey plugin (default: true) */
53
+ enablePasskey?: boolean;
54
+ }
54
55
 
55
56
  // =============================================================================
56
- // Auth Client with Password Hashing
57
+ // Auth Client Factory
57
58
  // =============================================================================
58
59
 
59
60
  /**
60
- * Extended auth client that hashes passwords before transmission.
61
+ * Creates a configured Better-Auth client with password hashing
62
+ *
63
+ * This factory function allows creating auth clients with custom configuration,
64
+ * making it reusable across different projects.
65
+ *
66
+ * @example
67
+ * ```typescript
68
+ * // Default configuration (works with nest-server defaults)
69
+ * const authClient = createBetterAuthClient();
70
+ *
71
+ * // Custom configuration
72
+ * const authClient = createBetterAuthClient({
73
+ * baseURL: 'https://api.example.com',
74
+ * basePath: '/auth',
75
+ * twoFactorRedirectPath: '/login/2fa',
76
+ * });
77
+ * ```
61
78
  *
62
79
  * SECURITY: Passwords are hashed with SHA256 client-side to prevent
63
80
  * plain text password transmission over the network.
64
- *
65
- * The server's normalizePasswordForIam() detects SHA256 hashes (64 hex chars)
66
- * and processes them correctly.
67
81
  */
68
- export const authClient = {
69
- // Spread all base client properties and methods
70
- ...baseClient,
71
-
72
- /**
73
- * Change password for an authenticated user (both passwords are hashed)
74
- */
75
- changePassword: async (params: { currentPassword: string; newPassword: string }, options?: any) => {
76
- const [hashedCurrent, hashedNew] = await Promise.all([sha256(params.currentPassword), sha256(params.newPassword)]);
77
- return baseClient.changePassword?.({ currentPassword: hashedCurrent, newPassword: hashedNew }, options);
78
- },
79
-
80
- /**
81
- * Reset password with token (new password is hashed before sending)
82
- */
83
- resetPassword: async (params: { newPassword: string; token: string }, options?: any) => {
84
- const hashedPassword = await sha256(params.newPassword);
85
- return baseClient.resetPassword?.({ newPassword: hashedPassword, token: params.token }, options);
86
- },
87
-
88
- // Override signIn to hash password
89
- signIn: {
90
- ...baseClient.signIn,
91
- /**
92
- * Sign in with email and password (password is hashed before sending)
93
- */
94
- email: async (params: { email: string; password: string; rememberMe?: boolean }, options?: any) => {
95
- const hashedPassword = await sha256(params.password);
96
- return baseClient.signIn.email({ ...params, password: hashedPassword }, options);
82
+ export function createBetterAuthClient(config: AuthClientConfig = {}) {
83
+ const {
84
+ baseURL = import.meta.env?.VITE_API_URL || process.env.API_URL || 'http://localhost:3000',
85
+ basePath = '/iam',
86
+ twoFactorRedirectPath = '/auth/2fa',
87
+ enableAdmin = true,
88
+ enableTwoFactor = true,
89
+ enablePasskey = true,
90
+ } = config;
91
+
92
+ // Build plugins array based on configuration
93
+ const plugins: any[] = [];
94
+
95
+ if (enableAdmin) {
96
+ plugins.push(adminClient());
97
+ }
98
+
99
+ if (enableTwoFactor) {
100
+ plugins.push(
101
+ twoFactorClient({
102
+ onTwoFactorRedirect() {
103
+ navigateTo(twoFactorRedirectPath);
104
+ },
105
+ }),
106
+ );
107
+ }
108
+
109
+ if (enablePasskey) {
110
+ plugins.push(passkeyClient());
111
+ }
112
+
113
+ // Create base client with configuration
114
+ const baseClient = createAuthClient({
115
+ basePath,
116
+ baseURL,
117
+ fetchOptions: {
118
+ credentials: 'include', // Required for cross-origin cookie handling
97
119
  },
98
- },
120
+ plugins,
121
+ });
99
122
 
100
- // Explicitly pass through signOut (not captured by spread operator)
101
- signOut: baseClient.signOut,
123
+ // Return extended client with password hashing
124
+ return {
125
+ // Spread all base client properties and methods
126
+ ...baseClient,
127
+
128
+ // Explicitly pass through methods not captured by spread operator
129
+ useSession: baseClient.useSession,
130
+ passkey: (baseClient as any).passkey,
131
+ admin: (baseClient as any).admin,
132
+ $Infer: baseClient.$Infer,
133
+ $fetch: baseClient.$fetch,
134
+ $store: baseClient.$store,
102
135
 
103
- // Override signUp to hash password
104
- signUp: {
105
- ...baseClient.signUp,
106
136
  /**
107
- * Sign up with email and password (password is hashed before sending)
137
+ * Change password for an authenticated user (both passwords are hashed)
108
138
  */
109
- email: async (params: { email: string; name: string; password: string }, options?: any) => {
110
- const hashedPassword = await sha256(params.password);
111
- return baseClient.signUp.email({ ...params, password: hashedPassword }, options);
139
+ changePassword: async (params: { currentPassword: string; newPassword: string }, options?: any) => {
140
+ const [hashedCurrent, hashedNew] = await Promise.all([sha256(params.currentPassword), sha256(params.newPassword)]);
141
+ return baseClient.changePassword?.({ currentPassword: hashedCurrent, newPassword: hashedNew }, options);
112
142
  },
113
- },
114
143
 
115
- // Override twoFactor to hash passwords
116
- twoFactor: {
117
- ...baseClient.twoFactor,
118
144
  /**
119
- * Disable 2FA (password is hashed before sending)
145
+ * Reset password with token (new password is hashed before sending)
120
146
  */
121
- disable: async (params: { password: string }, options?: any) => {
122
- const hashedPassword = await sha256(params.password);
123
- return baseClient.twoFactor.disable({ password: hashedPassword }, options);
147
+ resetPassword: async (params: { newPassword: string; token: string }, options?: any) => {
148
+ const hashedPassword = await sha256(params.newPassword);
149
+ return baseClient.resetPassword?.({ newPassword: hashedPassword, token: params.token }, options);
124
150
  },
125
- /**
126
- * Enable 2FA (password is hashed before sending)
127
- */
128
- enable: async (params: { password: string }, options?: any) => {
129
- const hashedPassword = await sha256(params.password);
130
- return baseClient.twoFactor.enable({ password: hashedPassword }, options);
151
+
152
+ // Override signIn to hash password (keep passkey method from plugin)
153
+ signIn: {
154
+ ...baseClient.signIn,
155
+ /**
156
+ * Sign in with email and password (password is hashed before sending)
157
+ */
158
+ email: async (params: { email: string; password: string; rememberMe?: boolean }, options?: any) => {
159
+ const hashedPassword = await sha256(params.password);
160
+ return baseClient.signIn.email({ ...params, password: hashedPassword }, options);
161
+ },
162
+ /**
163
+ * Sign in with passkey (pass through to base client - provided by passkeyClient plugin)
164
+ * @see https://www.better-auth.com/docs/plugins/passkey
165
+ */
166
+ passkey: (baseClient.signIn as any).passkey,
167
+ },
168
+
169
+ // Explicitly pass through signOut (not captured by spread operator)
170
+ signOut: baseClient.signOut,
171
+
172
+ // Override signUp to hash password
173
+ signUp: {
174
+ ...baseClient.signUp,
175
+ /**
176
+ * Sign up with email and password (password is hashed before sending)
177
+ */
178
+ email: async (params: { email: string; name: string; password: string }, options?: any) => {
179
+ const hashedPassword = await sha256(params.password);
180
+ return baseClient.signUp.email({ ...params, password: hashedPassword }, options);
181
+ },
131
182
  },
132
- },
133
- };
134
183
 
135
- export type AuthClient = typeof authClient;
184
+ // Override twoFactor to hash passwords (provided by twoFactorClient plugin)
185
+ twoFactor: {
186
+ ...(baseClient as any).twoFactor,
187
+ /**
188
+ * Disable 2FA (password is hashed before sending)
189
+ */
190
+ disable: async (params: { password: string }, options?: any) => {
191
+ const hashedPassword = await sha256(params.password);
192
+ return (baseClient as any).twoFactor.disable({ password: hashedPassword }, options);
193
+ },
194
+ /**
195
+ * Enable 2FA (password is hashed before sending)
196
+ */
197
+ enable: async (params: { password: string }, options?: any) => {
198
+ const hashedPassword = await sha256(params.password);
199
+ return (baseClient as any).twoFactor.enable({ password: hashedPassword }, options);
200
+ },
201
+ /**
202
+ * Verify TOTP code (pass through to base client)
203
+ */
204
+ verifyTotp: (baseClient as any).twoFactor.verifyTotp,
205
+ /**
206
+ * Verify backup code (pass through to base client)
207
+ */
208
+ verifyBackupCode: (baseClient as any).twoFactor.verifyBackupCode,
209
+ },
210
+ };
211
+ }
212
+
213
+ // =============================================================================
214
+ // Default Auth Client Instance
215
+ // =============================================================================
216
+
217
+ /**
218
+ * Default auth client instance with standard nest-server configuration
219
+ * Use createBetterAuthClient() for custom configuration
220
+ */
221
+ export const authClient = createBetterAuthClient();
222
+
223
+ export type AuthClient = ReturnType<typeof createBetterAuthClient>;
@@ -13,6 +13,7 @@ import { authClient } from '~/lib/auth-client';
13
13
  // Composables
14
14
  // ============================================================================
15
15
  const toast = useToast();
16
+ const { setUser, validateSession } = useBetterAuth();
16
17
 
17
18
  // ============================================================================
18
19
  // Page Meta
@@ -28,6 +29,9 @@ const loading = ref<boolean>(false);
28
29
  const useBackupCode = ref<boolean>(false);
29
30
  const trustDevice = ref<boolean>(false);
30
31
 
32
+ // Form state for UForm
33
+ const formState = reactive({ code: '' });
34
+
31
35
  const schema = v.object({
32
36
  code: v.pipe(v.string('Code ist erforderlich'), v.minLength(6, 'Code muss mindestens 6 Zeichen haben')),
33
37
  });
@@ -41,35 +45,46 @@ async function onSubmit(payload: FormSubmitEvent<Schema>): Promise<void> {
41
45
  loading.value = true;
42
46
 
43
47
  try {
48
+ let result: any;
49
+
44
50
  if (useBackupCode.value) {
45
- const { error } = await authClient.twoFactor.verifyBackupCode({
51
+ result = await authClient.twoFactor.verifyBackupCode({
46
52
  code: payload.data.code,
47
53
  });
48
54
 
49
- if (error) {
55
+ if (result.error) {
50
56
  toast.add({
51
57
  color: 'error',
52
- description: error.message || 'Backup-Code ungültig',
58
+ description: result.error.message || 'Backup-Code ungültig',
53
59
  title: 'Fehler',
54
60
  });
55
61
  return;
56
62
  }
57
63
  } else {
58
- const { error } = await authClient.twoFactor.verifyTotp({
64
+ result = await authClient.twoFactor.verifyTotp({
59
65
  code: payload.data.code,
60
66
  trustDevice: trustDevice.value,
61
67
  });
62
68
 
63
- if (error) {
69
+ if (result.error) {
64
70
  toast.add({
65
71
  color: 'error',
66
- description: error.message || 'Code ungültig',
72
+ description: result.error.message || 'Code ungültig',
67
73
  title: 'Fehler',
68
74
  });
69
75
  return;
70
76
  }
71
77
  }
72
78
 
79
+ // Update auth state with user data from response
80
+ const userData = result?.data?.user || result?.user;
81
+ if (userData) {
82
+ setUser(userData);
83
+ } else {
84
+ // Fallback: validate session to get user data
85
+ await validateSession();
86
+ }
87
+
73
88
  await navigateTo('/app');
74
89
  } finally {
75
90
  loading.value = false;
@@ -92,10 +107,10 @@ function toggleBackupCode(): void {
92
107
  </p>
93
108
  </div>
94
109
 
95
- <UForm :schema="schema" class="flex flex-col gap-4" @submit="onSubmit">
110
+ <UForm :schema="schema" :state="formState" class="flex flex-col gap-4" @submit="onSubmit">
96
111
  <UFormField :label="useBackupCode ? 'Backup-Code' : 'Authentifizierungscode'" name="code">
97
112
  <UInput
98
- name="code"
113
+ v-model="formState.code"
99
114
  :placeholder="useBackupCode ? 'Backup-Code eingeben' : '000000'"
100
115
  size="lg"
101
116
  class="text-center font-mono text-lg tracking-widest"
@@ -13,6 +13,7 @@ import { authClient } from '~/lib/auth-client';
13
13
  // Composables
14
14
  // ============================================================================
15
15
  const toast = useToast();
16
+ const { signIn, setUser, isLoading, validateSession } = useBetterAuth();
16
17
 
17
18
  // ============================================================================
18
19
  // Page Meta
@@ -51,22 +52,53 @@ const schema = v.object({
51
52
 
52
53
  type Schema = InferOutput<typeof schema>;
53
54
 
55
+ /**
56
+ * Handle passkey authentication
57
+ * Uses official Better Auth signIn.passkey() method
58
+ * @see https://www.better-auth.com/docs/plugins/passkey
59
+ */
54
60
  async function onPasskeyLogin(): Promise<void> {
55
61
  passkeyLoading.value = true;
56
62
 
57
63
  try {
58
- const { error } = await authClient.signIn.passkey();
64
+ // Use official Better Auth client method
65
+ // This calls: GET /passkey/generate-authenticate-options → POST /passkey/verify-authentication
66
+ const result = await authClient.signIn.passkey();
59
67
 
60
- if (error) {
68
+ // Check for error in response
69
+ if (result.error) {
61
70
  toast.add({
62
71
  color: 'error',
63
- description: error.message || 'Passkey-Anmeldung fehlgeschlagen',
72
+ description: result.error.message || 'Passkey-Anmeldung fehlgeschlagen',
64
73
  title: 'Fehler',
65
74
  });
66
75
  return;
67
76
  }
68
77
 
78
+ // Update auth state with user data if available
79
+ if (result.data?.user) {
80
+ setUser(result.data.user as any);
81
+ } else if (result.data?.session) {
82
+ // Passkey auth returns session without user - fetch user via session validation
83
+ await validateSession();
84
+ }
85
+
69
86
  await navigateTo('/app');
87
+ } catch (err: unknown) {
88
+ // Handle WebAuthn-specific errors
89
+ if (err instanceof Error && err.name === 'NotAllowedError') {
90
+ toast.add({
91
+ color: 'error',
92
+ description: 'Passkey-Authentifizierung wurde abgebrochen',
93
+ title: 'Fehler',
94
+ });
95
+ return;
96
+ }
97
+ toast.add({
98
+ color: 'error',
99
+ description: err instanceof Error ? err.message : 'Passkey-Anmeldung fehlgeschlagen',
100
+ title: 'Fehler',
101
+ });
70
102
  } finally {
71
103
  passkeyLoading.value = false;
72
104
  }
@@ -79,21 +111,48 @@ async function onSubmit(payload: FormSubmitEvent<Schema>): Promise<void> {
79
111
  loading.value = true;
80
112
 
81
113
  try {
82
- const { error } = await authClient.signIn.email({
114
+ const result = await signIn.email({
83
115
  email: payload.data.email,
84
116
  password: payload.data.password,
85
117
  });
86
118
 
87
- if (error) {
119
+ // Check for error in response
120
+ if ('error' in result && result.error) {
88
121
  toast.add({
89
122
  color: 'error',
90
- description: error.message || 'Anmeldung fehlgeschlagen',
123
+ description: (result.error as { message?: string }).message || 'Anmeldung fehlgeschlagen',
91
124
  title: 'Fehler',
92
125
  });
93
126
  return;
94
127
  }
95
128
 
96
- await navigateTo('/app');
129
+ // Check if 2FA is required
130
+ const resultData = 'data' in result ? result.data : result;
131
+ if (resultData && 'twoFactorRedirect' in resultData && resultData.twoFactorRedirect) {
132
+ // Redirect to 2FA page
133
+ await navigateTo('/auth/2fa');
134
+ return;
135
+ }
136
+
137
+ // Check if login was successful (user data in response)
138
+ const userData = 'user' in result ? result.user : ('data' in result ? result.data?.user : null);
139
+ if (userData) {
140
+ // Auth state is already stored by useBetterAuth
141
+ // Navigate to app
142
+ await navigateTo('/app');
143
+ } else {
144
+ toast.add({
145
+ color: 'error',
146
+ description: 'Anmeldung fehlgeschlagen - keine Benutzerdaten erhalten',
147
+ title: 'Fehler',
148
+ });
149
+ }
150
+ } catch (err) {
151
+ toast.add({
152
+ color: 'error',
153
+ description: 'Ein unerwarteter Fehler ist aufgetreten',
154
+ title: 'Fehler',
155
+ });
97
156
  } finally {
98
157
  loading.value = false;
99
158
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-nuxt-base",
3
- "version": "1.0.2",
3
+ "version": "1.1.0",
4
4
  "description": "Starter to generate a configured environment with VueJS, Nuxt, Tailwind, Eslint, Unit Tests, Playwright etc.",
5
5
  "license": "MIT",
6
6
  "repository": {