nuxt-users 1.8.5 → 1.10.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 CHANGED
@@ -1,6 +1,45 @@
1
1
  # Changelog
2
2
 
3
3
 
4
+ ## v1.10.0
5
+
6
+ [compare changes](https://github.com/rrd108/nuxt-users/compare/v1.9.0...v1.10.0)
7
+
8
+ ### 🚀 Enhancements
9
+
10
+ - Add user CRUD API endpoints ([8cf5e0a](https://github.com/rrd108/nuxt-users/commit/8cf5e0a))
11
+ - Add user API endpoints ([05f326e](https://github.com/rrd108/nuxt-users/commit/05f326e))
12
+
13
+ ### ❤️ Contributors
14
+
15
+ - Rrd108 ([@rrd108](https://github.com/rrd108))
16
+
17
+ ## v2.0.0
18
+
19
+ [compare changes](https://github.com/rrd108/nuxt-users/compare/v1.8.5...v2.0.0)
20
+
21
+ ### 🚀 Enhancements
22
+
23
+ - ⚠️ Export TypeScript types and utilities for consumers ([729d961](https://github.com/rrd108/nuxt-users/commit/729d961))
24
+ - Add getLastLoginTime function to track user login activity ([001d4f2](https://github.com/rrd108/nuxt-users/commit/001d4f2))
25
+
26
+ ### 🏡 Chore
27
+
28
+ - Fix lint ([beb3e43](https://github.com/rrd108/nuxt-users/commit/beb3e43))
29
+ - **release:** V1.8.5 ([403cbd3](https://github.com/rrd108/nuxt-users/commit/403cbd3))
30
+
31
+ ### 🎨 Styles
32
+
33
+ - Fix code formatting and remove trailing whitespace ([ea07a3c](https://github.com/rrd108/nuxt-users/commit/ea07a3c))
34
+
35
+ #### ⚠️ Breaking Changes
36
+
37
+ - ⚠️ Export TypeScript types and utilities for consumers ([729d961](https://github.com/rrd108/nuxt-users/commit/729d961))
38
+
39
+ ### ❤️ Contributors
40
+
41
+ - Rrd108 ([@rrd108](https://github.com/rrd108))
42
+
4
43
  ## v1.8.5
5
44
 
6
45
  [compare changes](https://github.com/rrd108/nuxt-users/compare/v1.8.4...v1.8.5)
package/dist/cli.mjs CHANGED
@@ -4,7 +4,8 @@ import { defaultOptions } from './module.mjs';
4
4
  import { loadNuxt } from '@nuxt/kit';
5
5
  import { defu } from 'defu';
6
6
  import { runMigrations, createUser as createUser$1, createUsersTable as createUsersTable$1, createPersonalAccessTokensTable as createPersonalAccessTokensTable$1, createPasswordResetTokensTable as createPasswordResetTokensTable$1, createMigrationsTable as createMigrationsTable$1 } from '../dist/runtime/server/utils/index.js';
7
- import { getPasswordValidationOptions, validatePassword } from './utils.mjs';
7
+ import { g as getPasswordValidationOptions, v as validatePassword } from './shared/nuxt-users.BnOQs2LI.mjs';
8
+ import '../dist/runtime/server/utils/user.js';
8
9
 
9
10
  const getOptionsFromEnv = () => {
10
11
  const connectorName = process.env.DB_CONNECTOR || "sqlite";
@@ -247,7 +248,7 @@ const main = defineCommand({
247
248
  meta: {
248
249
  name: "nuxt-users",
249
250
  description: "CLI for Nuxt Users Module - Manage users, migrations, and database operations",
250
- version: "1.8.4"
251
+ version: "1.9.0"
251
252
  },
252
253
  subCommands: {
253
254
  migrate,
package/dist/module.d.mts CHANGED
@@ -1,139 +1,7 @@
1
1
  import * as _nuxt_schema from '@nuxt/schema';
2
+ import { R as RuntimeModuleOptions, M as ModuleOptions } from './shared/nuxt-users.CXezgQLO.mjs';
2
3
  export * from '../dist/runtime/server/utils/index.js';
3
4
 
4
- type DatabaseType = 'sqlite' | 'mysql' | 'postgresql';
5
- type DatabaseConfig = {
6
- path?: string;
7
- host?: string;
8
- port?: number;
9
- user?: string;
10
- password?: string;
11
- database?: string;
12
- };
13
- interface RuntimeModuleOptions {
14
- connector?: {
15
- name: DatabaseType;
16
- options: DatabaseConfig;
17
- };
18
- tables?: {
19
- migrations?: string;
20
- users?: string;
21
- personalAccessTokens?: string;
22
- passwordResetTokens?: string;
23
- };
24
- /**
25
- * Mailer configuration options for sending emails (e.g., password resets)
26
- * Uses nodemailer
27
- */
28
- mailer?: MailerOptions;
29
- /**
30
- * Base URL for password reset links
31
- * @default 'http://localhost:3000' // Example, will be set in module defaults
32
- */
33
- passwordResetBaseUrl?: string;
34
- /**
35
- * Skip database checks during module setup to prevent hanging
36
- * @default false
37
- */
38
- auth?: {
39
- /**
40
- * Whitelisted routes that do not require authentication
41
- * @default ['/login']
42
- * @example ['/login', '/register']
43
- */
44
- whitelist?: string[];
45
- /**
46
- * Token expiration time in minutes
47
- * @default 1440
48
- */
49
- tokenExpiration?: number;
50
- /**
51
- * Role-based permissions configuration
52
- * @default {}
53
- * @example {
54
- * admin: ['*'], // admin can access everything
55
- * user: ['/profile', '/api/user/profile'],
56
- * moderator: ['/admin/*', '/api/admin/*']
57
- * }
58
- */
59
- permissions?: Record<string, string[]>;
60
- };
61
- /**
62
- * Password validation configuration
63
- */
64
- passwordValidation?: {
65
- /**
66
- * Minimum password length
67
- * @default 8
68
- */
69
- minLength?: number;
70
- /**
71
- * Require uppercase letters
72
- * @default true
73
- */
74
- requireUppercase?: boolean;
75
- /**
76
- * Require lowercase letters
77
- * @default true
78
- */
79
- requireLowercase?: boolean;
80
- /**
81
- * Require numbers
82
- * @default true
83
- */
84
- requireNumbers?: boolean;
85
- /**
86
- * Require special characters
87
- * @default true
88
- */
89
- requireSpecialChars?: boolean;
90
- /**
91
- * Prevent common passwords
92
- * @default true
93
- */
94
- preventCommonPasswords?: boolean;
95
- };
96
- }
97
- interface ModuleOptions {
98
- connector: {
99
- name: DatabaseType;
100
- options: DatabaseConfig;
101
- };
102
- tables: {
103
- migrations: string;
104
- users: string;
105
- personalAccessTokens: string;
106
- passwordResetTokens: string;
107
- };
108
- mailer?: MailerOptions;
109
- passwordResetBaseUrl?: string;
110
- auth: {
111
- whitelist: string[];
112
- tokenExpiration: number;
113
- permissions: Record<string, string[]>;
114
- };
115
- passwordValidation: {
116
- minLength: number;
117
- requireUppercase: boolean;
118
- requireLowercase: boolean;
119
- requireNumbers: boolean;
120
- requireSpecialChars: boolean;
121
- preventCommonPasswords: boolean;
122
- };
123
- }
124
- interface MailerOptions {
125
- host: string;
126
- port: number;
127
- secure?: boolean;
128
- auth: {
129
- user: string;
130
- pass: string;
131
- };
132
- defaults?: {
133
- from: string;
134
- };
135
- }
136
-
137
5
  declare const defaultOptions: ModuleOptions;
138
6
  declare const _default: _nuxt_schema.NuxtModule<RuntimeModuleOptions, RuntimeModuleOptions, false>;
139
7
 
package/dist/module.d.ts CHANGED
@@ -1,139 +1,7 @@
1
1
  import * as _nuxt_schema from '@nuxt/schema';
2
+ import { R as RuntimeModuleOptions, M as ModuleOptions } from './shared/nuxt-users.CXezgQLO.js';
2
3
  export * from '../dist/runtime/server/utils/index.js';
3
4
 
4
- type DatabaseType = 'sqlite' | 'mysql' | 'postgresql';
5
- type DatabaseConfig = {
6
- path?: string;
7
- host?: string;
8
- port?: number;
9
- user?: string;
10
- password?: string;
11
- database?: string;
12
- };
13
- interface RuntimeModuleOptions {
14
- connector?: {
15
- name: DatabaseType;
16
- options: DatabaseConfig;
17
- };
18
- tables?: {
19
- migrations?: string;
20
- users?: string;
21
- personalAccessTokens?: string;
22
- passwordResetTokens?: string;
23
- };
24
- /**
25
- * Mailer configuration options for sending emails (e.g., password resets)
26
- * Uses nodemailer
27
- */
28
- mailer?: MailerOptions;
29
- /**
30
- * Base URL for password reset links
31
- * @default 'http://localhost:3000' // Example, will be set in module defaults
32
- */
33
- passwordResetBaseUrl?: string;
34
- /**
35
- * Skip database checks during module setup to prevent hanging
36
- * @default false
37
- */
38
- auth?: {
39
- /**
40
- * Whitelisted routes that do not require authentication
41
- * @default ['/login']
42
- * @example ['/login', '/register']
43
- */
44
- whitelist?: string[];
45
- /**
46
- * Token expiration time in minutes
47
- * @default 1440
48
- */
49
- tokenExpiration?: number;
50
- /**
51
- * Role-based permissions configuration
52
- * @default {}
53
- * @example {
54
- * admin: ['*'], // admin can access everything
55
- * user: ['/profile', '/api/user/profile'],
56
- * moderator: ['/admin/*', '/api/admin/*']
57
- * }
58
- */
59
- permissions?: Record<string, string[]>;
60
- };
61
- /**
62
- * Password validation configuration
63
- */
64
- passwordValidation?: {
65
- /**
66
- * Minimum password length
67
- * @default 8
68
- */
69
- minLength?: number;
70
- /**
71
- * Require uppercase letters
72
- * @default true
73
- */
74
- requireUppercase?: boolean;
75
- /**
76
- * Require lowercase letters
77
- * @default true
78
- */
79
- requireLowercase?: boolean;
80
- /**
81
- * Require numbers
82
- * @default true
83
- */
84
- requireNumbers?: boolean;
85
- /**
86
- * Require special characters
87
- * @default true
88
- */
89
- requireSpecialChars?: boolean;
90
- /**
91
- * Prevent common passwords
92
- * @default true
93
- */
94
- preventCommonPasswords?: boolean;
95
- };
96
- }
97
- interface ModuleOptions {
98
- connector: {
99
- name: DatabaseType;
100
- options: DatabaseConfig;
101
- };
102
- tables: {
103
- migrations: string;
104
- users: string;
105
- personalAccessTokens: string;
106
- passwordResetTokens: string;
107
- };
108
- mailer?: MailerOptions;
109
- passwordResetBaseUrl?: string;
110
- auth: {
111
- whitelist: string[];
112
- tokenExpiration: number;
113
- permissions: Record<string, string[]>;
114
- };
115
- passwordValidation: {
116
- minLength: number;
117
- requireUppercase: boolean;
118
- requireLowercase: boolean;
119
- requireNumbers: boolean;
120
- requireSpecialChars: boolean;
121
- preventCommonPasswords: boolean;
122
- };
123
- }
124
- interface MailerOptions {
125
- host: string;
126
- port: number;
127
- secure?: boolean;
128
- auth: {
129
- user: string;
130
- pass: string;
131
- };
132
- defaults?: {
133
- from: string;
134
- };
135
- }
136
-
137
5
  declare const defaultOptions: ModuleOptions;
138
6
  declare const _default: _nuxt_schema.NuxtModule<RuntimeModuleOptions, RuntimeModuleOptions, false>;
139
7
 
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "nuxt-users",
3
3
  "configKey": "nuxtUsers",
4
- "version": "1.8.5",
4
+ "version": "1.10.0",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.1",
7
7
  "unbuild": "3.6.0"
package/dist/module.mjs CHANGED
@@ -117,15 +117,35 @@ const module = defineNuxtModule({
117
117
  handler: resolver.resolve("./runtime/server/api/auth/logout.get")
118
118
  });
119
119
  addServerHandler({
120
- route: "/api/user/profile",
120
+ route: "/api/nuxt-users/profile",
121
121
  method: "get",
122
- handler: resolver.resolve("./runtime/server/api/user/profile.get")
122
+ handler: resolver.resolve("./runtime/server/api/nuxt-users/profile.get")
123
123
  });
124
124
  addServerHandler({
125
125
  route: "/api/auth/update-password",
126
126
  method: "post",
127
127
  handler: resolver.resolve("./runtime/server/api/auth/update-password.post")
128
128
  });
129
+ addServerHandler({
130
+ route: "/api/nuxt-users",
131
+ method: "post",
132
+ handler: resolver.resolve("./runtime/server/api/nuxt-users/index.post")
133
+ });
134
+ addServerHandler({
135
+ route: "/api/nuxt-users/:id",
136
+ method: "get",
137
+ handler: resolver.resolve("./runtime/server/api/nuxt-users/[id].get")
138
+ });
139
+ addServerHandler({
140
+ route: "/api/nuxt-users/:id",
141
+ method: "patch",
142
+ handler: resolver.resolve("./runtime/server/api/nuxt-users/[id].patch")
143
+ });
144
+ addServerHandler({
145
+ route: "/api/nuxt-users/:id",
146
+ method: "delete",
147
+ handler: resolver.resolve("./runtime/server/api/nuxt-users/[id].delete")
148
+ });
129
149
  nuxt.hook("nitro:config", async (nitroConfig) => {
130
150
  nitroConfig.experimental = nitroConfig.experimental || {};
131
151
  nitroConfig.experimental.database = true;
@@ -0,0 +1,4 @@
1
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<{
2
+ success: boolean;
3
+ }>>;
4
+ export default _default;
@@ -0,0 +1,23 @@
1
+ import { createError, defineEventHandler } from "h3";
2
+ import { useRuntimeConfig } from "#imports";
3
+ import { deleteUser } from "../../utils/user.js";
4
+ export default defineEventHandler(async (event) => {
5
+ const { nuxtUsers } = useRuntimeConfig();
6
+ const options = nuxtUsers;
7
+ const userId = Number(event.context.params?.id);
8
+ if (!userId) {
9
+ throw createError({
10
+ statusCode: 400,
11
+ statusMessage: "Invalid user ID"
12
+ });
13
+ }
14
+ try {
15
+ await deleteUser(userId, options);
16
+ return { success: true };
17
+ } catch (error) {
18
+ throw createError({
19
+ statusCode: 500,
20
+ statusMessage: `Error deleting user: ${error.message}`
21
+ });
22
+ }
23
+ });
@@ -0,0 +1,32 @@
1
+ import { createError, defineEventHandler } from "h3";
2
+ import { useRuntimeConfig } from "#imports";
3
+ import { findUserById } from "../../utils/user.js";
4
+ export default defineEventHandler(async (event) => {
5
+ const { nuxtUsers } = useRuntimeConfig();
6
+ const options = nuxtUsers;
7
+ const userId = Number(event.context.params?.id);
8
+ if (!userId) {
9
+ throw createError({
10
+ statusCode: 400,
11
+ statusMessage: "Invalid user ID"
12
+ });
13
+ }
14
+ try {
15
+ const user = await findUserById(userId, options);
16
+ if (!user) {
17
+ throw createError({
18
+ statusCode: 404,
19
+ statusMessage: "User not found"
20
+ });
21
+ }
22
+ return { user };
23
+ } catch (error) {
24
+ if (error && typeof error === "object" && "statusCode" in error) {
25
+ throw error;
26
+ }
27
+ throw createError({
28
+ statusCode: 500,
29
+ statusMessage: `Error fetching user: ${error instanceof Error ? error.message : String(error)}`
30
+ });
31
+ }
32
+ });
@@ -0,0 +1,4 @@
1
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<{
2
+ user: import("../../../../types.js").UserWithoutPassword;
3
+ }>>;
4
+ export default _default;
@@ -0,0 +1,24 @@
1
+ import { createError, defineEventHandler, readBody } from "h3";
2
+ import { useRuntimeConfig } from "#imports";
3
+ import { updateUser } from "../../utils/user.js";
4
+ export default defineEventHandler(async (event) => {
5
+ const { nuxtUsers } = useRuntimeConfig();
6
+ const options = nuxtUsers;
7
+ const userId = Number(event.context.params?.id);
8
+ if (!userId) {
9
+ throw createError({
10
+ statusCode: 400,
11
+ statusMessage: "Invalid user ID"
12
+ });
13
+ }
14
+ const body = await readBody(event);
15
+ try {
16
+ const updatedUser = await updateUser(userId, body, options);
17
+ return { user: updatedUser };
18
+ } catch (error) {
19
+ throw createError({
20
+ statusCode: 500,
21
+ statusMessage: `Error updating user: ${error.message}`
22
+ });
23
+ }
24
+ });
@@ -0,0 +1,4 @@
1
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<{
2
+ user: Omit<import("../../../../types.js").User, "password">;
3
+ }>>;
4
+ export default _default;
@@ -0,0 +1,29 @@
1
+ import { createError, defineEventHandler, readBody } from "h3";
2
+ import { useRuntimeConfig } from "#imports";
3
+ import { createUser } from "../../utils/user.js";
4
+ export default defineEventHandler(async (event) => {
5
+ const { nuxtUsers } = useRuntimeConfig();
6
+ const options = nuxtUsers;
7
+ const body = await readBody(event);
8
+ if (!body.email || !body.name || !body.password) {
9
+ throw createError({
10
+ statusCode: 400,
11
+ statusMessage: "Missing required fields: email, name, password"
12
+ });
13
+ }
14
+ try {
15
+ const newUser = await createUser({
16
+ email: body.email,
17
+ name: body.name,
18
+ password: body.password,
19
+ role: body.role
20
+ // role is optional
21
+ }, options);
22
+ return { user: newUser };
23
+ } catch (error) {
24
+ throw createError({
25
+ statusCode: 500,
26
+ statusMessage: `Error creating user: ${error.message}`
27
+ });
28
+ }
29
+ });
@@ -0,0 +1,4 @@
1
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<{
2
+ user: import("../../../../types.js").UserWithoutPassword;
3
+ }>>;
4
+ export default _default;
@@ -1,5 +1,5 @@
1
1
  export { useDb, checkTableExists } from './db.js';
2
- export { createUser, findUserByEmail, updateUserPassword, getCurrentUserFromToken, hasAnyUsers, deleteExpiredPersonalAccessTokens, deleteTokensWithoutExpiration, cleanupPersonalAccessTokens, revokeUserTokens } from './user.js';
2
+ export { createUser, findUserByEmail, updateUserPassword, getCurrentUserFromToken, hasAnyUsers, deleteExpiredPersonalAccessTokens, deleteTokensWithoutExpiration, cleanupPersonalAccessTokens, revokeUserTokens, getLastLoginTime } 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, deleteExpiredPersonalAccessTokens, deleteTokensWithoutExpiration, cleanupPersonalAccessTokens, revokeUserTokens } from "./user.js";
2
+ export { createUser, findUserByEmail, updateUserPassword, getCurrentUserFromToken, hasAnyUsers, deleteExpiredPersonalAccessTokens, deleteTokensWithoutExpiration, cleanupPersonalAccessTokens, revokeUserTokens, getLastLoginTime } 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";
@@ -14,6 +14,18 @@ export declare const createUser: (userData: CreateUserParams, options: ModuleOpt
14
14
  * Finds a user by their email address.
15
15
  */
16
16
  export declare const findUserByEmail: (email: string, options: ModuleOptions) => Promise<User | null>;
17
+ /**
18
+ * Finds a user by their ID.
19
+ */
20
+ export declare const findUserById: <T extends boolean = false>(id: number, options: ModuleOptions, withPass?: T) => Promise<T extends true ? User | null : UserWithoutPassword | null>;
21
+ /**
22
+ * Updates a user's details.
23
+ */
24
+ export declare const updateUser: (id: number, userData: Partial<User>, options: ModuleOptions) => Promise<UserWithoutPassword>;
25
+ /**
26
+ * Deletes a user by their ID.
27
+ */
28
+ export declare const deleteUser: (id: number, options: ModuleOptions) => Promise<void>;
17
29
  /**
18
30
  * Updates a user's password.
19
31
  * Hashes the new password before storing.
@@ -44,4 +56,9 @@ export declare const cleanupPersonalAccessTokens: (options: ModuleOptions, inclu
44
56
  * Revokes all tokens for a specific user.
45
57
  */
46
58
  export declare const revokeUserTokens: (userId: number, options: ModuleOptions) => Promise<void>;
59
+ /**
60
+ * Gets the last login time for a user based on their most recent personal access token creation.
61
+ * This represents when the user last successfully logged in.
62
+ */
63
+ export declare const getLastLoginTime: (userId: number, options: ModuleOptions) => Promise<string | null>;
47
64
  export {};
@@ -47,6 +47,60 @@ export const findUserByEmail = async (email, options) => {
47
47
  updated_at: user.updated_at instanceof Date ? user.updated_at.toISOString() : user.updated_at
48
48
  };
49
49
  };
50
+ export const findUserById = async (id, options, withPass = false) => {
51
+ const db = await useDb(options);
52
+ const usersTable = options.tables.users;
53
+ const result = await db.sql`SELECT * FROM {${usersTable}} WHERE id = ${id}`;
54
+ if (result.rows.length === 0) {
55
+ return null;
56
+ }
57
+ const user = result.rows[0];
58
+ const normalizedUser = {
59
+ ...user,
60
+ created_at: typeof user.created_at === "object" ? user.created_at.toISOString() : user.created_at,
61
+ updated_at: typeof user.updated_at === "object" ? user.updated_at.toISOString() : user.updated_at
62
+ };
63
+ if (withPass === true) {
64
+ return normalizedUser;
65
+ }
66
+ const { password, ...userWithoutPassword } = normalizedUser;
67
+ return userWithoutPassword;
68
+ };
69
+ export const updateUser = async (id, userData, options) => {
70
+ const db = await useDb(options);
71
+ const usersTable = options.tables.users;
72
+ const currentUser = await findUserById(id, options, true);
73
+ if (!currentUser) {
74
+ throw new Error("User not found.");
75
+ }
76
+ const fieldsToUpdate = { ...userData };
77
+ delete fieldsToUpdate.id;
78
+ delete fieldsToUpdate.password;
79
+ if (Object.keys(fieldsToUpdate).length === 0) {
80
+ const { password, ...user } = currentUser;
81
+ return user;
82
+ }
83
+ for (const [key, value] of Object.entries(fieldsToUpdate)) {
84
+ await db.sql`
85
+ UPDATE {${usersTable}}
86
+ SET ${key} = ${value}, updated_at = CURRENT_TIMESTAMP
87
+ WHERE id = ${id}
88
+ `;
89
+ }
90
+ const updatedUser = await findUserById(id, options);
91
+ if (!updatedUser) {
92
+ throw new Error("Failed to retrieve updated user.");
93
+ }
94
+ return updatedUser;
95
+ };
96
+ export const deleteUser = async (id, options) => {
97
+ const db = await useDb(options);
98
+ const usersTable = options.tables.users;
99
+ const result = await db.sql`DELETE FROM {${usersTable}} WHERE id = ${id}`;
100
+ if (result.rows?.length === 0) {
101
+ throw new Error("User not found or could not be deleted.");
102
+ }
103
+ };
50
104
  export const updateUserPassword = async (email, newPassword, options) => {
51
105
  const db = await useDb(options);
52
106
  const usersTable = options.tables.users;
@@ -141,3 +195,19 @@ export const revokeUserTokens = async (userId, options) => {
141
195
  `;
142
196
  console.log(`[Nuxt Users] All tokens revoked for user ID: ${userId}`);
143
197
  };
198
+ export const getLastLoginTime = async (userId, options) => {
199
+ const db = await useDb(options);
200
+ const personalAccessTokensTable = options.tables.personalAccessTokens;
201
+ const result = await db.sql`
202
+ SELECT created_at
203
+ FROM {${personalAccessTokensTable}}
204
+ WHERE tokenable_id = ${userId} AND tokenable_type = 'user'
205
+ ORDER BY created_at DESC
206
+ LIMIT 1
207
+ `;
208
+ if (result.rows.length === 0) {
209
+ return null;
210
+ }
211
+ const lastLogin = result.rows[0].created_at;
212
+ return lastLogin instanceof Date ? lastLogin.toISOString() : lastLogin;
213
+ };
@@ -0,0 +1,162 @@
1
+ const DEFAULT_OPTIONS = {
2
+ minLength: 8,
3
+ requireUppercase: true,
4
+ requireLowercase: true,
5
+ requireNumbers: true,
6
+ requireSpecialChars: true,
7
+ preventCommonPasswords: true
8
+ };
9
+ const getPasswordValidationOptions = (moduleOptions) => {
10
+ if (!moduleOptions?.passwordValidation) {
11
+ return DEFAULT_OPTIONS;
12
+ }
13
+ return {
14
+ ...DEFAULT_OPTIONS,
15
+ ...moduleOptions.passwordValidation
16
+ };
17
+ };
18
+ const COMMON_PASSWORDS = [
19
+ "password",
20
+ "123456",
21
+ "123456789",
22
+ "qwerty",
23
+ "abc123",
24
+ "password123",
25
+ "admin",
26
+ "letmein",
27
+ "welcome",
28
+ "monkey",
29
+ "dragon",
30
+ "master",
31
+ "hello",
32
+ "freedom",
33
+ "whatever",
34
+ "qazwsx",
35
+ "trustno1",
36
+ "jordan",
37
+ "harley",
38
+ "ranger",
39
+ "buster",
40
+ "thomas",
41
+ "tigger",
42
+ "robert",
43
+ "soccer",
44
+ "batman",
45
+ "test",
46
+ "pass",
47
+ "user",
48
+ "guest",
49
+ "login",
50
+ "secret",
51
+ "god",
52
+ "love",
53
+ "sex",
54
+ "money",
55
+ "password1",
56
+ "12345678",
57
+ "qwerty123",
58
+ "admin123",
59
+ "password!",
60
+ "password1!",
61
+ "password123",
62
+ "password123!"
63
+ ];
64
+ const validatePassword = (password, options = {}) => {
65
+ const config = { ...DEFAULT_OPTIONS, ...options };
66
+ const errors = [];
67
+ const hints = [];
68
+ let score = 0;
69
+ if (password.length < config.minLength) {
70
+ errors.push(`Password must be at least ${config.minLength} characters long`);
71
+ hints.push(`Use at least ${config.minLength} characters`);
72
+ } else {
73
+ score += 20;
74
+ if (password.length < 12) {
75
+ hints.push("Use 12 or more characters for extra security");
76
+ }
77
+ }
78
+ if (config.requireUppercase && !/[A-Z]/.test(password)) {
79
+ errors.push("Password must contain at least one uppercase letter");
80
+ hints.push("Add an uppercase letter (A-Z)");
81
+ } else if (/[A-Z]/.test(password)) {
82
+ score += 15;
83
+ }
84
+ if (config.requireLowercase && !/[a-z]/.test(password)) {
85
+ errors.push("Password must contain at least one lowercase letter");
86
+ hints.push("Add a lowercase letter (a-z)");
87
+ } else if (/[a-z]/.test(password)) {
88
+ score += 15;
89
+ }
90
+ if (config.requireNumbers && !/\d/.test(password)) {
91
+ errors.push("Password must contain at least one number");
92
+ hints.push("Add a number (0-9)");
93
+ } else if (/\d/.test(password)) {
94
+ score += 15;
95
+ }
96
+ if (config.requireSpecialChars && !/[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]/.test(password)) {
97
+ errors.push("Password must contain at least one special character");
98
+ hints.push("Add a special character (e.g. !@#$%)");
99
+ } else if (/[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]/.test(password)) {
100
+ score += 15;
101
+ }
102
+ if (config.preventCommonPasswords && COMMON_PASSWORDS.includes(password.toLowerCase())) {
103
+ errors.push("Password is too common. Please choose a more unique password");
104
+ hints.push("Avoid common passwords or names");
105
+ }
106
+ if (password.length >= 12) {
107
+ score += 10;
108
+ } else if (password.length >= 10) {
109
+ score += 5;
110
+ }
111
+ const hasUppercase = /[A-Z]/.test(password);
112
+ const hasLowercase = /[a-z]/.test(password);
113
+ const hasNumbers = /\d/.test(password);
114
+ const hasSpecial = /[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]/.test(password);
115
+ const complexityTypes = [hasUppercase, hasLowercase, hasNumbers, hasSpecial].filter(Boolean).length;
116
+ if (complexityTypes >= 4) {
117
+ score += 10;
118
+ } else if (complexityTypes >= 3) {
119
+ score += 5;
120
+ hints.push("Use a mix of uppercase, lowercase, numbers, and special characters");
121
+ }
122
+ let strength = "weak";
123
+ if (score >= 80) {
124
+ strength = "strong";
125
+ } else if (score >= 60) {
126
+ strength = "medium";
127
+ }
128
+ return {
129
+ isValid: errors.length === 0,
130
+ errors,
131
+ strength,
132
+ score: Math.min(score, 100),
133
+ hints: Array.from(new Set(hints))
134
+ // deduplicate
135
+ };
136
+ };
137
+ const getPasswordStrengthColor = (strength) => {
138
+ switch (strength) {
139
+ case "weak":
140
+ return "#dc3545";
141
+ case "medium":
142
+ return "#ffc107";
143
+ case "strong":
144
+ return "#28a745";
145
+ default:
146
+ return "#6c757d";
147
+ }
148
+ };
149
+ const getPasswordStrengthText = (strength) => {
150
+ switch (strength) {
151
+ case "weak":
152
+ return "Weak";
153
+ case "medium":
154
+ return "Medium";
155
+ case "strong":
156
+ return "Strong";
157
+ default:
158
+ return "Unknown";
159
+ }
160
+ };
161
+
162
+ export { getPasswordStrengthColor as a, getPasswordStrengthText as b, getPasswordValidationOptions as g, validatePassword as v };
@@ -0,0 +1,167 @@
1
+ type DatabaseType = 'sqlite' | 'mysql' | 'postgresql';
2
+ type DatabaseConfig = {
3
+ path?: string;
4
+ host?: string;
5
+ port?: number;
6
+ user?: string;
7
+ password?: string;
8
+ database?: string;
9
+ };
10
+ interface RuntimeModuleOptions {
11
+ connector?: {
12
+ name: DatabaseType;
13
+ options: DatabaseConfig;
14
+ };
15
+ tables?: {
16
+ migrations?: string;
17
+ users?: string;
18
+ personalAccessTokens?: string;
19
+ passwordResetTokens?: string;
20
+ };
21
+ /**
22
+ * Mailer configuration options for sending emails (e.g., password resets)
23
+ * Uses nodemailer
24
+ */
25
+ mailer?: MailerOptions;
26
+ /**
27
+ * Base URL for password reset links
28
+ * @default 'http://localhost:3000' // Example, will be set in module defaults
29
+ */
30
+ passwordResetBaseUrl?: string;
31
+ /**
32
+ * Skip database checks during module setup to prevent hanging
33
+ * @default false
34
+ */
35
+ auth?: {
36
+ /**
37
+ * Whitelisted routes that do not require authentication
38
+ * @default ['/login']
39
+ * @example ['/login', '/register']
40
+ */
41
+ whitelist?: string[];
42
+ /**
43
+ * Token expiration time in minutes
44
+ * @default 1440
45
+ */
46
+ tokenExpiration?: number;
47
+ /**
48
+ * Role-based permissions configuration
49
+ * @default {}
50
+ * @example {
51
+ * admin: ['*'], // admin can access everything
52
+ * user: ['/profile', '/api/user/profile'],
53
+ * moderator: ['/admin/*', '/api/admin/*']
54
+ * }
55
+ */
56
+ permissions?: Record<string, string[]>;
57
+ };
58
+ /**
59
+ * Password validation configuration
60
+ */
61
+ passwordValidation?: {
62
+ /**
63
+ * Minimum password length
64
+ * @default 8
65
+ */
66
+ minLength?: number;
67
+ /**
68
+ * Require uppercase letters
69
+ * @default true
70
+ */
71
+ requireUppercase?: boolean;
72
+ /**
73
+ * Require lowercase letters
74
+ * @default true
75
+ */
76
+ requireLowercase?: boolean;
77
+ /**
78
+ * Require numbers
79
+ * @default true
80
+ */
81
+ requireNumbers?: boolean;
82
+ /**
83
+ * Require special characters
84
+ * @default true
85
+ */
86
+ requireSpecialChars?: boolean;
87
+ /**
88
+ * Prevent common passwords
89
+ * @default true
90
+ */
91
+ preventCommonPasswords?: boolean;
92
+ };
93
+ }
94
+ interface ModuleOptions {
95
+ connector: {
96
+ name: DatabaseType;
97
+ options: DatabaseConfig;
98
+ };
99
+ tables: {
100
+ migrations: string;
101
+ users: string;
102
+ personalAccessTokens: string;
103
+ passwordResetTokens: string;
104
+ };
105
+ mailer?: MailerOptions;
106
+ passwordResetBaseUrl?: string;
107
+ auth: {
108
+ whitelist: string[];
109
+ tokenExpiration: number;
110
+ permissions: Record<string, string[]>;
111
+ };
112
+ passwordValidation: {
113
+ minLength: number;
114
+ requireUppercase: boolean;
115
+ requireLowercase: boolean;
116
+ requireNumbers: boolean;
117
+ requireSpecialChars: boolean;
118
+ preventCommonPasswords: boolean;
119
+ };
120
+ }
121
+ interface MailerOptions {
122
+ host: string;
123
+ port: number;
124
+ secure?: boolean;
125
+ auth: {
126
+ user: string;
127
+ pass: string;
128
+ };
129
+ defaults?: {
130
+ from: string;
131
+ };
132
+ }
133
+ interface User {
134
+ id: number;
135
+ email: string;
136
+ name: string;
137
+ password: string;
138
+ role: string;
139
+ created_at: string;
140
+ updated_at: string;
141
+ }
142
+ type UserWithoutPassword = Omit<User, 'password'>;
143
+ interface PersonalAccessToken {
144
+ id: number;
145
+ tokenable_type: string;
146
+ tokenable_id: number;
147
+ name: string;
148
+ token: string;
149
+ abilities?: string;
150
+ last_used_at?: string;
151
+ expires_at?: string;
152
+ created_at: string;
153
+ updated_at: string;
154
+ }
155
+ interface PasswordResetToken {
156
+ id: number;
157
+ email: string;
158
+ token: string;
159
+ created_at: string;
160
+ }
161
+ interface LoginFormData {
162
+ email: string;
163
+ password: string;
164
+ rememberMe?: boolean;
165
+ }
166
+
167
+ export type { LoginFormData as L, ModuleOptions as M, PersonalAccessToken as P, RuntimeModuleOptions as R, User as U, UserWithoutPassword as a, PasswordResetToken as b };
@@ -0,0 +1,167 @@
1
+ type DatabaseType = 'sqlite' | 'mysql' | 'postgresql';
2
+ type DatabaseConfig = {
3
+ path?: string;
4
+ host?: string;
5
+ port?: number;
6
+ user?: string;
7
+ password?: string;
8
+ database?: string;
9
+ };
10
+ interface RuntimeModuleOptions {
11
+ connector?: {
12
+ name: DatabaseType;
13
+ options: DatabaseConfig;
14
+ };
15
+ tables?: {
16
+ migrations?: string;
17
+ users?: string;
18
+ personalAccessTokens?: string;
19
+ passwordResetTokens?: string;
20
+ };
21
+ /**
22
+ * Mailer configuration options for sending emails (e.g., password resets)
23
+ * Uses nodemailer
24
+ */
25
+ mailer?: MailerOptions;
26
+ /**
27
+ * Base URL for password reset links
28
+ * @default 'http://localhost:3000' // Example, will be set in module defaults
29
+ */
30
+ passwordResetBaseUrl?: string;
31
+ /**
32
+ * Skip database checks during module setup to prevent hanging
33
+ * @default false
34
+ */
35
+ auth?: {
36
+ /**
37
+ * Whitelisted routes that do not require authentication
38
+ * @default ['/login']
39
+ * @example ['/login', '/register']
40
+ */
41
+ whitelist?: string[];
42
+ /**
43
+ * Token expiration time in minutes
44
+ * @default 1440
45
+ */
46
+ tokenExpiration?: number;
47
+ /**
48
+ * Role-based permissions configuration
49
+ * @default {}
50
+ * @example {
51
+ * admin: ['*'], // admin can access everything
52
+ * user: ['/profile', '/api/user/profile'],
53
+ * moderator: ['/admin/*', '/api/admin/*']
54
+ * }
55
+ */
56
+ permissions?: Record<string, string[]>;
57
+ };
58
+ /**
59
+ * Password validation configuration
60
+ */
61
+ passwordValidation?: {
62
+ /**
63
+ * Minimum password length
64
+ * @default 8
65
+ */
66
+ minLength?: number;
67
+ /**
68
+ * Require uppercase letters
69
+ * @default true
70
+ */
71
+ requireUppercase?: boolean;
72
+ /**
73
+ * Require lowercase letters
74
+ * @default true
75
+ */
76
+ requireLowercase?: boolean;
77
+ /**
78
+ * Require numbers
79
+ * @default true
80
+ */
81
+ requireNumbers?: boolean;
82
+ /**
83
+ * Require special characters
84
+ * @default true
85
+ */
86
+ requireSpecialChars?: boolean;
87
+ /**
88
+ * Prevent common passwords
89
+ * @default true
90
+ */
91
+ preventCommonPasswords?: boolean;
92
+ };
93
+ }
94
+ interface ModuleOptions {
95
+ connector: {
96
+ name: DatabaseType;
97
+ options: DatabaseConfig;
98
+ };
99
+ tables: {
100
+ migrations: string;
101
+ users: string;
102
+ personalAccessTokens: string;
103
+ passwordResetTokens: string;
104
+ };
105
+ mailer?: MailerOptions;
106
+ passwordResetBaseUrl?: string;
107
+ auth: {
108
+ whitelist: string[];
109
+ tokenExpiration: number;
110
+ permissions: Record<string, string[]>;
111
+ };
112
+ passwordValidation: {
113
+ minLength: number;
114
+ requireUppercase: boolean;
115
+ requireLowercase: boolean;
116
+ requireNumbers: boolean;
117
+ requireSpecialChars: boolean;
118
+ preventCommonPasswords: boolean;
119
+ };
120
+ }
121
+ interface MailerOptions {
122
+ host: string;
123
+ port: number;
124
+ secure?: boolean;
125
+ auth: {
126
+ user: string;
127
+ pass: string;
128
+ };
129
+ defaults?: {
130
+ from: string;
131
+ };
132
+ }
133
+ interface User {
134
+ id: number;
135
+ email: string;
136
+ name: string;
137
+ password: string;
138
+ role: string;
139
+ created_at: string;
140
+ updated_at: string;
141
+ }
142
+ type UserWithoutPassword = Omit<User, 'password'>;
143
+ interface PersonalAccessToken {
144
+ id: number;
145
+ tokenable_type: string;
146
+ tokenable_id: number;
147
+ name: string;
148
+ token: string;
149
+ abilities?: string;
150
+ last_used_at?: string;
151
+ expires_at?: string;
152
+ created_at: string;
153
+ updated_at: string;
154
+ }
155
+ interface PasswordResetToken {
156
+ id: number;
157
+ email: string;
158
+ token: string;
159
+ created_at: string;
160
+ }
161
+ interface LoginFormData {
162
+ email: string;
163
+ password: string;
164
+ rememberMe?: boolean;
165
+ }
166
+
167
+ export type { LoginFormData as L, ModuleOptions as M, PersonalAccessToken as P, RuntimeModuleOptions as R, User as U, UserWithoutPassword as a, PasswordResetToken as b };
package/dist/utils.d.mts CHANGED
@@ -1,3 +1,6 @@
1
+ export { L as LoginFormData, M as ModuleOptions, b as PasswordResetToken, P as PersonalAccessToken, U as User, a as UserWithoutPassword } from './shared/nuxt-users.CXezgQLO.mjs';
2
+ export { findUserByEmail, getCurrentUserFromToken, getLastLoginTime } from '../dist/runtime/server/utils/user.js';
3
+
1
4
  interface PasswordValidationResult {
2
5
  isValid: boolean;
3
6
  errors: string[];
package/dist/utils.d.ts CHANGED
@@ -1,3 +1,6 @@
1
+ export { L as LoginFormData, M as ModuleOptions, b as PasswordResetToken, P as PersonalAccessToken, U as User, a as UserWithoutPassword } from './shared/nuxt-users.CXezgQLO.js';
2
+ export { findUserByEmail, getCurrentUserFromToken, getLastLoginTime } from '../dist/runtime/server/utils/user.js';
3
+
1
4
  interface PasswordValidationResult {
2
5
  isValid: boolean;
3
6
  errors: string[];
package/dist/utils.mjs CHANGED
@@ -1,162 +1,2 @@
1
- const DEFAULT_OPTIONS = {
2
- minLength: 8,
3
- requireUppercase: true,
4
- requireLowercase: true,
5
- requireNumbers: true,
6
- requireSpecialChars: true,
7
- preventCommonPasswords: true
8
- };
9
- const getPasswordValidationOptions = (moduleOptions) => {
10
- if (!moduleOptions?.passwordValidation) {
11
- return DEFAULT_OPTIONS;
12
- }
13
- return {
14
- ...DEFAULT_OPTIONS,
15
- ...moduleOptions.passwordValidation
16
- };
17
- };
18
- const COMMON_PASSWORDS = [
19
- "password",
20
- "123456",
21
- "123456789",
22
- "qwerty",
23
- "abc123",
24
- "password123",
25
- "admin",
26
- "letmein",
27
- "welcome",
28
- "monkey",
29
- "dragon",
30
- "master",
31
- "hello",
32
- "freedom",
33
- "whatever",
34
- "qazwsx",
35
- "trustno1",
36
- "jordan",
37
- "harley",
38
- "ranger",
39
- "buster",
40
- "thomas",
41
- "tigger",
42
- "robert",
43
- "soccer",
44
- "batman",
45
- "test",
46
- "pass",
47
- "user",
48
- "guest",
49
- "login",
50
- "secret",
51
- "god",
52
- "love",
53
- "sex",
54
- "money",
55
- "password1",
56
- "12345678",
57
- "qwerty123",
58
- "admin123",
59
- "password!",
60
- "password1!",
61
- "password123",
62
- "password123!"
63
- ];
64
- const validatePassword = (password, options = {}) => {
65
- const config = { ...DEFAULT_OPTIONS, ...options };
66
- const errors = [];
67
- const hints = [];
68
- let score = 0;
69
- if (password.length < config.minLength) {
70
- errors.push(`Password must be at least ${config.minLength} characters long`);
71
- hints.push(`Use at least ${config.minLength} characters`);
72
- } else {
73
- score += 20;
74
- if (password.length < 12) {
75
- hints.push("Use 12 or more characters for extra security");
76
- }
77
- }
78
- if (config.requireUppercase && !/[A-Z]/.test(password)) {
79
- errors.push("Password must contain at least one uppercase letter");
80
- hints.push("Add an uppercase letter (A-Z)");
81
- } else if (/[A-Z]/.test(password)) {
82
- score += 15;
83
- }
84
- if (config.requireLowercase && !/[a-z]/.test(password)) {
85
- errors.push("Password must contain at least one lowercase letter");
86
- hints.push("Add a lowercase letter (a-z)");
87
- } else if (/[a-z]/.test(password)) {
88
- score += 15;
89
- }
90
- if (config.requireNumbers && !/\d/.test(password)) {
91
- errors.push("Password must contain at least one number");
92
- hints.push("Add a number (0-9)");
93
- } else if (/\d/.test(password)) {
94
- score += 15;
95
- }
96
- if (config.requireSpecialChars && !/[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]/.test(password)) {
97
- errors.push("Password must contain at least one special character");
98
- hints.push("Add a special character (e.g. !@#$%)");
99
- } else if (/[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]/.test(password)) {
100
- score += 15;
101
- }
102
- if (config.preventCommonPasswords && COMMON_PASSWORDS.includes(password.toLowerCase())) {
103
- errors.push("Password is too common. Please choose a more unique password");
104
- hints.push("Avoid common passwords or names");
105
- }
106
- if (password.length >= 12) {
107
- score += 10;
108
- } else if (password.length >= 10) {
109
- score += 5;
110
- }
111
- const hasUppercase = /[A-Z]/.test(password);
112
- const hasLowercase = /[a-z]/.test(password);
113
- const hasNumbers = /\d/.test(password);
114
- const hasSpecial = /[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]/.test(password);
115
- const complexityTypes = [hasUppercase, hasLowercase, hasNumbers, hasSpecial].filter(Boolean).length;
116
- if (complexityTypes >= 4) {
117
- score += 10;
118
- } else if (complexityTypes >= 3) {
119
- score += 5;
120
- hints.push("Use a mix of uppercase, lowercase, numbers, and special characters");
121
- }
122
- let strength = "weak";
123
- if (score >= 80) {
124
- strength = "strong";
125
- } else if (score >= 60) {
126
- strength = "medium";
127
- }
128
- return {
129
- isValid: errors.length === 0,
130
- errors,
131
- strength,
132
- score: Math.min(score, 100),
133
- hints: Array.from(new Set(hints))
134
- // deduplicate
135
- };
136
- };
137
- const getPasswordStrengthColor = (strength) => {
138
- switch (strength) {
139
- case "weak":
140
- return "#dc3545";
141
- case "medium":
142
- return "#ffc107";
143
- case "strong":
144
- return "#28a745";
145
- default:
146
- return "#6c757d";
147
- }
148
- };
149
- const getPasswordStrengthText = (strength) => {
150
- switch (strength) {
151
- case "weak":
152
- return "Weak";
153
- case "medium":
154
- return "Medium";
155
- case "strong":
156
- return "Strong";
157
- default:
158
- return "Unknown";
159
- }
160
- };
161
-
162
- export { getPasswordStrengthColor, getPasswordStrengthText, getPasswordValidationOptions, validatePassword };
1
+ export { a as getPasswordStrengthColor, b as getPasswordStrengthText, g as getPasswordValidationOptions, v as validatePassword } from './shared/nuxt-users.BnOQs2LI.mjs';
2
+ export { findUserByEmail, getCurrentUserFromToken, getLastLoginTime } from '../dist/runtime/server/utils/user.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nuxt-users",
3
- "version": "1.8.5",
3
+ "version": "1.10.0",
4
4
  "description": "A comprehensive user management module for Nuxt 3 and Nuxt 4 applications with authentication, authorization, database support, and CLI tools",
5
5
  "keywords": [
6
6
  "nuxt",