@meistrari/auth-nuxt 0.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/README.md ADDED
@@ -0,0 +1,271 @@
1
+ # @meistrari/auth-sdk
2
+
3
+ A Nuxt module that provides authentication and organization management capabilities using Better Auth.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @meistrari/auth-sdk
9
+ ```
10
+
11
+ ## Setup
12
+
13
+ Add the module to your `nuxt.config.ts`:
14
+
15
+ ```typescript
16
+ export default defineNuxtConfig({
17
+ modules: ['@meistrari/auth-sdk'],
18
+ authSdk: {
19
+ apiUrl: 'https://your-auth-api.com',
20
+ useJwt: true, // Enable JWT token refresh cycle
21
+ jwtCookieName: 'auth-jwt', // Custom JWT cookie name
22
+ skipServerMiddleware: false, // Skip automatic server middleware
23
+ isDevelopment: false, // Development mode settings
24
+ }
25
+ })
26
+ ```
27
+
28
+ ### Configuration Options
29
+
30
+ | Option | Type | Default | Description |
31
+ |--------|------|---------|-------------|
32
+ | `apiUrl` | `string` | **Required** | Base URL of your authentication API |
33
+ | `useJwt` | `boolean` | `false` | Enable JWT cookie management and token refresh |
34
+ | `jwtCookieName` | `string` | `'auth-jwt'` | Name of the JWT cookie |
35
+ | `skipServerMiddleware` | `boolean` | `false` | Skip automatic server-side auth context setup |
36
+ | `isDevelopment` | `boolean` | `false` | Enable development-specific features |
37
+
38
+ ## Usage
39
+
40
+ ### Client-side Authentication
41
+
42
+ The SDK provides the `useMeistrariAuth` composable for managing authentication state:
43
+
44
+ ```vue
45
+ <script setup>
46
+ const { user, session, signOut } = useMeistrariAuth()
47
+
48
+ // Access current user
49
+ console.log(user.value) // User object or null
50
+
51
+ // Access current session
52
+ console.log(session.value) // Session object or null
53
+
54
+ // Sign out
55
+ await signOut(() => {
56
+ // Optional callback after sign out
57
+ console.log('User signed out')
58
+ })
59
+ </script>
60
+
61
+ <template>
62
+ <div v-if="user">
63
+ Welcome, {{ user.name }}!
64
+ <button @click="signOut">Sign Out</button>
65
+ </div>
66
+ <div v-else>
67
+ Please sign in
68
+ </div>
69
+ </template>
70
+ ```
71
+
72
+ ### Authentication Client
73
+
74
+ Access the underlying Better Auth client through the Nuxt app:
75
+
76
+ ```typescript
77
+ // In a plugin, middleware, or component
78
+ const { $auth } = useNuxtApp()
79
+
80
+ // Get current session
81
+ const sessionData = await $auth.client.getSession()
82
+
83
+ // Organization operations
84
+ await $auth.client.organization.create({
85
+ name: 'My Organization',
86
+ slug: 'my-org'
87
+ })
88
+
89
+ // Access custom endpoints
90
+ await $auth.client.customEndpoints.session.token()
91
+ ```
92
+
93
+ ### Server-side Usage
94
+
95
+ The SDK automatically sets up server-side authentication context for API routes:
96
+
97
+ ```typescript
98
+ // server/api/protected.ts
99
+ export default defineEventHandler(async (event) => {
100
+ // Authentication context is automatically available
101
+ const { user, session } = event.context.auth
102
+
103
+ if (!user) {
104
+ throw createError({
105
+ statusCode: 401,
106
+ statusMessage: 'Unauthorized'
107
+ })
108
+ }
109
+
110
+ return {
111
+ message: `Hello, ${user.name}!`,
112
+ userId: user.id
113
+ }
114
+ })
115
+ ```
116
+
117
+ ### Custom Middleware
118
+
119
+ You can create custom server middleware using the provided helper:
120
+
121
+ ```typescript
122
+ // server/middleware/custom.ts
123
+ import { meistrariAuthMiddleware } from '@meistrari/auth-sdk/server/middleware/auth'
124
+
125
+ export default meistrariAuthMiddleware(async (event) => {
126
+ // Your custom logic here
127
+ // event.context.auth contains user and session
128
+ })
129
+ ```
130
+
131
+ ### JWT Token Management
132
+
133
+ When `useJwt` is enabled, the SDK automatically:
134
+
135
+ - Manages JWT tokens in cookies
136
+ - Refreshes tokens before expiration (every 60 seconds)
137
+ - Validates token expiry client-side
138
+ - Provides `getToken()` method for accessing valid tokens
139
+
140
+ ```typescript
141
+ const { $auth } = useNuxtApp()
142
+
143
+ // Get a valid JWT token (refreshes if needed)
144
+ const token = await $auth.getToken()
145
+ ```
146
+
147
+ ## Types
148
+
149
+ The SDK exports comprehensive TypeScript types:
150
+
151
+ ```typescript
152
+ import type {
153
+ User,
154
+ Session,
155
+ Organization,
156
+ Workspace,
157
+ Member,
158
+ Invitation,
159
+ Team,
160
+ TeamMember
161
+ } from '@meistrari/auth-sdk/utils'
162
+ ```
163
+
164
+ ### Key Types
165
+
166
+ - **User**: User account information including email, name, and verification status
167
+ - **Session**: Active session data with expiration and device info
168
+ - **Organization**: Organization entity with basic metadata
169
+ - **Workspace**: Extended organization with settings and statistics
170
+ - **Member**: Organization member with role and team assignments
171
+ - **Team**: Team entity within an organization
172
+ - **Invitation**: Pending organization invitations
173
+
174
+ ## Organization & Team Management
175
+
176
+ The SDK includes full organization and team management capabilities:
177
+
178
+ ```typescript
179
+ const { $auth } = useNuxtApp()
180
+
181
+ // Create organization
182
+ await $auth.client.organization.create({
183
+ name: 'Acme Corp',
184
+ slug: 'acme-corp'
185
+ })
186
+
187
+ // Invite members
188
+ await $auth.client.organization.inviteMember({
189
+ email: 'user@example.com',
190
+ role: 'org:member',
191
+ organizationId: 'org-id'
192
+ })
193
+
194
+ // Create teams
195
+ await $auth.client.organization.createTeam({
196
+ name: 'Development Team',
197
+ organizationId: 'org-id'
198
+ })
199
+
200
+ // Manage member roles
201
+ await $auth.client.organization.updateMemberRole({
202
+ membershipId: 'member-id',
203
+ role: 'org:admin'
204
+ })
205
+ ```
206
+
207
+ ### Role-based Access Control
208
+
209
+ The SDK implements role-based access control with three built-in roles:
210
+
211
+ - **org:admin**: Full organization management permissions
212
+ - **org:reviewer**: Review-only access
213
+ - **org:member**: Basic member access
214
+
215
+ ```typescript
216
+ // Access control is automatically enforced in API calls
217
+ // Roles are validated server-side based on user permissions
218
+ ```
219
+
220
+ ## Utilities
221
+
222
+ ### Token Validation
223
+
224
+ ```typescript
225
+ import { isTokenExpired, validateToken } from '@meistrari/auth-sdk/utils'
226
+
227
+ // Check if token is expired
228
+ if (isTokenExpired(jwtToken)) {
229
+ // Token needs refresh
230
+ }
231
+
232
+ // Validate token against JWKS endpoint
233
+ const isValid = await validateToken(jwtToken, 'https://your-api.com')
234
+ ```
235
+
236
+ ## Error Handling
237
+
238
+ The SDK handles common authentication errors automatically:
239
+
240
+ - Expired tokens are refreshed automatically
241
+ - Invalid sessions redirect to sign-in
242
+ - Network errors are gracefully handled
243
+ - CSRF protection is built-in
244
+
245
+ ```typescript
246
+ try {
247
+ await $auth.client.getSession()
248
+ } catch (error) {
249
+ console.error('Authentication failed:', error.message)
250
+ }
251
+ ```
252
+
253
+ ## Development
254
+
255
+ Set `isDevelopment: true` in your config to enable:
256
+
257
+ - Additional debugging logs
258
+ - Development-specific cookie handling
259
+ - Relaxed security policies for local development
260
+
261
+ ## Security Features
262
+
263
+ - **CSRF Protection**: Built into all API calls
264
+ - **JWT Validation**: Cryptographic validation using JWKS
265
+ - **Secure Cookies**: HTTP-only, secure cookies in production
266
+ - **Token Refresh**: Automatic token rotation
267
+ - **Session Management**: Secure session handling
268
+
269
+ ## Migration
270
+
271
+ If upgrading from a previous version, ensure your configuration includes the required `apiUrl` parameter and update any deprecated options.
@@ -0,0 +1,16 @@
1
+ import * as _nuxt_schema from '@nuxt/schema';
2
+
3
+ interface ModuleOptions {
4
+ /** Auth API base URL */
5
+ apiUrl: string;
6
+ /** Set JWT cookie and start token refresh cycle */
7
+ useJwt: boolean;
8
+ /** Cookie name for authentication token */
9
+ jwtCookieName: string;
10
+ skipServerMiddleware: boolean;
11
+ isDevelopment: boolean;
12
+ }
13
+ declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
14
+
15
+ export { _default as default };
16
+ export type { ModuleOptions };
@@ -0,0 +1,9 @@
1
+ {
2
+ "name": "@meistrari/auth-sdk",
3
+ "configKey": "authSdk",
4
+ "version": "0.1.0",
5
+ "builder": {
6
+ "@nuxt/module-builder": "1.0.2",
7
+ "unbuild": "3.6.1"
8
+ }
9
+ }
@@ -0,0 +1,46 @@
1
+ import { defineNuxtModule, createResolver, addServerHandler, addImports, addPlugin } from '@nuxt/kit';
2
+
3
+ const module = defineNuxtModule({
4
+ meta: {
5
+ name: "@meistrari/auth-sdk",
6
+ configKey: "authSdk"
7
+ },
8
+ defaults: {
9
+ apiUrl: "",
10
+ useJwt: false,
11
+ jwtCookieName: "auth-jwt",
12
+ skipServerMiddleware: false,
13
+ isDevelopment: false
14
+ },
15
+ setup(options, nuxt) {
16
+ if (!options.apiUrl) {
17
+ console.error("[@meistrari/auth-sdk] error: apiUrl is required in module config");
18
+ }
19
+ const resolver = createResolver(import.meta.url);
20
+ nuxt.options.runtimeConfig.public.auth = options;
21
+ if (!options.skipServerMiddleware) {
22
+ addServerHandler({
23
+ route: "",
24
+ handler: resolver.resolve("./runtime/server/middleware/auth")
25
+ });
26
+ }
27
+ if (options.isDevelopment) {
28
+ addServerHandler({
29
+ route: "",
30
+ handler: resolver.resolve("./runtime/server/middleware/set-cookies")
31
+ });
32
+ addServerHandler({
33
+ route: "/api/meistrari-auth/sign-out",
34
+ handler: resolver.resolve("./runtime/server/api/sign-out")
35
+ });
36
+ }
37
+ addImports({
38
+ name: "useMeistrariAuth",
39
+ as: "useMeistrariAuth",
40
+ from: resolver.resolve("runtime/composable")
41
+ });
42
+ addPlugin(resolver.resolve("./runtime/plugin"));
43
+ }
44
+ });
45
+
46
+ export { module as default };
@@ -0,0 +1,5 @@
1
+ export declare function useMeistrariAuth(): {
2
+ user: import("vue").Ref<any, any>;
3
+ session: import("vue").Ref<any, any>;
4
+ signOut: (callback?: () => void) => Promise<void>;
5
+ };
@@ -0,0 +1,29 @@
1
+ import { useCookie, useState, useRuntimeConfig, useNuxtApp } from "#app";
2
+ export function useMeistrariAuth() {
3
+ const user = useState("user", () => null);
4
+ const session = useState("session", () => null);
5
+ const { useJwt, jwtCookieName, isDevelopment } = useRuntimeConfig().public.auth;
6
+ const { $auth } = useNuxtApp();
7
+ async function signOut(callback) {
8
+ await $auth.client.signOut({
9
+ fetchOptions: {
10
+ onSuccess: async () => {
11
+ if (useJwt) {
12
+ useCookie(jwtCookieName).value = null;
13
+ }
14
+ if (isDevelopment) {
15
+ await $fetch("/api/meistrari-auth/sign-out");
16
+ }
17
+ user.value = null;
18
+ session.value = null;
19
+ callback?.();
20
+ }
21
+ }
22
+ });
23
+ }
24
+ return {
25
+ user,
26
+ session,
27
+ signOut
28
+ };
29
+ }
@@ -0,0 +1,15 @@
1
+ import { createAuthClient } from '@meistrari/auth-core';
2
+ declare const _default: import("#app").Plugin<{
3
+ auth: {
4
+ client: ReturnType<typeof createAuthClient>;
5
+ getToken: () => Promise<string | null | undefined>;
6
+ refreshToken: () => Promise<string | null | undefined>;
7
+ };
8
+ }> & import("#app").ObjectPlugin<{
9
+ auth: {
10
+ client: ReturnType<typeof createAuthClient>;
11
+ getToken: () => Promise<string | null | undefined>;
12
+ refreshToken: () => Promise<string | null | undefined>;
13
+ };
14
+ }>;
15
+ export default _default;
@@ -0,0 +1,105 @@
1
+ import { defineNuxtPlugin, useCookie, useRequestHeaders, useRuntimeConfig } from "#app";
2
+ import { watch } from "vue";
3
+ import { createAuthClient, isTokenExpired } from "@meistrari/auth-core";
4
+ import { useMeistrariAuth } from "./composable.js";
5
+ export default defineNuxtPlugin({
6
+ name: "meistrari-auth",
7
+ parallel: true,
8
+ async setup() {
9
+ const config = useRuntimeConfig().public.auth;
10
+ const authClient = createAuthClient(config.apiUrl);
11
+ const { user, session } = useMeistrariAuth();
12
+ async function getToken() {
13
+ const token = useCookie(config.jwtCookieName);
14
+ if (token.value) {
15
+ if (!isTokenExpired(token.value)) {
16
+ return token.value;
17
+ }
18
+ }
19
+ try {
20
+ return await refreshToken();
21
+ } catch (error) {
22
+ throw new Error("Failed to get token: " + (error instanceof Error ? error.message : String(error)));
23
+ }
24
+ }
25
+ async function refreshToken() {
26
+ const token = useCookie(config.jwtCookieName);
27
+ const headers = useRequestHeaders();
28
+ const session2 = await authClient.getSession({
29
+ fetchOptions: {
30
+ // on the client side we include credentials
31
+ credentials: "include",
32
+ // on the server side we forward the cookies
33
+ headers,
34
+ onSuccess: (ctx) => {
35
+ const jwt = ctx.response.headers.get("set-auth-jwt");
36
+ if (jwt) {
37
+ token.value = jwt;
38
+ }
39
+ }
40
+ }
41
+ });
42
+ if (session2.error || !session2.data?.session) {
43
+ throw new Error("Failed to refresh token");
44
+ }
45
+ return token.value;
46
+ }
47
+ async function initializeSession() {
48
+ if (session.value && user.value)
49
+ return;
50
+ const token = useCookie(config.jwtCookieName);
51
+ const headers = import.meta.server ? useRequestHeaders() : void 0;
52
+ const { data: sessionData, error: sessionError } = await authClient.getSession(
53
+ {
54
+ fetchOptions: {
55
+ // on the client side we include credentials
56
+ credentials: "include",
57
+ // on the server side we forward the cookies
58
+ headers,
59
+ ...config.useJwt && {
60
+ onSuccess: (context) => {
61
+ const jwt = context.response.headers.get("Set-Auth-Jwt");
62
+ if (jwt) {
63
+ token.value = jwt;
64
+ }
65
+ }
66
+ }
67
+ }
68
+ }
69
+ );
70
+ if (sessionError || !sessionData) {
71
+ return;
72
+ }
73
+ user.value = sessionData.user;
74
+ session.value = sessionData.session;
75
+ }
76
+ await initializeSession();
77
+ if (import.meta.client) {
78
+ if (config.useJwt) {
79
+ let tokenRefreshInterval = null;
80
+ watch(session, async (newVal) => {
81
+ if (tokenRefreshInterval) {
82
+ clearTimeout(tokenRefreshInterval);
83
+ tokenRefreshInterval = null;
84
+ }
85
+ if (newVal) {
86
+ async function createTokenRefreshInterval() {
87
+ await refreshToken();
88
+ tokenRefreshInterval = window.setTimeout(createTokenRefreshInterval, 6e4);
89
+ }
90
+ createTokenRefreshInterval();
91
+ }
92
+ }, { immediate: true });
93
+ }
94
+ }
95
+ return {
96
+ provide: {
97
+ auth: {
98
+ client: authClient,
99
+ getToken,
100
+ refreshToken
101
+ }
102
+ }
103
+ };
104
+ }
105
+ });
@@ -0,0 +1,2 @@
1
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
2
+ export default _default;
@@ -0,0 +1,16 @@
1
+ import { defineEventHandler, deleteCookie, sendNoContent } from "h3";
2
+ export default defineEventHandler(async (event) => {
3
+ deleteCookie(event, "__Secure-__session", {
4
+ httpOnly: true,
5
+ secure: true,
6
+ sameSite: "lax",
7
+ path: "/"
8
+ });
9
+ deleteCookie(event, "__Secure-__session_data", {
10
+ httpOnly: true,
11
+ secure: true,
12
+ sameSite: "lax",
13
+ path: "/"
14
+ });
15
+ return sendNoContent(event);
16
+ });
@@ -0,0 +1,4 @@
1
+ import type { AuthenticatedH3Event } from '../types/h3.js';
2
+ export declare function meistrariAuthMiddleware(callback: (event: AuthenticatedH3Event) => void | Promise<void>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
3
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
4
+ export default _default;
@@ -0,0 +1,35 @@
1
+ import { defineEventHandler, getCookie } from "h3";
2
+ import { useRuntimeConfig } from "nitropack/runtime/config";
3
+ import { createAuthClient } from "@meistrari/auth-core";
4
+ async function setAuthContext(event) {
5
+ event.context.auth = {
6
+ session: null,
7
+ user: null
8
+ };
9
+ const sessionCookie = getCookie(event, "__Secure-__session");
10
+ if (!sessionCookie)
11
+ return;
12
+ const authConfig = useRuntimeConfig(event).public.auth;
13
+ const authClient = createAuthClient(authConfig.apiUrl);
14
+ const { data: sessionData, error: sessionError } = await authClient.getSession({
15
+ fetchOptions: {
16
+ headers: event.headers
17
+ }
18
+ });
19
+ if (sessionError || !sessionData) {
20
+ return;
21
+ }
22
+ event.context.auth.session = sessionData.session;
23
+ event.context.auth.user = sessionData.user;
24
+ }
25
+ export function meistrariAuthMiddleware(callback) {
26
+ return defineEventHandler(async (event) => {
27
+ await setAuthContext(event);
28
+ await callback(event);
29
+ });
30
+ }
31
+ export default defineEventHandler(async (event) => {
32
+ if (!event.path.startsWith("/api"))
33
+ return;
34
+ await setAuthContext(event);
35
+ });
@@ -0,0 +1,2 @@
1
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
2
+ export default _default;
@@ -0,0 +1,42 @@
1
+ import { defineEventHandler, getQuery, setCookie, sendRedirect, getRequestURL } from "h3";
2
+ export default defineEventHandler(async (event) => {
3
+ const query = getQuery(event);
4
+ const hasSessionParam = query.session && typeof query.session === "string";
5
+ const hasSessionDataParam = query.session_data && typeof query.session_data === "string";
6
+ if (hasSessionParam || hasSessionDataParam) {
7
+ if (hasSessionParam) {
8
+ setCookie(event, "__Secure-__session", query.session, {
9
+ httpOnly: true,
10
+ secure: true,
11
+ sameSite: "lax",
12
+ path: "/",
13
+ maxAge: 60 * 60 * 24 * 7
14
+ // 1 week in seconds
15
+ });
16
+ }
17
+ if (hasSessionDataParam) {
18
+ setCookie(event, "__Secure-__session_data", query.session_data, {
19
+ httpOnly: true,
20
+ secure: true,
21
+ sameSite: "lax",
22
+ path: "/",
23
+ maxAge: 60 * 60 * 24 * 7
24
+ // 1 week in seconds
25
+ });
26
+ }
27
+ const url = getRequestURL(event);
28
+ const newQuery = new URLSearchParams();
29
+ for (const [key, value] of Object.entries(query)) {
30
+ if (key !== "session" && key !== "session_data" && value !== void 0 && value !== null) {
31
+ if (Array.isArray(value)) {
32
+ value.forEach((v) => newQuery.append(key, String(v)));
33
+ } else {
34
+ newQuery.append(key, String(value));
35
+ }
36
+ }
37
+ }
38
+ const queryString = newQuery.toString();
39
+ const redirectUrl = `${url.pathname}${queryString ? `?${queryString}` : ""}`;
40
+ return sendRedirect(event, redirectUrl, 302);
41
+ }
42
+ });
@@ -0,0 +1,3 @@
1
+ {
2
+ "extends": "../../../.nuxt/tsconfig.server.json",
3
+ }
@@ -0,0 +1,22 @@
1
+ import type { User, Session } from '@meistrari/auth-core'
2
+ import type { H3Event, H3EventContext } from 'h3'
3
+
4
+ declare module 'h3' {
5
+ interface H3EventContext {
6
+ auth?: {
7
+ user: User | null
8
+ session: Session | null
9
+ }
10
+ }
11
+ }
12
+
13
+ export interface AuthenticatedH3EventContext extends H3EventContext {
14
+ auth: {
15
+ user: User | null
16
+ session: Session | null
17
+ }
18
+ }
19
+
20
+ export interface AuthenticatedH3Event extends H3Event {
21
+ context: AuthenticatedH3EventContext
22
+ }
@@ -0,0 +1,3 @@
1
+ export { default } from './module.mjs'
2
+
3
+ export { type ModuleOptions } from './module.mjs'
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@meistrari/auth-nuxt",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "exports": {
6
+ ".": {
7
+ "types": "./dist/types.d.mts",
8
+ "import": "./dist/module.mjs"
9
+ },
10
+ "./server/middleware/auth": {
11
+ "types": "./dist/runtime/server/middleware/auth.d.ts",
12
+ "import": "./dist/runtime/server/middleware/auth.js"
13
+ }
14
+ },
15
+ "main": "./dist/module.mjs",
16
+ "typesVersions": {
17
+ "*": {
18
+ ".": [
19
+ "./dist/types.d.mts"
20
+ ]
21
+ }
22
+ },
23
+ "files": [
24
+ "dist"
25
+ ],
26
+ "scripts": {
27
+ "build": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxt-module-build build"
28
+ },
29
+ "dependencies": {
30
+ "@meistrari/auth-core": "0.1.1"
31
+ },
32
+ "peerDependencies": {
33
+ "nuxt": "^3.0.0 || ^4.0.0"
34
+ },
35
+ "devDependencies": {
36
+ "@nuxt/devtools": "2.6.3",
37
+ "@nuxt/eslint-config": "1.9.0",
38
+ "@nuxt/kit": "4.0.3",
39
+ "@nuxt/module-builder": "1.0.2",
40
+ "@nuxt/schema": "4.0.3",
41
+ "@nuxt/test-utils": "3.19.2",
42
+ "@types/node": "latest",
43
+ "changelogen": "0.6.2",
44
+ "eslint": "9.34.0",
45
+ "nuxt": "4.0.3",
46
+ "typescript": "5.9.2",
47
+ "unbuild": "3.6.1",
48
+ "vitest": "3.2.4",
49
+ "vue-tsc": "3.0.6"
50
+ }
51
+ }