nuxt-users 1.6.6 → 1.6.7

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 CHANGED
@@ -1,6 +1,38 @@
1
1
  # Changelog
2
2
 
3
3
 
4
+ ## v1.6.7
5
+
6
+ [compare changes](https://github.com/rrd108/nuxt-users/compare/v1.6.6...v1.6.7)
7
+
8
+ ### 🩹 Fixes
9
+
10
+ - Authentication middleware logic ([18a3bb5](https://github.com/rrd108/nuxt-users/commit/18a3bb5))
11
+ - Password validation and UI ([07b44b3](https://github.com/rrd108/nuxt-users/commit/07b44b3))
12
+ - Type errors ([c403645](https://github.com/rrd108/nuxt-users/commit/c403645))
13
+
14
+ ### 💅 Refactors
15
+
16
+ - API routes ([3c7603f](https://github.com/rrd108/nuxt-users/commit/3c7603f))
17
+ - Authentication components and logic ([4da544f](https://github.com/rrd108/nuxt-users/commit/4da544f))
18
+ - Token cleanup task and middleware ([d47a4a5](https://github.com/rrd108/nuxt-users/commit/d47a4a5))
19
+
20
+ ### 🏡 Chore
21
+
22
+ - **release:** V1.6.6 ([7b37694](https://github.com/rrd108/nuxt-users/commit/7b37694))
23
+
24
+ ### ✅ Tests
25
+
26
+ - Fix mock ([791dc94](https://github.com/rrd108/nuxt-users/commit/791dc94))
27
+ - Adds unit tests for auth API endpoints ([d844630](https://github.com/rrd108/nuxt-users/commit/d844630))
28
+ - Remove unused test ([3dc23ad](https://github.com/rrd108/nuxt-users/commit/3dc23ad))
29
+ - Fix response ([db3b03c](https://github.com/rrd108/nuxt-users/commit/db3b03c))
30
+ - Fix mysql field type ([2faf0c6](https://github.com/rrd108/nuxt-users/commit/2faf0c6))
31
+
32
+ ### ❤️ Contributors
33
+
34
+ - Rrd108 ([@rrd108](https://github.com/rrd108))
35
+
4
36
  ## v1.6.6
5
37
 
6
38
  [compare changes](https://github.com/rrd108/nuxt-users/compare/v1.6.5...v1.6.6)
package/dist/cli.mjs CHANGED
@@ -247,7 +247,7 @@ const main = defineCommand({
247
247
  meta: {
248
248
  name: "nuxt-users",
249
249
  description: "CLI for Nuxt Users Module - Manage users, migrations, and database operations",
250
- version: "1.6.5"
250
+ version: "1.6.6"
251
251
  },
252
252
  subCommands: {
253
253
  migrate,
package/dist/module.d.mts CHANGED
@@ -9,76 +9,7 @@ type DatabaseConfig = {
9
9
  password?: string;
10
10
  database?: string;
11
11
  };
12
- interface RuntimeModuleOptions {
13
- connector?: {
14
- name: DatabaseType;
15
- options: DatabaseConfig;
16
- };
17
- tables?: {
18
- migrations?: string;
19
- users?: string;
20
- personalAccessTokens?: string;
21
- passwordResetTokens?: string;
22
- };
23
- /**
24
- * Mailer configuration options for sending emails (e.g., password resets)
25
- * Uses nodemailer
26
- */
27
- mailer?: MailerOptions;
28
- /**
29
- * Base URL for password reset links
30
- * @default 'http://localhost:3000' // Example, will be set in module defaults
31
- */
32
- passwordResetBaseUrl?: string;
33
- /**
34
- * Skip database checks during module setup to prevent hanging
35
- * @default false
36
- */
37
- auth?: {
38
- /**
39
- * Whitelisted routes that do not require authentication
40
- * @default ['/login']
41
- * @example ['/login', '/register']
42
- */
43
- whitelist?: string[];
44
- };
45
- /**
46
- * Password validation configuration
47
- */
48
- passwordValidation?: {
49
- /**
50
- * Minimum password length
51
- * @default 8
52
- */
53
- minLength?: number;
54
- /**
55
- * Require uppercase letters
56
- * @default true
57
- */
58
- requireUppercase?: boolean;
59
- /**
60
- * Require lowercase letters
61
- * @default true
62
- */
63
- requireLowercase?: boolean;
64
- /**
65
- * Require numbers
66
- * @default true
67
- */
68
- requireNumbers?: boolean;
69
- /**
70
- * Require special characters
71
- * @default true
72
- */
73
- requireSpecialChars?: boolean;
74
- /**
75
- * Prevent common passwords
76
- * @default true
77
- */
78
- preventCommonPasswords?: boolean;
79
- };
80
- }
81
- interface ModuleOptions extends Omit<RuntimeModuleOptions, 'tables' | 'auth' | 'passwordValidation'> {
12
+ interface ModuleOptions {
82
13
  connector: {
83
14
  name: DatabaseType;
84
15
  options: DatabaseConfig;
@@ -89,8 +20,11 @@ interface ModuleOptions extends Omit<RuntimeModuleOptions, 'tables' | 'auth' | '
89
20
  personalAccessTokens: string;
90
21
  passwordResetTokens: string;
91
22
  };
23
+ mailer?: MailerOptions;
24
+ passwordResetBaseUrl?: string;
92
25
  auth: {
93
26
  whitelist: string[];
27
+ tokenExpiration: number;
94
28
  };
95
29
  passwordValidation: {
96
30
  minLength: number;
package/dist/module.d.ts CHANGED
@@ -9,76 +9,7 @@ type DatabaseConfig = {
9
9
  password?: string;
10
10
  database?: string;
11
11
  };
12
- interface RuntimeModuleOptions {
13
- connector?: {
14
- name: DatabaseType;
15
- options: DatabaseConfig;
16
- };
17
- tables?: {
18
- migrations?: string;
19
- users?: string;
20
- personalAccessTokens?: string;
21
- passwordResetTokens?: string;
22
- };
23
- /**
24
- * Mailer configuration options for sending emails (e.g., password resets)
25
- * Uses nodemailer
26
- */
27
- mailer?: MailerOptions;
28
- /**
29
- * Base URL for password reset links
30
- * @default 'http://localhost:3000' // Example, will be set in module defaults
31
- */
32
- passwordResetBaseUrl?: string;
33
- /**
34
- * Skip database checks during module setup to prevent hanging
35
- * @default false
36
- */
37
- auth?: {
38
- /**
39
- * Whitelisted routes that do not require authentication
40
- * @default ['/login']
41
- * @example ['/login', '/register']
42
- */
43
- whitelist?: string[];
44
- };
45
- /**
46
- * Password validation configuration
47
- */
48
- passwordValidation?: {
49
- /**
50
- * Minimum password length
51
- * @default 8
52
- */
53
- minLength?: number;
54
- /**
55
- * Require uppercase letters
56
- * @default true
57
- */
58
- requireUppercase?: boolean;
59
- /**
60
- * Require lowercase letters
61
- * @default true
62
- */
63
- requireLowercase?: boolean;
64
- /**
65
- * Require numbers
66
- * @default true
67
- */
68
- requireNumbers?: boolean;
69
- /**
70
- * Require special characters
71
- * @default true
72
- */
73
- requireSpecialChars?: boolean;
74
- /**
75
- * Prevent common passwords
76
- * @default true
77
- */
78
- preventCommonPasswords?: boolean;
79
- };
80
- }
81
- interface ModuleOptions extends Omit<RuntimeModuleOptions, 'tables' | 'auth' | 'passwordValidation'> {
12
+ interface ModuleOptions {
82
13
  connector: {
83
14
  name: DatabaseType;
84
15
  options: DatabaseConfig;
@@ -89,8 +20,11 @@ interface ModuleOptions extends Omit<RuntimeModuleOptions, 'tables' | 'auth' | '
89
20
  personalAccessTokens: string;
90
21
  passwordResetTokens: string;
91
22
  };
23
+ mailer?: MailerOptions;
24
+ passwordResetBaseUrl?: string;
92
25
  auth: {
93
26
  whitelist: string[];
27
+ tokenExpiration: number;
94
28
  };
95
29
  passwordValidation: {
96
30
  minLength: number;
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "nuxt-users",
3
3
  "configKey": "nuxtUsers",
4
- "version": "1.6.6",
4
+ "version": "1.6.7",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.1",
7
7
  "unbuild": "3.6.0"
package/dist/module.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { defineNuxtModule, createResolver, addPlugin, addImportsDir, addServerHandler, addRouteMiddleware, addComponent } from '@nuxt/kit';
1
+ import { defineNuxtModule, createResolver, addPlugin, addImportsDir, addRouteMiddleware, addServerHandler, addComponent } from '@nuxt/kit';
2
2
  import { defu } from 'defu';
3
3
 
4
4
  const defaultOptions = {
@@ -31,7 +31,9 @@ const defaultOptions = {
31
31
  },
32
32
  passwordResetBaseUrl: "http://localhost:3000",
33
33
  auth: {
34
- whitelist: ["/login"]
34
+ whitelist: ["/login"],
35
+ tokenExpiration: 24 * 60
36
+ // 24 hours
35
37
  },
36
38
  passwordValidation: {
37
39
  minLength: 8,
@@ -61,7 +63,8 @@ const module = defineNuxtModule({
61
63
  passwordResetTokens: options.tables?.passwordResetTokens || defaultOptions.tables.passwordResetTokens
62
64
  },
63
65
  auth: {
64
- whitelist: [...defaultOptions.auth?.whitelist || [], ...options.auth?.whitelist || []]
66
+ whitelist: [...defaultOptions.auth?.whitelist || [], ...options.auth?.whitelist || []],
67
+ tokenExpiration: options.auth?.tokenExpiration || defaultOptions.auth.tokenExpiration
65
68
  }
66
69
  };
67
70
  nuxt.options.runtimeConfig.public = nuxt.options.runtimeConfig.public || {};
@@ -83,10 +86,6 @@ const module = defineNuxtModule({
83
86
  mode: "server"
84
87
  });
85
88
  addImportsDir(resolver.resolve("./runtime/composables"));
86
- addServerHandler({
87
- middleware: true,
88
- handler: resolver.resolve("./runtime/server/middleware/auth.server")
89
- });
90
89
  addRouteMiddleware({
91
90
  name: "auth.client",
92
91
  path: resolver.resolve("./runtime/middleware/auth.client"),
@@ -122,6 +121,12 @@ const module = defineNuxtModule({
122
121
  method: "post",
123
122
  handler: resolver.resolve("./runtime/server/api/auth/update-password.post")
124
123
  });
124
+ nuxt.hook("nitro:config", async (nitroConfig) => {
125
+ nitroConfig.experimental = nitroConfig.experimental || {};
126
+ nitroConfig.experimental.tasks = true;
127
+ nitroConfig.scanDirs = nitroConfig.scanDirs || [];
128
+ nitroConfig.scanDirs.push(resolver.resolve("./runtime/server/tasks"));
129
+ });
125
130
  addComponent({
126
131
  name: "LoginForm",
127
132
  filePath: resolver.resolve("./runtime/components/LoginForm.vue")
@@ -35,9 +35,11 @@ export default defineEventHandler(async (event) => {
35
35
  }
36
36
  const token = crypto.randomBytes(64).toString("hex");
37
37
  const tokenName = "auth_token";
38
+ const expiresAt = /* @__PURE__ */ new Date();
39
+ expiresAt.setMinutes(expiresAt.getMinutes() + options.auth.tokenExpiration);
38
40
  await db.sql`
39
- INSERT INTO {${personalAccessTokensTable}} (tokenable_type, tokenable_id, name, token, created_at, updated_at)
40
- VALUES ('user', ${user.id}, ${tokenName}, ${token}, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
41
+ INSERT INTO {${personalAccessTokensTable}} (tokenable_type, tokenable_id, name, token, expires_at, created_at, updated_at)
42
+ VALUES ('user', ${user.id}, ${tokenName}, ${token}, ${expiresAt.toISOString().slice(0, 19).replace("T", " ")}, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
41
43
  `;
42
44
  setCookie(event, "auth_token", token, {
43
45
  httpOnly: true,
@@ -0,0 +1,2 @@
1
+ declare const _default: import("nitropack").Task<"success">;
2
+ export default _default;
@@ -0,0 +1,30 @@
1
+ import { defineTask } from "nitropack/runtime/task";
2
+ export default defineTask({
3
+ meta: {
4
+ name: "nuxt-users:cleanup-tokens",
5
+ description: "Clean up expired personal access tokens and tokens without expiration"
6
+ },
7
+ async run({ payload }) {
8
+ const { useRuntimeConfig } = await import("#imports");
9
+ const { cleanupPersonalAccessTokens } = await import("../utils/index.js");
10
+ const { nuxtUsers } = useRuntimeConfig();
11
+ const options = nuxtUsers;
12
+ if (!options) {
13
+ throw new Error("Nuxt Users module not configured");
14
+ }
15
+ const includeNoExpiration = payload?.includeNoExpiration ?? true;
16
+ try {
17
+ const result = await cleanupPersonalAccessTokens(options, includeNoExpiration);
18
+ return {
19
+ result: "success",
20
+ expiredTokensRemoved: result.expiredCount,
21
+ noExpirationTokensRemoved: result.noExpirationCount,
22
+ totalTokensCleaned: result.totalCount,
23
+ includeNoExpiration
24
+ };
25
+ } catch (error) {
26
+ console.error("[Nuxt Users] Token cleanup failed:", error);
27
+ throw error;
28
+ }
29
+ }
30
+ });
@@ -1,5 +1,5 @@
1
1
  export { useDb, checkTableExists } from './db.js';
2
- export { createUser, findUserByEmail, updateUserPassword, getCurrentUserFromToken, hasAnyUsers } from './user.js';
2
+ export { createUser, findUserByEmail, updateUserPassword, getCurrentUserFromToken, hasAnyUsers, deleteExpiredPersonalAccessTokens, deleteTokensWithoutExpiration, cleanupPersonalAccessTokens, revokeUserTokens } from './user.js';
3
3
  export { createUsersTable } from './create-users-table.js';
4
4
  export { createPersonalAccessTokensTable } from './create-personal-access-tokens-table.js';
5
5
  export { createPasswordResetTokensTable } from './create-password-reset-tokens-table.js';
@@ -1,5 +1,5 @@
1
1
  export { useDb, checkTableExists } from "./db.js";
2
- export { createUser, findUserByEmail, updateUserPassword, getCurrentUserFromToken, hasAnyUsers } from "./user.js";
2
+ export { createUser, findUserByEmail, updateUserPassword, getCurrentUserFromToken, hasAnyUsers, deleteExpiredPersonalAccessTokens, deleteTokensWithoutExpiration, cleanupPersonalAccessTokens, revokeUserTokens } from "./user.js";
3
3
  export { createUsersTable } from "./create-users-table.js";
4
4
  export { createPersonalAccessTokensTable } from "./create-personal-access-tokens-table.js";
5
5
  export { createPasswordResetTokensTable } from "./create-password-reset-tokens-table.js";
@@ -24,4 +24,24 @@ export declare const hasAnyUsers: (options: ModuleOptions) => Promise<boolean>;
24
24
  * Gets the current user from an authentication token.
25
25
  */
26
26
  export declare const getCurrentUserFromToken: <T extends boolean = false>(token: string, options: ModuleOptions, withPass?: T) => Promise<T extends true ? User | null : UserWithoutPassword | null>;
27
+ /**
28
+ * Deletes expired personal access tokens from the database.
29
+ */
30
+ export declare const deleteExpiredPersonalAccessTokens: (options: ModuleOptions) => Promise<number>;
31
+ /**
32
+ * Deletes tokens without expiration dates (legacy tokens or security cleanup).
33
+ */
34
+ export declare const deleteTokensWithoutExpiration: (options: ModuleOptions) => Promise<number>;
35
+ /**
36
+ * Comprehensive token cleanup: removes expired tokens and optionally tokens without expiration.
37
+ */
38
+ export declare const cleanupPersonalAccessTokens: (options: ModuleOptions, includeNoExpiration?: boolean) => Promise<{
39
+ expiredCount: number;
40
+ noExpirationCount: number;
41
+ totalCount: number;
42
+ }>;
43
+ /**
44
+ * Revokes all tokens for a specific user.
45
+ */
46
+ export declare const revokeUserTokens: (userId: number, options: ModuleOptions) => Promise<void>;
27
47
  export {};
@@ -76,13 +76,19 @@ export const getCurrentUserFromToken = async (token, options, withPass = false)
76
76
  const personalAccessTokensTable = options.tables.personalAccessTokens;
77
77
  const usersTable = options.tables.users;
78
78
  const tokenResult = await db.sql`
79
- SELECT tokenable_id FROM {${personalAccessTokensTable}}
79
+ SELECT tokenable_id, expires_at FROM {${personalAccessTokensTable}}
80
80
  WHERE token = ${token}
81
+ AND (expires_at IS NULL OR expires_at > CURRENT_TIMESTAMP)
81
82
  `;
82
83
  if (tokenResult.rows.length === 0) {
83
84
  return null;
84
85
  }
85
86
  const userId = tokenResult.rows[0].tokenable_id;
87
+ await db.sql`
88
+ UPDATE {${personalAccessTokensTable}}
89
+ SET last_used_at = CURRENT_TIMESTAMP
90
+ WHERE token = ${token}
91
+ `;
86
92
  const userResult = await db.sql`
87
93
  SELECT * FROM {${usersTable}}
88
94
  WHERE id = ${userId}
@@ -97,3 +103,41 @@ export const getCurrentUserFromToken = async (token, options, withPass = false)
97
103
  const { password: _, ...userWithoutPassword } = user;
98
104
  return userWithoutPassword;
99
105
  };
106
+ export const deleteExpiredPersonalAccessTokens = async (options) => {
107
+ const db = await useDb(options);
108
+ const personalAccessTokensTable = options.tables.personalAccessTokens;
109
+ const result = await db.sql`
110
+ DELETE FROM {${personalAccessTokensTable}}
111
+ WHERE expires_at IS NOT NULL AND expires_at <= CURRENT_TIMESTAMP
112
+ `;
113
+ const deletedCount = result.rows?.length || 0;
114
+ console.log(`[Nuxt Users] ${deletedCount} expired personal access tokens deleted.`);
115
+ return deletedCount;
116
+ };
117
+ export const deleteTokensWithoutExpiration = async (options) => {
118
+ const db = await useDb(options);
119
+ const personalAccessTokensTable = options.tables.personalAccessTokens;
120
+ const result = await db.sql`
121
+ DELETE FROM {${personalAccessTokensTable}}
122
+ WHERE expires_at IS NULL
123
+ `;
124
+ const deletedCount = result.rows?.length || 0;
125
+ console.log(`[Nuxt Users] ${deletedCount} tokens without expiration deleted.`);
126
+ return deletedCount;
127
+ };
128
+ export const cleanupPersonalAccessTokens = async (options, includeNoExpiration = true) => {
129
+ const expiredCount = await deleteExpiredPersonalAccessTokens(options);
130
+ const noExpirationCount = includeNoExpiration ? await deleteTokensWithoutExpiration(options) : 0;
131
+ const totalCount = expiredCount + noExpirationCount;
132
+ console.log(`[Nuxt Users] Token cleanup completed: ${expiredCount} expired + ${noExpirationCount} without expiration = ${totalCount} total tokens removed.`);
133
+ return { expiredCount, noExpirationCount, totalCount };
134
+ };
135
+ export const revokeUserTokens = async (userId, options) => {
136
+ const db = await useDb(options);
137
+ const personalAccessTokensTable = options.tables.personalAccessTokens;
138
+ await db.sql`
139
+ DELETE FROM {${personalAccessTokensTable}}
140
+ WHERE tokenable_type = 'user' AND tokenable_id = ${userId}
141
+ `;
142
+ console.log(`[Nuxt Users] All tokens revoked for user ID: ${userId}`);
143
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nuxt-users",
3
- "version": "1.6.6",
3
+ "version": "1.6.7",
4
4
  "description": "A comprehensive user management module for Nuxt 3 applications with authentication, database support, and CLI tools",
5
5
  "keywords": [
6
6
  "nuxt",