create-nuxt-base 0.3.17 → 1.0.3
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/.github/workflows/publish.yml +4 -2
- package/.oxfmtrc.jsonc +7 -0
- package/CHANGELOG.md +22 -8
- package/README.md +130 -3
- package/nuxt-base-template/.dockerignore +44 -0
- package/nuxt-base-template/.env.example +0 -2
- package/nuxt-base-template/.nuxtrc +1 -0
- package/nuxt-base-template/.oxfmtrc.jsonc +8 -0
- package/nuxt-base-template/Dockerfile.dev +23 -0
- package/nuxt-base-template/README.md +76 -29
- package/nuxt-base-template/app/components/Modal/ModalBackupCodes.vue +117 -0
- package/nuxt-base-template/app/components/Upload/TusFileUpload.vue +302 -0
- package/nuxt-base-template/app/composables/use-better-auth.ts +25 -0
- package/nuxt-base-template/app/composables/use-file.ts +39 -4
- package/nuxt-base-template/app/composables/use-share.ts +1 -1
- package/nuxt-base-template/app/composables/use-tus-upload.ts +278 -0
- package/nuxt-base-template/app/interfaces/upload.interface.ts +58 -0
- package/nuxt-base-template/app/interfaces/user.interface.ts +12 -0
- package/nuxt-base-template/app/lib/auth-client.ts +135 -0
- package/nuxt-base-template/app/middleware/admin.global.ts +23 -0
- package/nuxt-base-template/app/middleware/auth.global.ts +18 -0
- package/nuxt-base-template/app/middleware/guest.global.ts +18 -0
- package/nuxt-base-template/app/pages/app/settings/security.vue +409 -0
- package/nuxt-base-template/app/pages/auth/2fa.vue +120 -0
- package/nuxt-base-template/app/pages/auth/forgot-password.vue +72 -21
- package/nuxt-base-template/app/pages/auth/login.vue +75 -11
- package/nuxt-base-template/app/pages/auth/register.vue +184 -0
- package/nuxt-base-template/app/pages/auth/reset-password.vue +153 -0
- package/nuxt-base-template/app/utils/crypto.ts +13 -0
- package/nuxt-base-template/docker-entrypoint.sh +21 -0
- package/nuxt-base-template/nuxt.config.ts +4 -1
- package/nuxt-base-template/oxlint.json +14 -0
- package/nuxt-base-template/package-lock.json +11582 -10675
- package/nuxt-base-template/package.json +35 -32
- package/nuxt-base-template/tests/iam.spec.ts +247 -0
- package/package.json +14 -11
- package/.eslintignore +0 -14
- package/.eslintrc +0 -3
- package/.prettierignore +0 -5
- package/.prettierrc +0 -6
- package/nuxt-base-template/CLAUDE.md +0 -361
- package/nuxt-base-template/app/pages/auth/reset-password/[token].vue +0 -110
- package/nuxt-base-template/app/public/favicon.ico +0 -0
- package/nuxt-base-template/eslint.config.mjs +0 -4
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// ============================================================================
|
|
3
|
+
// Imports
|
|
4
|
+
// ============================================================================
|
|
5
|
+
import type { AuthFormField, FormSubmitEvent } from '@nuxt/ui';
|
|
6
|
+
import type { InferOutput } from 'valibot';
|
|
7
|
+
|
|
8
|
+
import * as v from 'valibot';
|
|
9
|
+
|
|
10
|
+
import { authClient } from '~/lib/auth-client';
|
|
11
|
+
|
|
12
|
+
// ============================================================================
|
|
13
|
+
// Composables
|
|
14
|
+
// ============================================================================
|
|
15
|
+
const route = useRoute();
|
|
16
|
+
const toast = useToast();
|
|
17
|
+
|
|
18
|
+
// ============================================================================
|
|
19
|
+
// Page Meta
|
|
20
|
+
// ============================================================================
|
|
21
|
+
definePageMeta({
|
|
22
|
+
layout: 'slim',
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// ============================================================================
|
|
26
|
+
// Variables
|
|
27
|
+
// ============================================================================
|
|
28
|
+
const token = computed<string>(() => {
|
|
29
|
+
const queryToken = route.query.token;
|
|
30
|
+
return typeof queryToken === 'string' ? queryToken : '';
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const isTokenValid = computed<boolean>(() => token.value.length > 0);
|
|
34
|
+
const loading = ref<boolean>(false);
|
|
35
|
+
const resetSuccess = ref<boolean>(false);
|
|
36
|
+
|
|
37
|
+
const fields: AuthFormField[] = [
|
|
38
|
+
{
|
|
39
|
+
label: 'Neues Passwort',
|
|
40
|
+
name: 'password',
|
|
41
|
+
placeholder: 'Neues Passwort eingeben',
|
|
42
|
+
required: true,
|
|
43
|
+
type: 'password',
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
label: 'Passwort bestätigen',
|
|
47
|
+
name: 'confirmPassword',
|
|
48
|
+
placeholder: 'Passwort wiederholen',
|
|
49
|
+
required: true,
|
|
50
|
+
type: 'password',
|
|
51
|
+
},
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
const schema = v.pipe(
|
|
55
|
+
v.object({
|
|
56
|
+
confirmPassword: v.pipe(v.string('Passwortbestätigung ist erforderlich'), v.minLength(8, 'Mindestens 8 Zeichen erforderlich')),
|
|
57
|
+
password: v.pipe(v.string('Passwort ist erforderlich'), v.minLength(8, 'Mindestens 8 Zeichen erforderlich')),
|
|
58
|
+
}),
|
|
59
|
+
v.forward(
|
|
60
|
+
v.partialCheck([['password'], ['confirmPassword']], (input) => input.password === input.confirmPassword, 'Passwörter stimmen nicht überein'),
|
|
61
|
+
['confirmPassword'],
|
|
62
|
+
),
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
type Schema = InferOutput<typeof schema>;
|
|
66
|
+
|
|
67
|
+
// ============================================================================
|
|
68
|
+
// Lifecycle Hooks
|
|
69
|
+
// ============================================================================
|
|
70
|
+
onMounted(() => {
|
|
71
|
+
if (!isTokenValid.value) {
|
|
72
|
+
toast.add({
|
|
73
|
+
color: 'error',
|
|
74
|
+
description: 'Der Link zum Zurücksetzen des Passworts ist ungültig oder fehlt.',
|
|
75
|
+
title: 'Ungültiger Link',
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// ============================================================================
|
|
81
|
+
// Functions
|
|
82
|
+
// ============================================================================
|
|
83
|
+
async function onSubmit(payload: FormSubmitEvent<Schema>): Promise<void> {
|
|
84
|
+
loading.value = true;
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
const { error } = await authClient.resetPassword({
|
|
88
|
+
newPassword: payload.data.password,
|
|
89
|
+
token: token.value,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
if (error) {
|
|
93
|
+
toast.add({
|
|
94
|
+
color: 'error',
|
|
95
|
+
description: error.message || 'Passwort konnte nicht zurückgesetzt werden',
|
|
96
|
+
title: 'Fehler',
|
|
97
|
+
});
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
resetSuccess.value = true;
|
|
102
|
+
} finally {
|
|
103
|
+
loading.value = false;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
</script>
|
|
107
|
+
|
|
108
|
+
<template>
|
|
109
|
+
<UPageCard class="w-md" variant="naked">
|
|
110
|
+
<template v-if="resetSuccess">
|
|
111
|
+
<div class="flex flex-col items-center gap-6">
|
|
112
|
+
<UIcon name="i-lucide-check-circle" class="size-16 text-success" />
|
|
113
|
+
<div class="text-center">
|
|
114
|
+
<h2 class="text-xl font-semibold">Passwort zurückgesetzt</h2>
|
|
115
|
+
<p class="mt-2 text-sm text-muted">Dein Passwort wurde erfolgreich geändert. Du kannst dich jetzt mit deinem neuen Passwort anmelden.</p>
|
|
116
|
+
</div>
|
|
117
|
+
|
|
118
|
+
<UButton to="/auth/login" block> Zur Anmeldung </UButton>
|
|
119
|
+
</div>
|
|
120
|
+
</template>
|
|
121
|
+
|
|
122
|
+
<template v-else>
|
|
123
|
+
<UAlert
|
|
124
|
+
v-if="!isTokenValid"
|
|
125
|
+
color="error"
|
|
126
|
+
description="Der Link zum Zurücksetzen des Passworts ist ungültig oder fehlt. Bitte fordere einen neuen Link an."
|
|
127
|
+
icon="i-lucide-alert-triangle"
|
|
128
|
+
title="Ungültiger Link"
|
|
129
|
+
class="mb-4"
|
|
130
|
+
/>
|
|
131
|
+
|
|
132
|
+
<UAuthForm
|
|
133
|
+
:schema="schema"
|
|
134
|
+
title="Neues Passwort"
|
|
135
|
+
icon="i-lucide-shield-check"
|
|
136
|
+
:fields="fields"
|
|
137
|
+
:loading="loading"
|
|
138
|
+
:submit="{
|
|
139
|
+
label: 'Passwort speichern',
|
|
140
|
+
block: true,
|
|
141
|
+
disabled: !isTokenValid,
|
|
142
|
+
}"
|
|
143
|
+
@submit="onSubmit"
|
|
144
|
+
>
|
|
145
|
+
<template #footer>
|
|
146
|
+
<p class="text-center text-sm text-muted">
|
|
147
|
+
<ULink to="/auth/forgot-password" class="text-primary font-medium"> Neuen Link anfordern </ULink>
|
|
148
|
+
</p>
|
|
149
|
+
</template>
|
|
150
|
+
</UAuthForm>
|
|
151
|
+
</template>
|
|
152
|
+
</UPageCard>
|
|
153
|
+
</template>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hashes a string using SHA256
|
|
3
|
+
* Uses the Web Crypto API which is available in all modern browsers
|
|
4
|
+
*
|
|
5
|
+
* @param message - The string to hash
|
|
6
|
+
* @returns The SHA256 hash as a lowercase hex string (64 characters)
|
|
7
|
+
*/
|
|
8
|
+
export async function sha256(message: string): Promise<string> {
|
|
9
|
+
const msgBuffer = new TextEncoder().encode(message);
|
|
10
|
+
const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer);
|
|
11
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
12
|
+
return hashArray.map((b) => b.toString(16).padStart(2, '0')).join('');
|
|
13
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
set -e
|
|
3
|
+
|
|
4
|
+
# Check if node_modules exists and if package.json has changed
|
|
5
|
+
PACKAGE_HASH_FILE="/app/.package-hash"
|
|
6
|
+
CURRENT_HASH=$(md5sum /app/package.json /app/package-lock.json 2>/dev/null | md5sum | cut -d' ' -f1)
|
|
7
|
+
|
|
8
|
+
if [ ! -d "/app/node_modules" ] || [ ! -f "$PACKAGE_HASH_FILE" ] || [ "$(cat $PACKAGE_HASH_FILE 2>/dev/null)" != "$CURRENT_HASH" ]; then
|
|
9
|
+
echo "Installing dependencies..."
|
|
10
|
+
npm ci
|
|
11
|
+
echo "$CURRENT_HASH" > "$PACKAGE_HASH_FILE"
|
|
12
|
+
echo "Dependencies installed successfully."
|
|
13
|
+
else
|
|
14
|
+
echo "Dependencies are up to date."
|
|
15
|
+
fi
|
|
16
|
+
|
|
17
|
+
# Prepare Nuxt (generate types, etc.)
|
|
18
|
+
echo "Preparing Nuxt..."
|
|
19
|
+
npx nuxt prepare
|
|
20
|
+
|
|
21
|
+
exec "$@"
|
|
@@ -110,8 +110,11 @@ export default defineNuxtConfig({
|
|
|
110
110
|
// Runtime Configuration (Environment Variables)
|
|
111
111
|
// ============================================================================
|
|
112
112
|
runtimeConfig: {
|
|
113
|
+
// Server-only (for SSR requests)
|
|
114
|
+
apiUrl: process.env.API_URL || 'http://localhost:3000',
|
|
113
115
|
public: {
|
|
114
|
-
|
|
116
|
+
// Client-side (browser requests)
|
|
117
|
+
apiUrl: process.env.API_URL_BROWSER || process.env.API_URL || 'http://localhost:3000',
|
|
115
118
|
host: process.env.API_URL,
|
|
116
119
|
webPushKey: process.env.WEB_PUSH_KEY,
|
|
117
120
|
},
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
|
3
|
+
"plugins": ["typescript", "vue", "unicorn", "import"],
|
|
4
|
+
"env": {
|
|
5
|
+
"browser": true,
|
|
6
|
+
"node": true
|
|
7
|
+
},
|
|
8
|
+
"rules": {
|
|
9
|
+
"eqeqeq": "warn",
|
|
10
|
+
"no-console": "warn",
|
|
11
|
+
"no-unused-vars": "warn",
|
|
12
|
+
"@typescript-eslint/no-explicit-any": "warn"
|
|
13
|
+
}
|
|
14
|
+
}
|