create-nuxt-base 1.2.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 +40 -0
- package/nuxt-base-template/.github/workflows/test.yml +90 -0
- package/nuxt-base-template/app/components/Modal/ModalBackupCodes.vue +2 -1
- package/nuxt-base-template/app/components/Upload/TusFileUpload.vue +7 -7
- package/nuxt-base-template/app/interfaces/user.interface.ts +5 -12
- package/nuxt-base-template/app/layouts/default.vue +1 -1
- package/nuxt-base-template/app/middleware/admin.global.ts +2 -2
- package/nuxt-base-template/app/middleware/auth.global.ts +2 -2
- package/nuxt-base-template/app/middleware/guest.global.ts +2 -2
- package/nuxt-base-template/app/pages/app/index.vue +1 -1
- package/nuxt-base-template/app/pages/app/settings/security.vue +54 -43
- package/nuxt-base-template/app/pages/auth/2fa.vue +2 -3
- package/nuxt-base-template/app/pages/auth/forgot-password.vue +2 -1
- package/nuxt-base-template/app/pages/auth/login.vue +6 -4
- package/nuxt-base-template/app/pages/auth/register.vue +85 -61
- package/nuxt-base-template/app/pages/auth/reset-password.vue +2 -1
- package/nuxt-base-template/docs/pages/docs.vue +1 -1
- package/nuxt-base-template/nuxt.config.ts +50 -1
- package/nuxt-base-template/package-lock.json +1311 -2920
- package/nuxt-base-template/package.json +27 -2
- 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/app/components/Transition/TransitionFade.vue +0 -27
- package/nuxt-base-template/app/components/Transition/TransitionFadeScale.vue +0 -27
- package/nuxt-base-template/app/components/Transition/TransitionSlide.vue +0 -12
- package/nuxt-base-template/app/components/Transition/TransitionSlideBottom.vue +0 -12
- package/nuxt-base-template/app/components/Transition/TransitionSlideRevert.vue +0 -12
- package/nuxt-base-template/app/composables/use-better-auth.ts +0 -597
- package/nuxt-base-template/app/composables/use-file.ts +0 -71
- package/nuxt-base-template/app/composables/use-share.ts +0 -38
- package/nuxt-base-template/app/composables/use-tus-upload.ts +0 -278
- package/nuxt-base-template/app/composables/use-tw.ts +0 -1
- package/nuxt-base-template/app/interfaces/upload.interface.ts +0 -58
- package/nuxt-base-template/app/lib/auth-client.ts +0 -229
- package/nuxt-base-template/app/lib/auth-state.ts +0 -206
- package/nuxt-base-template/app/plugins/auth-interceptor.client.ts +0 -151
- package/nuxt-base-template/app/utils/crypto.ts +0 -44
- package/nuxt-base-template/tests/iam.spec.ts +0 -247
- /package/nuxt-base-template/tests/{init.spec.ts → e2e/init.spec.ts} +0 -0
|
@@ -2,16 +2,13 @@
|
|
|
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
|
// ============================================================================
|
|
11
8
|
// Composables
|
|
12
9
|
// ============================================================================
|
|
13
10
|
const toast = useToast();
|
|
14
|
-
const { signUp, signIn, registerPasskey } =
|
|
11
|
+
const { signUp, signIn, registerPasskey } = useLtAuth();
|
|
15
12
|
|
|
16
13
|
// ============================================================================
|
|
17
14
|
// Page Meta
|
|
@@ -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>
|
|
@@ -7,7 +7,8 @@ import type { InferOutput } from 'valibot';
|
|
|
7
7
|
|
|
8
8
|
import * as v from 'valibot';
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
// Auth client from @lenne.tech/nuxt-extensions (auto-imported as ltAuthClient)
|
|
11
|
+
const authClient = ltAuthClient;
|
|
11
12
|
|
|
12
13
|
// ============================================================================
|
|
13
14
|
// Composables
|
|
@@ -182,7 +182,7 @@ async function handleFormSubmit(): Promise<void> {
|
|
|
182
182
|
}
|
|
183
183
|
|
|
184
184
|
async function handleShare(): Promise<void> {
|
|
185
|
-
const { share } =
|
|
185
|
+
const { share } = useLtShare();
|
|
186
186
|
await share('Nuxt Base Starter', 'Check out this Nuxt Base Starter Template!', window.location.href);
|
|
187
187
|
|
|
188
188
|
toast.add({
|
|
@@ -67,12 +67,40 @@ export default defineNuxtConfig({
|
|
|
67
67
|
dirs: ['./states', './stores', './forms', './interfaces', './base', './plugins'],
|
|
68
68
|
},
|
|
69
69
|
|
|
70
|
+
// ============================================================================
|
|
71
|
+
// lenne.tech Nuxt Extensions
|
|
72
|
+
// ============================================================================
|
|
73
|
+
ltExtensions: {
|
|
74
|
+
auth: {
|
|
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',
|
|
80
|
+
basePath: '/iam',
|
|
81
|
+
loginPath: '/auth/login',
|
|
82
|
+
twoFactorRedirectPath: '/auth/2fa',
|
|
83
|
+
enableAdmin: true,
|
|
84
|
+
enableTwoFactor: true,
|
|
85
|
+
enablePasskey: true,
|
|
86
|
+
interceptor: {
|
|
87
|
+
enabled: true,
|
|
88
|
+
publicPaths: ['/auth/login', '/auth/register', '/auth/forgot-password', '/auth/reset-password'],
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
tus: {
|
|
92
|
+
defaultEndpoint: '/files/upload',
|
|
93
|
+
defaultChunkSize: 5 * 1024 * 1024,
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
|
|
70
97
|
// ============================================================================
|
|
71
98
|
// Nuxt Modules
|
|
72
99
|
// ============================================================================
|
|
73
100
|
modules: [
|
|
101
|
+
'@lenne.tech/nuxt-extensions', // Auth, Upload, Transitions
|
|
74
102
|
'@nuxt/test-utils/module', // E2E testing with Playwright
|
|
75
|
-
'@lenne.tech/bug.lt', // Bug reporting to Linear
|
|
103
|
+
// '@lenne.tech/bug.lt', // Bug reporting to Linear - TEMPORARILY DISABLED FOR TESTING
|
|
76
104
|
'@vueuse/nuxt', // Vue composition utilities
|
|
77
105
|
'dayjs-nuxt', // Date/time handling
|
|
78
106
|
'@nuxt/image', // Image optimization
|
|
@@ -158,5 +186,26 @@ export default defineNuxtConfig({
|
|
|
158
186
|
exclude: ['@tailwindcss/vite', 'lightningcss', '@vue/devtools-core', '@vue/devtools-kit', '@internationalized/date'],
|
|
159
187
|
},
|
|
160
188
|
plugins: [tailwindcss()],
|
|
189
|
+
server: {
|
|
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
|
|
199
|
+
'/api': {
|
|
200
|
+
target: 'http://localhost:3000',
|
|
201
|
+
changeOrigin: true,
|
|
202
|
+
},
|
|
203
|
+
// IAM proxy for direct BetterAuth endpoints (SSR mode)
|
|
204
|
+
'/iam': {
|
|
205
|
+
target: 'http://localhost:3000',
|
|
206
|
+
changeOrigin: true,
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
},
|
|
161
210
|
},
|
|
162
211
|
});
|