@xenterprises/nuxt-x-auth-better 0.1.1

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/LICENSE ADDED
@@ -0,0 +1,60 @@
1
+ PROPRIETARY SOFTWARE LICENSE
2
+
3
+ Copyright (c) 2024-2026 X Enterprises LLC. All Rights Reserved.
4
+
5
+ This software and associated documentation files (the "Software") are the
6
+ exclusive property of X Enterprises LLC, a Washington limited liability
7
+ company.
8
+
9
+ TERMS AND CONDITIONS
10
+
11
+ 1. OWNERSHIP
12
+ All rights, title, and interest in and to the Software, including all
13
+ intellectual property rights, are and shall remain the exclusive property
14
+ of X Enterprises LLC.
15
+
16
+ 2. RESTRICTIONS
17
+ Without the prior written consent of X Enterprises LLC, you may not:
18
+ - Copy, modify, or distribute the Software
19
+ - Reverse engineer, decompile, or disassemble the Software
20
+ - Sublicense, sell, lease, or otherwise transfer the Software
21
+ - Remove or alter any proprietary notices or labels
22
+
23
+ 3. AUTHORIZED USE
24
+ Use of this Software is limited to authorized employees, contractors, and
25
+ agents of X Enterprises LLC, solely for purposes approved by X Enterprises
26
+ LLC.
27
+
28
+ 4. NO WARRANTY
29
+ THE SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
30
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
31
+ FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL
32
+ X ENTERPRISES LLC BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY,
33
+ WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF,
34
+ OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
35
+ SOFTWARE.
36
+
37
+ 5. LIMITATION OF LIABILITY
38
+ IN NO EVENT SHALL X ENTERPRISES LLC BE LIABLE FOR ANY INDIRECT, INCIDENTAL,
39
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
40
+ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
41
+ OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
42
+ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
43
+ OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
44
+ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
45
+
46
+ 6. GOVERNING LAW
47
+ This license shall be governed by and construed in accordance with the laws
48
+ of the State of Washington, United States, without regard to its conflict
49
+ of law provisions.
50
+
51
+ 7. TERMINATION
52
+ This license is effective until terminated. X Enterprises LLC may terminate
53
+ this license at any time without notice. Upon termination, you must destroy
54
+ all copies of the Software in your possession.
55
+
56
+ For licensing inquiries, contact: legal@x.enterprises
57
+
58
+ ---
59
+ X Enterprises LLC
60
+ Bothell, Washington, United States
package/README.md ADDED
@@ -0,0 +1,199 @@
1
+ # @xenterprises/nuxt-x-auth-better
2
+
3
+ BetterAuth authentication layer for Nuxt 4.
4
+
5
+ ## Features
6
+
7
+ - **Better Auth integration** - Industry-standard authentication with Better Auth
8
+ - **OAuth providers** - Google, GitHub, and more
9
+ - **Magic link authentication** - Passwordless login via email
10
+ - **Secure token storage** - HttpOnly cookies with SameSite protection
11
+ - **Field normalization** - Unified user object across providers
12
+ - **Pre-built UI components** - Login, Signup, Forgot Password, Magic Link, OAuth
13
+ - **Route protection** - Global middleware for authenticated routes
14
+ - **Responsive design** - Built with Nuxt UI v4
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ npm install @xenterprises/nuxt-x-auth-better better-auth
20
+ ```
21
+
22
+ ## Setup
23
+
24
+ ### 1. Extend the layer
25
+
26
+ ```ts
27
+ // nuxt.config.ts
28
+ export default defineNuxtConfig({
29
+ extends: ['@xenterprises/nuxt-x-auth-better']
30
+ })
31
+ ```
32
+
33
+ ### 2. Configure your provider
34
+
35
+ ```ts
36
+ // app.config.ts
37
+ export default defineAppConfig({
38
+ xAuth: {
39
+ tokens: {
40
+ accessCookie: 'x_auth_access',
41
+ refreshCookie: 'x_auth_refresh',
42
+ hasRefresh: true,
43
+ },
44
+ redirects: {
45
+ login: '/auth/login',
46
+ signup: '/auth/signup',
47
+ afterLogin: '/dashboard',
48
+ afterSignup: '/dashboard',
49
+ afterLogout: '/auth/login',
50
+ forgotPassword: '/auth/forgot-password',
51
+ },
52
+ features: {
53
+ oauth: true,
54
+ magicLink: true,
55
+ forgotPassword: true,
56
+ signup: true,
57
+ },
58
+ oauthProviders: ['google', 'github'],
59
+ ui: {
60
+ showLogo: true,
61
+ logoUrl: '/logo.svg',
62
+ brandName: 'My App',
63
+ },
64
+ },
65
+ })
66
+ ```
67
+
68
+ ### 3. Set environment variables
69
+
70
+ ```env
71
+ NUXT_PUBLIC_BETTER_AUTH_BASE_URL=https://api.example.com
72
+ ```
73
+
74
+ ## Usage
75
+
76
+ ### Composable
77
+
78
+ ```vue
79
+ <script setup>
80
+ const {
81
+ user,
82
+ login,
83
+ logout,
84
+ signup,
85
+ loginWithOAuth,
86
+ sendMagicLink,
87
+ forgotPassword,
88
+ isLoading,
89
+ isAuthenticated
90
+ } = useXAuth()
91
+
92
+ // Email/password login
93
+ await login('email@example.com', 'password')
94
+
95
+ // OAuth login
96
+ await loginWithOAuth('google')
97
+
98
+ // Magic link
99
+ await sendMagicLink('email@example.com')
100
+
101
+ // Signup
102
+ await signup('email@example.com', 'password', { name: 'John' })
103
+
104
+ // Password reset
105
+ await forgotPassword('email@example.com')
106
+
107
+ // Logout
108
+ await logout()
109
+ </script>
110
+ ```
111
+
112
+ ### Normalized User Object
113
+
114
+ Better Auth returns a normalized user object:
115
+
116
+ ```ts
117
+ {
118
+ id: 'user-123',
119
+ email: 'user@example.com',
120
+ name: 'John Doe',
121
+ avatar: 'https://example.com/avatar.jpg',
122
+ emailVerified: true,
123
+ metadata: { /* provider-specific data */ }
124
+ }
125
+ ```
126
+
127
+ ### Protected Routes
128
+
129
+ Routes are automatically protected by the global middleware.
130
+
131
+ **Route types:**
132
+ - **Guest-only routes** - `/auth/login`, `/auth/signup`, `/auth/forgot-password`, `/auth/magic-link` (redirects logged-in users)
133
+ - **Public routes** - `/auth/handler/*`, `/auth/logout` (accessible by anyone)
134
+ - **Protected routes** - Everything else (requires authentication)
135
+
136
+ ## Components
137
+
138
+ | Component | Description |
139
+ |-----------|-------------|
140
+ | `XAuthLogin` | Email/password login form |
141
+ | `XAuthSignup` | Registration form |
142
+ | `XAuthForgotPassword` | Password reset request |
143
+ | `XAuthMagicLink` | Magic link authentication |
144
+ | `XAuthOAuthButton` | Single OAuth provider button |
145
+ | `XAuthOAuthButtonGroup` | All configured OAuth buttons |
146
+ | `XAuthHandler` | OAuth callback handler |
147
+
148
+ ## Pages (Auto-registered)
149
+
150
+ - `/auth/login` - Login page
151
+ - `/auth/signup` - Registration page
152
+ - `/auth/forgot-password` - Password reset
153
+ - `/auth/magic-link` - Magic link authentication
154
+ - `/auth/logout` - Logout handler
155
+ - `/auth/handler/*` - OAuth callback handlers
156
+
157
+ ## OAuth Providers
158
+
159
+ Configure which OAuth providers to show:
160
+
161
+ ```ts
162
+ // app.config.ts
163
+ export default defineAppConfig({
164
+ xAuth: {
165
+ oauthProviders: ['google', 'github', 'microsoft', 'apple'],
166
+ },
167
+ })
168
+ ```
169
+
170
+ Available providers depend on your Better Auth backend configuration.
171
+
172
+ ## Token Storage
173
+
174
+ Tokens are stored in secure cookies:
175
+ - `HttpOnly` - Not accessible via JavaScript
176
+ - `Secure` - Only sent over HTTPS in production
177
+ - `SameSite=Lax` - CSRF protection
178
+
179
+ Cookie names are configurable via `tokens.accessCookie` and `tokens.refreshCookie`.
180
+
181
+ ## Testing
182
+
183
+ ```bash
184
+ # Unit tests
185
+ npm run test
186
+
187
+ # E2E tests
188
+ npx playwright test
189
+ ```
190
+
191
+ ## Requirements
192
+
193
+ - Nuxt 4+
194
+ - Better Auth 1.0+
195
+ - @nuxt/ui v4+ (included)
196
+
197
+ ## License
198
+
199
+ MIT
@@ -0,0 +1,66 @@
1
+ export default defineAppConfig({
2
+ xAuth: {
3
+ tokens: {
4
+ accessCookie: "x_auth_access",
5
+ refreshCookie: "x_auth_refresh",
6
+ hasRefresh: true,
7
+ },
8
+
9
+ redirects: {
10
+ login: "/auth/login",
11
+ signup: "/auth/signup",
12
+ afterLogin: "/",
13
+ afterSignup: "/",
14
+ afterLogout: "/auth/login",
15
+ forgotPassword: "/auth/forgot-password",
16
+ },
17
+
18
+ features: {
19
+ oauth: false,
20
+ magicLink: false,
21
+ otp: false,
22
+ forgotPassword: true,
23
+ signup: true,
24
+ },
25
+
26
+ oauthProviders: [] as Array<{
27
+ id: string;
28
+ label: string;
29
+ icon: string;
30
+ }>,
31
+
32
+ ui: {
33
+ showLogo: true,
34
+ showBrandName: true,
35
+ logoUrl: "",
36
+ brandName: "",
37
+ tagline: "",
38
+ layout: "centered" as "centered" | "split",
39
+ background: {
40
+ type: "gradient" as "gradient" | "image" | "solid",
41
+ imageUrl: "",
42
+ overlayOpacity: 50,
43
+ },
44
+ card: {
45
+ glass: false,
46
+ glassIntensity: "medium" as "subtle" | "medium" | "strong",
47
+ logoUrl: "",
48
+ },
49
+ legal: {
50
+ copyright: "",
51
+ links: [] as Array<{ label: string; to: string }>,
52
+ },
53
+ split: {
54
+ heroPosition: "left" as "left" | "right",
55
+ heroImageUrl: "",
56
+ headline: "",
57
+ subheadline: "",
58
+ features: [] as string[],
59
+ },
60
+ form: {
61
+ icon: "",
62
+ showSeparator: true,
63
+ },
64
+ },
65
+ },
66
+ });
@@ -0,0 +1,118 @@
1
+ @import "tailwindcss";
2
+ @import "@nuxt/ui";
3
+
4
+ :focus-visible {
5
+ outline: 2px solid rgb(var(--color-primary-500));
6
+ outline-offset: 2px;
7
+ }
8
+
9
+ .password-strength {
10
+ display: flex;
11
+ gap: 0.25rem;
12
+ margin-top: 0.5rem;
13
+ }
14
+
15
+ .password-strength__bar {
16
+ flex: 1;
17
+ height: 3px;
18
+ border-radius: 9999px;
19
+ background: rgb(var(--color-neutral-200));
20
+ transition: background 0.2s ease;
21
+ }
22
+
23
+ .dark .password-strength__bar {
24
+ background: rgb(var(--color-neutral-800));
25
+ }
26
+
27
+ .password-strength[data-strength="weak"] .password-strength__bar:nth-child(1) {
28
+ background: rgb(var(--color-error-500));
29
+ }
30
+
31
+ .password-strength[data-strength="medium"] .password-strength__bar:nth-child(1),
32
+ .password-strength[data-strength="medium"] .password-strength__bar:nth-child(2) {
33
+ background: rgb(var(--color-warning-500));
34
+ }
35
+
36
+ .password-strength[data-strength="strong"] .password-strength__bar {
37
+ background: rgb(var(--color-success-500));
38
+ }
39
+
40
+ @keyframes skeleton-pulse {
41
+ 0%, 100% {
42
+ opacity: 1;
43
+ }
44
+ 50% {
45
+ opacity: 0.5;
46
+ }
47
+ }
48
+
49
+ .skeleton {
50
+ animation: skeleton-pulse 1.5s ease-in-out infinite;
51
+ background: rgb(var(--color-neutral-200));
52
+ border-radius: 0.375rem;
53
+ }
54
+
55
+ .dark .skeleton {
56
+ background: rgb(var(--color-neutral-800));
57
+ }
58
+
59
+ @keyframes shake {
60
+ 0%, 100% { transform: translateX(0); }
61
+ 10%, 30%, 50%, 70%, 90% { transform: translateX(-4px); }
62
+ 20%, 40%, 60%, 80% { transform: translateX(4px); }
63
+ }
64
+
65
+ .auth-error {
66
+ animation: shake 0.5s ease-in-out;
67
+ }
68
+
69
+ @keyframes checkmark {
70
+ 0% {
71
+ stroke-dashoffset: 100;
72
+ }
73
+ 100% {
74
+ stroke-dashoffset: 0;
75
+ }
76
+ }
77
+
78
+ .auth-success-check {
79
+ stroke-dasharray: 100;
80
+ stroke-dashoffset: 100;
81
+ animation: checkmark 0.5s ease-out forwards;
82
+ }
83
+
84
+ .page-enter-active,
85
+ .page-leave-active {
86
+ transition: opacity 0.3s ease, transform 0.3s ease;
87
+ }
88
+
89
+ .page-enter-from {
90
+ opacity: 0;
91
+ transform: translateY(10px);
92
+ }
93
+
94
+ .page-leave-to {
95
+ opacity: 0;
96
+ transform: translateY(-10px);
97
+ }
98
+
99
+ @media (prefers-reduced-motion: reduce) {
100
+ *,
101
+ *::before,
102
+ *::after {
103
+ animation-duration: 0.01ms !important;
104
+ animation-iteration-count: 1 !important;
105
+ transition-duration: 0.01ms !important;
106
+ }
107
+ }
108
+
109
+ @media print {
110
+ .auth-layout {
111
+ background: white !important;
112
+ }
113
+
114
+ .auth-bg,
115
+ .auth-hero {
116
+ display: none !important;
117
+ }
118
+ }
@@ -0,0 +1,91 @@
1
+ <template>
2
+ <div>
3
+ <Transition
4
+ enter-active-class="transition-all duration-300 ease-out"
5
+ enter-from-class="opacity-0 translate-y-2"
6
+ enter-to-class="opacity-100 translate-y-0"
7
+ leave-active-class="transition-all duration-200 ease-in"
8
+ leave-from-class="opacity-100 translate-y-0"
9
+ leave-to-class="opacity-0 -translate-y-2"
10
+ mode="out-in"
11
+ >
12
+ <UAuthForm
13
+ v-if="!emailSent"
14
+ key="form"
15
+ title="Reset your password"
16
+ description="We'll send you a link to reset it"
17
+ :fields="fields"
18
+ :schema="schema"
19
+ :loading="isLoading"
20
+ :submit="{ label: 'Send reset link' }"
21
+ @submit="sendResetEmail"
22
+ >
23
+ <template #footer>
24
+ <ULink
25
+ :to="config.xAuth?.redirects?.login || '/auth/login'"
26
+ class="inline-flex items-center gap-1.5 text-sm text-muted hover:text-highlighted transition-colors"
27
+ >
28
+ <UIcon name="i-lucide-arrow-left" class="size-4" />
29
+ Back to sign in
30
+ </ULink>
31
+ </template>
32
+ </UAuthForm>
33
+
34
+ <article v-else key="success" class="text-center space-y-6">
35
+ <figure class="mx-auto flex size-16 items-center justify-center rounded-full bg-success/10 ring-1 ring-success/20">
36
+ <UIcon name="i-lucide-check" class="size-8 text-success" />
37
+ </figure>
38
+
39
+ <header class="space-y-2">
40
+ <h3 class="text-xl font-semibold tracking-tight text-highlighted">
41
+ Check your email
42
+ </h3>
43
+ <p class="text-sm text-muted">
44
+ If an account exists for <strong class="text-highlighted">{{ sentEmail }}</strong>,
45
+ we've sent password reset instructions.
46
+ </p>
47
+ </header>
48
+
49
+ <UButton
50
+ label="Return to sign in"
51
+ block
52
+ size="lg"
53
+ @click="goToLogin"
54
+ />
55
+ </article>
56
+ </Transition>
57
+ </div>
58
+ </template>
59
+
60
+ <script setup>
61
+ import { z } from "zod"
62
+
63
+ const config = useAppConfig()
64
+ const router = useRouter()
65
+ const { forgotPassword, isLoading, emailSent } = useXAuth()
66
+
67
+ const sentEmail = ref("")
68
+
69
+ const fields = [
70
+ {
71
+ name: "email",
72
+ type: "email",
73
+ label: "Email",
74
+ placeholder: "you@example.com",
75
+ required: true,
76
+ },
77
+ ]
78
+
79
+ const schema = z.object({
80
+ email: z.string().email("Please enter a valid email address"),
81
+ })
82
+
83
+ async function sendResetEmail(payload) {
84
+ sentEmail.value = payload.data.email
85
+ await forgotPassword(payload.data.email)
86
+ }
87
+
88
+ function goToLogin() {
89
+ router.push(config.xAuth?.redirects?.login || "/auth/login")
90
+ }
91
+ </script>