create-nuxt-base 2.0.0 → 2.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/CHANGELOG.md +7 -0
- package/nuxt-base-template/.github/workflows/test.yml +90 -0
- package/nuxt-base-template/app/pages/app/settings/security.vue +52 -41
- package/nuxt-base-template/app/pages/auth/login.vue +4 -2
- package/nuxt-base-template/app/pages/auth/register.vue +84 -60
- package/nuxt-base-template/nuxt.config.ts +13 -1
- package/nuxt-base-template/package-lock.json +1169 -9
- package/nuxt-base-template/package.json +27 -3
- package/nuxt-base-template/playwright.config.ts +1 -1
- package/nuxt-base-template/tests/e2e/auth.spec.ts +467 -0
- package/nuxt-base-template/tests/unit/auth/auth.spec.ts +439 -0
- package/nuxt-base-template/tests/unit/auth/error-translation.spec.ts +279 -0
- package/nuxt-base-template/tests/unit/mocks/auth-client.mock.ts +165 -0
- package/nuxt-base-template/tests/unit/mocks/nuxt-imports.ts +105 -0
- package/nuxt-base-template/tests/unit/setup.ts +56 -0
- package/nuxt-base-template/vitest.config.ts +25 -0
- package/package.json +1 -1
- package/nuxt-base-template/tests/iam.spec.ts +0 -247
- /package/nuxt-base-template/tests/{init.spec.ts → e2e/init.spec.ts} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
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.1.0](https://github.com/lenneTech/nuxt-base-starter/compare/v2.0.0...v2.1.0) (2026-01-24)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* **tests, i18n:** add test infrastructure and error translation integration ([f8aff01](https://github.com/lenneTech/nuxt-base-starter/commit/f8aff01b5c763ea54cee270e10a1a29e2bece867))
|
|
11
|
+
|
|
5
12
|
## [2.0.0](https://github.com/lenneTech/nuxt-base-starter/compare/v1.2.0...v2.0.0) (2026-01-24)
|
|
6
13
|
|
|
7
14
|
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
name: Tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main, develop]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main, develop]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
unit-tests:
|
|
11
|
+
name: Unit Tests
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
|
|
14
|
+
steps:
|
|
15
|
+
- name: Checkout repository
|
|
16
|
+
uses: actions/checkout@v4
|
|
17
|
+
|
|
18
|
+
- name: Setup Node.js
|
|
19
|
+
uses: actions/setup-node@v4
|
|
20
|
+
with:
|
|
21
|
+
node-version: '22'
|
|
22
|
+
cache: 'npm'
|
|
23
|
+
|
|
24
|
+
- name: Install dependencies
|
|
25
|
+
run: npm ci
|
|
26
|
+
|
|
27
|
+
- name: Run unit tests
|
|
28
|
+
run: npm run test:unit
|
|
29
|
+
|
|
30
|
+
- name: Run unit tests with coverage
|
|
31
|
+
run: npm run test:unit:coverage
|
|
32
|
+
|
|
33
|
+
- name: Upload coverage report
|
|
34
|
+
uses: actions/upload-artifact@v4
|
|
35
|
+
if: always()
|
|
36
|
+
with:
|
|
37
|
+
name: coverage-report
|
|
38
|
+
path: coverage/
|
|
39
|
+
retention-days: 7
|
|
40
|
+
|
|
41
|
+
lint:
|
|
42
|
+
name: Lint
|
|
43
|
+
runs-on: ubuntu-latest
|
|
44
|
+
|
|
45
|
+
steps:
|
|
46
|
+
- name: Checkout repository
|
|
47
|
+
uses: actions/checkout@v4
|
|
48
|
+
|
|
49
|
+
- name: Setup Node.js
|
|
50
|
+
uses: actions/setup-node@v4
|
|
51
|
+
with:
|
|
52
|
+
node-version: '22'
|
|
53
|
+
cache: 'npm'
|
|
54
|
+
|
|
55
|
+
- name: Install dependencies
|
|
56
|
+
run: npm ci
|
|
57
|
+
|
|
58
|
+
- name: Run linter
|
|
59
|
+
run: npm run lint
|
|
60
|
+
|
|
61
|
+
- name: Check formatting
|
|
62
|
+
run: npm run format:check
|
|
63
|
+
|
|
64
|
+
build:
|
|
65
|
+
name: Build
|
|
66
|
+
runs-on: ubuntu-latest
|
|
67
|
+
needs: [unit-tests, lint]
|
|
68
|
+
|
|
69
|
+
steps:
|
|
70
|
+
- name: Checkout repository
|
|
71
|
+
uses: actions/checkout@v4
|
|
72
|
+
|
|
73
|
+
- name: Setup Node.js
|
|
74
|
+
uses: actions/setup-node@v4
|
|
75
|
+
with:
|
|
76
|
+
node-version: '22'
|
|
77
|
+
cache: 'npm'
|
|
78
|
+
|
|
79
|
+
- name: Install dependencies
|
|
80
|
+
run: npm ci
|
|
81
|
+
|
|
82
|
+
- name: Build application
|
|
83
|
+
run: npm run build
|
|
84
|
+
|
|
85
|
+
- name: Upload build artifacts
|
|
86
|
+
uses: actions/upload-artifact@v4
|
|
87
|
+
with:
|
|
88
|
+
name: build
|
|
89
|
+
path: .output/
|
|
90
|
+
retention-days: 7
|
|
@@ -2,11 +2,6 @@
|
|
|
2
2
|
// ============================================================================
|
|
3
3
|
// Imports
|
|
4
4
|
// ============================================================================
|
|
5
|
-
import type { FormSubmitEvent } from '@nuxt/ui';
|
|
6
|
-
import type { InferOutput } from 'valibot';
|
|
7
|
-
|
|
8
|
-
import * as v from 'valibot';
|
|
9
|
-
|
|
10
5
|
import ModalBackupCodes from '~/components/Modal/ModalBackupCodes.vue';
|
|
11
6
|
|
|
12
7
|
// ============================================================================
|
|
@@ -39,21 +34,10 @@ const passkeyLoading = ref<boolean>(false);
|
|
|
39
34
|
const newPasskeyName = ref<string>('');
|
|
40
35
|
const showAddPasskey = ref<boolean>(false);
|
|
41
36
|
|
|
42
|
-
// Form states
|
|
43
|
-
const
|
|
44
|
-
const
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
const passwordSchema = v.object({
|
|
48
|
-
password: v.pipe(v.string('Passwort ist erforderlich'), v.minLength(1, 'Passwort ist erforderlich')),
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
const totpSchema = v.object({
|
|
52
|
-
code: v.pipe(v.string('Code ist erforderlich'), v.length(6, 'Code muss 6 Ziffern haben')),
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
type PasswordSchema = InferOutput<typeof passwordSchema>;
|
|
56
|
-
type TotpSchema = InferOutput<typeof totpSchema>;
|
|
37
|
+
// Form states - using reactive for direct v-model binding
|
|
38
|
+
const enable2FAPassword = ref('');
|
|
39
|
+
const disable2FAPassword = ref('');
|
|
40
|
+
const totpCode = ref('');
|
|
57
41
|
|
|
58
42
|
// ============================================================================
|
|
59
43
|
// Lifecycle Hooks
|
|
@@ -130,12 +114,21 @@ async function deletePasskey(id: string): Promise<void> {
|
|
|
130
114
|
}
|
|
131
115
|
}
|
|
132
116
|
|
|
133
|
-
async function disable2FA(
|
|
117
|
+
async function disable2FA(): Promise<void> {
|
|
118
|
+
if (!disable2FAPassword.value) {
|
|
119
|
+
toast.add({
|
|
120
|
+
color: 'error',
|
|
121
|
+
description: 'Passwort ist erforderlich',
|
|
122
|
+
title: 'Validierungsfehler',
|
|
123
|
+
});
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
134
127
|
loading.value = true;
|
|
135
128
|
|
|
136
129
|
try {
|
|
137
130
|
const { error } = await authClient.twoFactor.disable({
|
|
138
|
-
password:
|
|
131
|
+
password: disable2FAPassword.value,
|
|
139
132
|
});
|
|
140
133
|
|
|
141
134
|
if (error) {
|
|
@@ -159,18 +152,27 @@ async function disable2FA(payload: FormSubmitEvent<PasswordSchema>): Promise<voi
|
|
|
159
152
|
});
|
|
160
153
|
|
|
161
154
|
show2FADisable.value = false;
|
|
162
|
-
|
|
155
|
+
disable2FAPassword.value = '';
|
|
163
156
|
} finally {
|
|
164
157
|
loading.value = false;
|
|
165
158
|
}
|
|
166
159
|
}
|
|
167
160
|
|
|
168
|
-
async function enable2FA(
|
|
161
|
+
async function enable2FA(): Promise<void> {
|
|
162
|
+
if (!enable2FAPassword.value) {
|
|
163
|
+
toast.add({
|
|
164
|
+
color: 'error',
|
|
165
|
+
description: 'Passwort ist erforderlich',
|
|
166
|
+
title: 'Validierungsfehler',
|
|
167
|
+
});
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
169
171
|
loading.value = true;
|
|
170
172
|
|
|
171
173
|
try {
|
|
172
174
|
const { data, error } = await authClient.twoFactor.enable({
|
|
173
|
-
password:
|
|
175
|
+
password: enable2FAPassword.value,
|
|
174
176
|
});
|
|
175
177
|
|
|
176
178
|
if (error) {
|
|
@@ -185,7 +187,7 @@ async function enable2FA(payload: FormSubmitEvent<PasswordSchema>): Promise<void
|
|
|
185
187
|
totpUri.value = data?.totpURI ?? '';
|
|
186
188
|
backupCodes.value = data?.backupCodes ?? [];
|
|
187
189
|
showTotpSetup.value = true;
|
|
188
|
-
|
|
190
|
+
enable2FAPassword.value = '';
|
|
189
191
|
} finally {
|
|
190
192
|
loading.value = false;
|
|
191
193
|
}
|
|
@@ -218,12 +220,21 @@ async function openBackupCodesModal(codes: string[] = []): Promise<void> {
|
|
|
218
220
|
await modal.open();
|
|
219
221
|
}
|
|
220
222
|
|
|
221
|
-
async function verifyTotp(
|
|
223
|
+
async function verifyTotp(): Promise<void> {
|
|
224
|
+
if (!totpCode.value || totpCode.value.length !== 6) {
|
|
225
|
+
toast.add({
|
|
226
|
+
color: 'error',
|
|
227
|
+
description: 'Code muss 6 Ziffern haben',
|
|
228
|
+
title: 'Validierungsfehler',
|
|
229
|
+
});
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
|
|
222
233
|
loading.value = true;
|
|
223
234
|
|
|
224
235
|
try {
|
|
225
236
|
const { error } = await authClient.twoFactor.verifyTotp({
|
|
226
|
-
code:
|
|
237
|
+
code: totpCode.value,
|
|
227
238
|
});
|
|
228
239
|
|
|
229
240
|
if (error) {
|
|
@@ -247,7 +258,7 @@ async function verifyTotp(payload: FormSubmitEvent<TotpSchema>): Promise<void> {
|
|
|
247
258
|
});
|
|
248
259
|
|
|
249
260
|
showTotpSetup.value = false;
|
|
250
|
-
|
|
261
|
+
totpCode.value = '';
|
|
251
262
|
await openBackupCodesModal(backupCodes.value);
|
|
252
263
|
} finally {
|
|
253
264
|
loading.value = false;
|
|
@@ -288,12 +299,12 @@ async function verifyTotp(payload: FormSubmitEvent<TotpSchema>): Promise<void> {
|
|
|
288
299
|
</div>
|
|
289
300
|
|
|
290
301
|
<template v-if="!is2FAEnabled && !showTotpSetup">
|
|
291
|
-
<
|
|
292
|
-
<UFormField label="Passwort bestätigen" name="password">
|
|
293
|
-
<UInput v-model="
|
|
302
|
+
<form class="space-y-4" @submit.prevent="enable2FA">
|
|
303
|
+
<UFormField label="Passwort bestätigen" name="password" class="w-full">
|
|
304
|
+
<UInput v-model="enable2FAPassword" type="password" placeholder="Dein Passwort" class="w-full" />
|
|
294
305
|
</UFormField>
|
|
295
306
|
<UButton type="submit" :loading="loading"> 2FA aktivieren </UButton>
|
|
296
|
-
</
|
|
307
|
+
</form>
|
|
297
308
|
</template>
|
|
298
309
|
|
|
299
310
|
<template v-if="showTotpSetup">
|
|
@@ -304,15 +315,15 @@ async function verifyTotp(payload: FormSubmitEvent<TotpSchema>): Promise<void> {
|
|
|
304
315
|
<img v-if="totpUri" :src="`https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=${encodeURIComponent(totpUri)}`" alt="TOTP QR Code" class="rounded-lg" />
|
|
305
316
|
</div>
|
|
306
317
|
|
|
307
|
-
<
|
|
308
|
-
<UFormField label="Verifizierungscode" name="code">
|
|
309
|
-
<UInput v-model="
|
|
318
|
+
<form class="space-y-4" @submit.prevent="verifyTotp">
|
|
319
|
+
<UFormField label="Verifizierungscode" name="code" class="w-full">
|
|
320
|
+
<UInput v-model="totpCode" placeholder="000000" class="w-full text-center font-mono" />
|
|
310
321
|
</UFormField>
|
|
311
322
|
<div class="flex gap-2">
|
|
312
323
|
<UButton type="submit" :loading="loading"> Verifizieren </UButton>
|
|
313
324
|
<UButton variant="outline" color="neutral" @click="showTotpSetup = false"> Abbrechen </UButton>
|
|
314
325
|
</div>
|
|
315
|
-
</
|
|
326
|
+
</form>
|
|
316
327
|
</div>
|
|
317
328
|
</template>
|
|
318
329
|
|
|
@@ -324,16 +335,16 @@ async function verifyTotp(payload: FormSubmitEvent<TotpSchema>): Promise<void> {
|
|
|
324
335
|
</template>
|
|
325
336
|
|
|
326
337
|
<template v-if="show2FADisable">
|
|
327
|
-
<
|
|
338
|
+
<form class="space-y-4" @submit.prevent="disable2FA">
|
|
328
339
|
<UAlert color="warning" icon="i-lucide-alert-triangle"> 2FA zu deaktivieren verringert die Sicherheit deines Kontos. </UAlert>
|
|
329
|
-
<UFormField label="Passwort bestätigen" name="password">
|
|
330
|
-
<UInput v-model="
|
|
340
|
+
<UFormField label="Passwort bestätigen" name="password" class="w-full">
|
|
341
|
+
<UInput v-model="disable2FAPassword" type="password" placeholder="Dein Passwort" class="w-full" />
|
|
331
342
|
</UFormField>
|
|
332
343
|
<div class="flex gap-2">
|
|
333
344
|
<UButton type="submit" color="error" :loading="loading"> 2FA deaktivieren </UButton>
|
|
334
345
|
<UButton variant="outline" color="neutral" @click="show2FADisable = false"> Abbrechen </UButton>
|
|
335
346
|
</div>
|
|
336
|
-
</
|
|
347
|
+
</form>
|
|
337
348
|
</template>
|
|
338
349
|
</div>
|
|
339
350
|
</UCard>
|
|
@@ -12,6 +12,7 @@ import * as v from 'valibot';
|
|
|
12
12
|
// ============================================================================
|
|
13
13
|
const toast = useToast();
|
|
14
14
|
const { signIn, setUser, isLoading, validateSession, authenticateWithPasskey } = useLtAuth();
|
|
15
|
+
const { translateError } = useLtErrorTranslation();
|
|
15
16
|
|
|
16
17
|
// ============================================================================
|
|
17
18
|
// Page Meta
|
|
@@ -114,10 +115,11 @@ async function onSubmit(payload: FormSubmitEvent<Schema>): Promise<void> {
|
|
|
114
115
|
|
|
115
116
|
// Check for error in response
|
|
116
117
|
if ('error' in result && result.error) {
|
|
118
|
+
const errorMessage = (result.error as { message?: string }).message || 'Anmeldung fehlgeschlagen';
|
|
117
119
|
toast.add({
|
|
118
120
|
color: 'error',
|
|
119
|
-
description: (
|
|
120
|
-
title: '
|
|
121
|
+
description: translateError(errorMessage),
|
|
122
|
+
title: 'Anmeldung fehlgeschlagen',
|
|
121
123
|
});
|
|
122
124
|
return;
|
|
123
125
|
}
|
|
@@ -2,9 +2,6 @@
|
|
|
2
2
|
// ============================================================================
|
|
3
3
|
// Imports
|
|
4
4
|
// ============================================================================
|
|
5
|
-
import type { AuthFormField, FormSubmitEvent } from '@nuxt/ui';
|
|
6
|
-
import type { InferOutput } from 'valibot';
|
|
7
|
-
|
|
8
5
|
import * as v from 'valibot';
|
|
9
6
|
|
|
10
7
|
// ============================================================================
|
|
@@ -27,36 +24,13 @@ const loading = ref<boolean>(false);
|
|
|
27
24
|
const showPasskeyPrompt = ref<boolean>(false);
|
|
28
25
|
const passkeyLoading = ref<boolean>(false);
|
|
29
26
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
},
|
|
38
|
-
{
|
|
39
|
-
label: 'E-Mail',
|
|
40
|
-
name: 'email',
|
|
41
|
-
placeholder: 'E-Mail eingeben',
|
|
42
|
-
required: true,
|
|
43
|
-
type: 'email',
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
label: 'Passwort',
|
|
47
|
-
name: 'password',
|
|
48
|
-
placeholder: 'Passwort eingeben',
|
|
49
|
-
required: true,
|
|
50
|
-
type: 'password',
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
label: 'Passwort bestätigen',
|
|
54
|
-
name: 'confirmPassword',
|
|
55
|
-
placeholder: 'Passwort wiederholen',
|
|
56
|
-
required: true,
|
|
57
|
-
type: 'password',
|
|
58
|
-
},
|
|
59
|
-
];
|
|
27
|
+
// Form state - using refs for direct v-model binding
|
|
28
|
+
const formState = reactive({
|
|
29
|
+
name: '',
|
|
30
|
+
email: '',
|
|
31
|
+
password: '',
|
|
32
|
+
confirmPassword: '',
|
|
33
|
+
});
|
|
60
34
|
|
|
61
35
|
const schema = v.pipe(
|
|
62
36
|
v.object({
|
|
@@ -71,20 +45,30 @@ const schema = v.pipe(
|
|
|
71
45
|
),
|
|
72
46
|
);
|
|
73
47
|
|
|
74
|
-
type Schema = InferOutput<typeof schema>;
|
|
75
|
-
|
|
76
48
|
// ============================================================================
|
|
77
49
|
// Functions
|
|
78
50
|
// ============================================================================
|
|
79
|
-
async function onSubmit(
|
|
51
|
+
async function onSubmit(): Promise<void> {
|
|
52
|
+
// Validate
|
|
53
|
+
const result = v.safeParse(schema, formState);
|
|
54
|
+
if (!result.success) {
|
|
55
|
+
const firstError = result.issues[0];
|
|
56
|
+
toast.add({
|
|
57
|
+
color: 'error',
|
|
58
|
+
description: firstError.message,
|
|
59
|
+
title: 'Validierungsfehler',
|
|
60
|
+
});
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
80
64
|
loading.value = true;
|
|
81
65
|
|
|
82
66
|
try {
|
|
83
67
|
// Step 1: Sign up
|
|
84
68
|
const signUpResult = await signUp.email({
|
|
85
|
-
email:
|
|
86
|
-
name:
|
|
87
|
-
password:
|
|
69
|
+
email: formState.email,
|
|
70
|
+
name: formState.name,
|
|
71
|
+
password: formState.password,
|
|
88
72
|
});
|
|
89
73
|
|
|
90
74
|
const signUpError = 'error' in signUpResult ? signUpResult.error : null;
|
|
@@ -100,8 +84,8 @@ async function onSubmit(payload: FormSubmitEvent<Schema>): Promise<void> {
|
|
|
100
84
|
|
|
101
85
|
// Step 2: Sign in to create session (required for passkey registration)
|
|
102
86
|
const signInResult = await signIn.email({
|
|
103
|
-
email:
|
|
104
|
-
password:
|
|
87
|
+
email: formState.email,
|
|
88
|
+
password: formState.password,
|
|
105
89
|
});
|
|
106
90
|
|
|
107
91
|
const signInError = 'error' in signInResult ? signInResult.error : null;
|
|
@@ -168,25 +152,65 @@ async function skipPasskey(): Promise<void> {
|
|
|
168
152
|
<template>
|
|
169
153
|
<UPageCard class="w-md" variant="naked">
|
|
170
154
|
<template v-if="!showPasskeyPrompt">
|
|
171
|
-
<
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
155
|
+
<div class="flex flex-col items-center gap-4">
|
|
156
|
+
<UIcon name="i-lucide-user-plus" class="size-12 text-primary" />
|
|
157
|
+
<h1 class="text-2xl font-semibold">Registrieren</h1>
|
|
158
|
+
</div>
|
|
159
|
+
|
|
160
|
+
<form class="mt-6 flex flex-col gap-4" @submit.prevent="onSubmit">
|
|
161
|
+
<UFormField label="Name" name="name" required class="w-full">
|
|
162
|
+
<UInput
|
|
163
|
+
v-model="formState.name"
|
|
164
|
+
name="name"
|
|
165
|
+
type="text"
|
|
166
|
+
placeholder="Name eingeben"
|
|
167
|
+
autocomplete="name"
|
|
168
|
+
class="w-full"
|
|
169
|
+
/>
|
|
170
|
+
</UFormField>
|
|
171
|
+
|
|
172
|
+
<UFormField label="E-Mail" name="email" required class="w-full">
|
|
173
|
+
<UInput
|
|
174
|
+
v-model="formState.email"
|
|
175
|
+
name="email"
|
|
176
|
+
type="email"
|
|
177
|
+
placeholder="E-Mail eingeben"
|
|
178
|
+
autocomplete="email"
|
|
179
|
+
class="w-full"
|
|
180
|
+
/>
|
|
181
|
+
</UFormField>
|
|
182
|
+
|
|
183
|
+
<UFormField label="Passwort" name="password" required class="w-full">
|
|
184
|
+
<UInput
|
|
185
|
+
v-model="formState.password"
|
|
186
|
+
name="password"
|
|
187
|
+
type="password"
|
|
188
|
+
placeholder="Passwort eingeben"
|
|
189
|
+
autocomplete="new-password"
|
|
190
|
+
class="w-full"
|
|
191
|
+
/>
|
|
192
|
+
</UFormField>
|
|
193
|
+
|
|
194
|
+
<UFormField label="Passwort bestätigen" name="confirmPassword" required class="w-full">
|
|
195
|
+
<UInput
|
|
196
|
+
v-model="formState.confirmPassword"
|
|
197
|
+
name="confirmPassword"
|
|
198
|
+
type="password"
|
|
199
|
+
placeholder="Passwort wiederholen"
|
|
200
|
+
autocomplete="new-password"
|
|
201
|
+
class="w-full"
|
|
202
|
+
/>
|
|
203
|
+
</UFormField>
|
|
204
|
+
|
|
205
|
+
<UButton type="submit" block :loading="loading">
|
|
206
|
+
Konto erstellen
|
|
207
|
+
</UButton>
|
|
208
|
+
</form>
|
|
209
|
+
|
|
210
|
+
<p class="mt-4 text-center text-sm text-muted">
|
|
211
|
+
Bereits ein Konto?
|
|
212
|
+
<ULink to="/auth/login" class="text-primary font-medium">Anmelden</ULink>
|
|
213
|
+
</p>
|
|
190
214
|
</template>
|
|
191
215
|
|
|
192
216
|
<template v-else>
|
|
@@ -73,6 +73,10 @@ export default defineNuxtConfig({
|
|
|
73
73
|
ltExtensions: {
|
|
74
74
|
auth: {
|
|
75
75
|
enabled: true,
|
|
76
|
+
// baseURL is used in production mode for cross-origin API requests
|
|
77
|
+
// In dev mode, Nuxt proxy is used (baseURL is ignored, requests go through /api/iam)
|
|
78
|
+
// In production, requests go directly to baseURL + basePath (e.g., https://api.example.com/iam)
|
|
79
|
+
baseURL: process.env.API_URL || 'http://localhost:3000',
|
|
76
80
|
basePath: '/iam',
|
|
77
81
|
loginPath: '/auth/login',
|
|
78
82
|
twoFactorRedirectPath: '/auth/2fa',
|
|
@@ -184,11 +188,19 @@ export default defineNuxtConfig({
|
|
|
184
188
|
plugins: [tailwindcss()],
|
|
185
189
|
server: {
|
|
186
190
|
proxy: {
|
|
191
|
+
// IAM proxy via /api prefix (nuxt-extensions adds /api in dev mode)
|
|
192
|
+
// Must be before /api to match more specifically
|
|
193
|
+
'/api/iam': {
|
|
194
|
+
target: 'http://localhost:3000',
|
|
195
|
+
changeOrigin: true,
|
|
196
|
+
rewrite: (path) => path.replace(/^\/api/, ''),
|
|
197
|
+
},
|
|
198
|
+
// API proxy - no rewrite, backend expects /api/... paths
|
|
187
199
|
'/api': {
|
|
188
200
|
target: 'http://localhost:3000',
|
|
189
201
|
changeOrigin: true,
|
|
190
|
-
rewrite: (path: string) => path.replace(/^\/api/, ''),
|
|
191
202
|
},
|
|
203
|
+
// IAM proxy for direct BetterAuth endpoints (SSR mode)
|
|
192
204
|
'/iam': {
|
|
193
205
|
target: 'http://localhost:3000',
|
|
194
206
|
changeOrigin: true,
|