nuxt-users 1.34.7 → 1.36.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 +98 -0
- package/dist/cli.mjs +111 -0
- package/dist/module.d.mts +1 -1
- package/dist/module.d.ts +1 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +38 -3
- package/dist/runtime/assets/nuxt-users.css +1 -1
- package/dist/runtime/components/NUsersEmailConfirmation.d.vue.ts +38 -0
- package/dist/runtime/components/NUsersEmailConfirmation.vue.d.ts +3 -2
- package/dist/runtime/components/NUsersGoogleLoginButton.d.vue.ts +31 -0
- package/dist/runtime/components/NUsersGoogleLoginButton.vue +75 -0
- package/dist/runtime/components/NUsersGoogleLoginButton.vue.d.ts +31 -0
- package/dist/runtime/components/NUsersList.d.vue.ts +0 -0
- package/dist/runtime/components/NUsersLoginForm.d.vue.ts +48 -0
- package/dist/runtime/components/NUsersLoginForm.vue.d.ts +5 -4
- package/dist/runtime/components/NUsersLogoutLink.d.vue.ts +38 -0
- package/dist/runtime/components/NUsersLogoutLink.vue.d.ts +5 -4
- package/dist/runtime/components/NUsersPasswordStrengthIndicator.d.vue.ts +13 -0
- package/dist/runtime/components/NUsersPasswordStrengthIndicator.vue.d.ts +2 -1
- package/dist/runtime/components/NUsersProfileInfo.d.vue.ts +3 -0
- package/dist/runtime/components/NUsersProfileInfo.vue.d.ts +2 -1
- package/dist/runtime/components/NUsersRegisterForm.d.vue.ts +61 -0
- package/dist/runtime/components/NUsersRegisterForm.vue.d.ts +5 -4
- package/dist/runtime/components/NUsersResetPasswordForm.d.vue.ts +18 -0
- package/dist/runtime/components/NUsersResetPasswordForm.vue.d.ts +4 -3
- package/dist/runtime/components/NUsersUserCard.d.vue.ts +44 -0
- package/dist/runtime/components/NUsersUserCard.vue.d.ts +3 -2
- package/dist/runtime/components/NUsersUserForm.d.vue.ts +15 -0
- package/dist/runtime/components/NUsersUserForm.vue.d.ts +2 -1
- package/dist/runtime/composables/useAuthentication.d.ts +1 -1
- package/dist/runtime/composables/useAuthentication.js +26 -8
- package/dist/runtime/middleware/authorization.client.js +9 -7
- package/dist/runtime/server/api/nuxt-users/auth/google/callback.get.d.ts +2 -0
- package/dist/runtime/server/api/nuxt-users/auth/google/callback.get.js +68 -0
- package/dist/runtime/server/api/nuxt-users/auth/google/redirect.get.d.ts +2 -0
- package/dist/runtime/server/api/nuxt-users/auth/google/redirect.get.js +39 -0
- package/dist/runtime/server/api/nuxt-users/inactive.get.js +1 -1
- package/dist/runtime/server/api/nuxt-users/index.get.js +1 -1
- package/dist/runtime/server/api/nuxt-users/session/index.post.d.ts +1 -1
- package/dist/runtime/server/api/nuxt-users/session/index.post.js +6 -0
- package/dist/runtime/server/services/password.js +8 -0
- package/dist/runtime/server/services/registration.js +14 -1
- package/dist/runtime/server/utils/add-google-oauth-fields.d.ts +2 -0
- package/dist/runtime/server/utils/add-google-oauth-fields.js +22 -0
- package/dist/runtime/server/utils/create-users-table.js +7 -1
- package/dist/runtime/server/utils/google-oauth.d.ts +33 -0
- package/dist/runtime/server/utils/google-oauth.js +142 -0
- package/dist/runtime/server/utils/index.d.ts +1 -0
- package/dist/runtime/server/utils/index.js +1 -0
- package/dist/runtime/server/utils/migrate.js +5 -0
- package/dist/runtime/server/utils/user.js +24 -3
- package/dist/shared/{nuxt-users.BTVH9EyH.d.mts → nuxt-users.SXaMnaEL.d.mts} +45 -1
- package/dist/shared/{nuxt-users.BTVH9EyH.d.ts → nuxt-users.SXaMnaEL.d.ts} +45 -1
- package/dist/utils.d.mts +1 -1
- package/dist/utils.d.ts +1 -1
- package/package.json +8 -6
- package/scripts/check-peer-dependencies.js +0 -0
- package/scripts/post-build.js +0 -0
|
@@ -32,6 +32,9 @@ export const registerUser = async (userData, options, baseUrl) => {
|
|
|
32
32
|
throw new Error("Failed to create user.");
|
|
33
33
|
}
|
|
34
34
|
const user = result.rows[0];
|
|
35
|
+
if (!user) {
|
|
36
|
+
throw new Error("Failed to retrieve created user.");
|
|
37
|
+
}
|
|
35
38
|
const passwordResetTokensTable = options.tables.passwordResetTokens;
|
|
36
39
|
await db.sql`
|
|
37
40
|
INSERT INTO {${passwordResetTokensTable}} (email, token, created_at)
|
|
@@ -149,8 +152,16 @@ export const confirmUserEmail = async (token, email, options) => {
|
|
|
149
152
|
const currentTimeString = now.toISOString().slice(0, 19).replace("T", " ");
|
|
150
153
|
const createdAtString = validTokenRecord.created_at instanceof Date ? validTokenRecord.created_at.toISOString().slice(0, 19).replace("T", " ") : String(validTokenRecord.created_at);
|
|
151
154
|
const [datePart, timePart] = createdAtString.split(/[ T]/);
|
|
155
|
+
if (!datePart || !timePart) {
|
|
156
|
+
console.log(`[Nuxt Users] Invalid timestamp format for token: ${createdAtString}`);
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
152
159
|
const [year, month, day] = datePart.split("-").map(Number);
|
|
153
160
|
const [hour, minute, second] = timePart.split(":").map(Number);
|
|
161
|
+
if (!year || !month || !day || hour === void 0 || minute === void 0 || second === void 0) {
|
|
162
|
+
console.log("[Nuxt Users] Invalid timestamp components");
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
154
165
|
let expirationHour = hour + TOKEN_EXPIRATION_HOURS;
|
|
155
166
|
let expirationDay = day;
|
|
156
167
|
let expirationMonth = month;
|
|
@@ -162,7 +173,9 @@ export const confirmUserEmail = async (token, email, options) => {
|
|
|
162
173
|
if (expirationYear % 4 === 0 && (expirationYear % 100 !== 0 || expirationYear % 400 === 0)) {
|
|
163
174
|
daysInMonth[1] = 29;
|
|
164
175
|
}
|
|
165
|
-
|
|
176
|
+
const monthIndex = expirationMonth - 1;
|
|
177
|
+
const daysInCurrentMonth = daysInMonth[monthIndex];
|
|
178
|
+
if (daysInCurrentMonth && expirationDay > daysInCurrentMonth) {
|
|
166
179
|
expirationDay = 1;
|
|
167
180
|
expirationMonth++;
|
|
168
181
|
if (expirationMonth > 12) {
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { useDb } from "./db.js";
|
|
2
|
+
export const addGoogleOauthFields = async (options) => {
|
|
3
|
+
const connectorName = options.connector.name;
|
|
4
|
+
const db = await useDb(options);
|
|
5
|
+
const tableName = options.tables.users;
|
|
6
|
+
console.log(`[Nuxt Users] DB:Add Google OAuth fields to ${connectorName} Users Table in ${tableName}...`);
|
|
7
|
+
if (connectorName === "sqlite" || connectorName === "mysql" || connectorName === "postgresql") {
|
|
8
|
+
try {
|
|
9
|
+
await db.sql`ALTER TABLE {${tableName}} ADD COLUMN google_id TEXT`;
|
|
10
|
+
console.log("[Nuxt Users] Added google_id column \u2705");
|
|
11
|
+
} catch (error) {
|
|
12
|
+
console.error("[Nuxt Users] google_id column might already exist", error);
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
await db.sql`ALTER TABLE {${tableName}} ADD COLUMN profile_picture TEXT`;
|
|
16
|
+
console.log("[Nuxt Users] Added profile_picture column \u2705");
|
|
17
|
+
} catch (error) {
|
|
18
|
+
console.error("[Nuxt Users] profile_picture column might already exist", error);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
console.log(`[Nuxt Users] DB:Add Google OAuth fields to ${connectorName} Users Table \u2705`);
|
|
22
|
+
};
|
|
@@ -12,6 +12,8 @@ export const createUsersTable = async (options) => {
|
|
|
12
12
|
name TEXT NOT NULL,
|
|
13
13
|
password TEXT NOT NULL,
|
|
14
14
|
role TEXT NOT NULL DEFAULT 'user',
|
|
15
|
+
google_id TEXT UNIQUE,
|
|
16
|
+
profile_picture TEXT,
|
|
15
17
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
16
18
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
17
19
|
)
|
|
@@ -25,6 +27,8 @@ export const createUsersTable = async (options) => {
|
|
|
25
27
|
name VARCHAR(255) NOT NULL,
|
|
26
28
|
password VARCHAR(255) NOT NULL,
|
|
27
29
|
role VARCHAR(32) NOT NULL DEFAULT 'user',
|
|
30
|
+
google_id VARCHAR(255) UNIQUE,
|
|
31
|
+
profile_picture TEXT,
|
|
28
32
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
29
33
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
30
34
|
)
|
|
@@ -38,10 +42,12 @@ export const createUsersTable = async (options) => {
|
|
|
38
42
|
name VARCHAR(255) NOT NULL,
|
|
39
43
|
password VARCHAR(255) NOT NULL,
|
|
40
44
|
role VARCHAR(32) NOT NULL DEFAULT 'user',
|
|
45
|
+
google_id VARCHAR(255) UNIQUE,
|
|
46
|
+
profile_picture TEXT,
|
|
41
47
|
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
|
42
48
|
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
|
43
49
|
)
|
|
44
50
|
`;
|
|
45
51
|
}
|
|
46
|
-
console.log(`[Nuxt Users] DB:Create ${connectorName} Users Table Fields: id, email, name, password, role, created_at, updated_at \u2705`);
|
|
52
|
+
console.log(`[Nuxt Users] DB:Create ${connectorName} Users Table Fields: id, email, name, password, role, google_id, profile_picture, created_at, updated_at \u2705`);
|
|
47
53
|
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { OAuth2Client } from 'google-auth-library';
|
|
2
|
+
import type { ModuleOptions, User, GoogleOAuthOptions } from 'nuxt-users/utils';
|
|
3
|
+
export interface GoogleUserInfo {
|
|
4
|
+
id: string;
|
|
5
|
+
email: string;
|
|
6
|
+
name: string;
|
|
7
|
+
picture?: string;
|
|
8
|
+
verified_email: boolean;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Create Google OAuth2 client
|
|
12
|
+
*/
|
|
13
|
+
export declare function createGoogleOAuth2Client(options: GoogleOAuthOptions, callbackUrl: string): OAuth2Client;
|
|
14
|
+
/**
|
|
15
|
+
* Generate authorization URL for Google OAuth
|
|
16
|
+
*/
|
|
17
|
+
export declare function getGoogleAuthUrl(oauth2Client: OAuth2Client, options: GoogleOAuthOptions): string;
|
|
18
|
+
/**
|
|
19
|
+
* Exchange authorization code for tokens and get user info
|
|
20
|
+
*/
|
|
21
|
+
export declare function getGoogleUserFromCode(oauth2Client: OAuth2Client, code: string): Promise<GoogleUserInfo>;
|
|
22
|
+
/**
|
|
23
|
+
* Generate a secure random password for OAuth users
|
|
24
|
+
*/
|
|
25
|
+
export declare function generateSecurePassword(): Promise<string>;
|
|
26
|
+
/**
|
|
27
|
+
* Find or create user from Google OAuth data
|
|
28
|
+
*/
|
|
29
|
+
export declare function findOrCreateGoogleUser(googleUser: GoogleUserInfo, moduleOptions: ModuleOptions): Promise<User | null>;
|
|
30
|
+
/**
|
|
31
|
+
* Create authentication token for user (reusing existing logic)
|
|
32
|
+
*/
|
|
33
|
+
export declare function createAuthTokenForUser(user: User, moduleOptions: ModuleOptions, rememberMe?: boolean): Promise<string>;
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { OAuth2Client } from "google-auth-library";
|
|
2
|
+
import crypto from "node:crypto";
|
|
3
|
+
import bcrypt from "bcrypt";
|
|
4
|
+
import { useDb } from "./db.js";
|
|
5
|
+
export function createGoogleOAuth2Client(options, callbackUrl) {
|
|
6
|
+
return new OAuth2Client(
|
|
7
|
+
options.clientId,
|
|
8
|
+
options.clientSecret,
|
|
9
|
+
callbackUrl
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
export function getGoogleAuthUrl(oauth2Client, options) {
|
|
13
|
+
const scopes = options.scopes || ["openid", "profile", "email"];
|
|
14
|
+
return oauth2Client.generateAuthUrl({
|
|
15
|
+
access_type: "offline",
|
|
16
|
+
scope: scopes,
|
|
17
|
+
include_granted_scopes: true
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
export async function getGoogleUserFromCode(oauth2Client, code) {
|
|
21
|
+
const { tokens } = await oauth2Client.getToken(code);
|
|
22
|
+
oauth2Client.setCredentials(tokens);
|
|
23
|
+
const response = await fetch("https://www.googleapis.com/oauth2/v2/userinfo", {
|
|
24
|
+
headers: {
|
|
25
|
+
Authorization: `Bearer ${tokens.access_token}`
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
if (!response.ok) {
|
|
29
|
+
throw new Error(`Failed to fetch user info: ${response.statusText}`);
|
|
30
|
+
}
|
|
31
|
+
const data = await response.json();
|
|
32
|
+
if (!data.email || !data.verified_email) {
|
|
33
|
+
throw new Error("Google account email not verified");
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
id: data.id,
|
|
37
|
+
email: data.email,
|
|
38
|
+
name: data.name || data.email.split("@")[0],
|
|
39
|
+
picture: data.picture || void 0,
|
|
40
|
+
verified_email: data.verified_email
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export async function generateSecurePassword() {
|
|
44
|
+
const randomBytes = crypto.randomBytes(32).toString("hex");
|
|
45
|
+
return await bcrypt.hash(`OAUTH_${randomBytes}`, 10);
|
|
46
|
+
}
|
|
47
|
+
export async function findOrCreateGoogleUser(googleUser, moduleOptions) {
|
|
48
|
+
const db = await useDb(moduleOptions);
|
|
49
|
+
const usersTable = moduleOptions.tables.users;
|
|
50
|
+
const allowAutoRegistration = moduleOptions.auth.google?.allowAutoRegistration ?? false;
|
|
51
|
+
let userResult = await db.sql`
|
|
52
|
+
SELECT * FROM {${usersTable}}
|
|
53
|
+
WHERE google_id = ${googleUser.id}
|
|
54
|
+
`;
|
|
55
|
+
if (userResult.rows.length > 0) {
|
|
56
|
+
const user = userResult.rows[0];
|
|
57
|
+
if (!user) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
if (user.profile_picture !== googleUser.picture) {
|
|
61
|
+
await db.sql`
|
|
62
|
+
UPDATE {${usersTable}}
|
|
63
|
+
SET profile_picture = ${googleUser.picture}, updated_at = CURRENT_TIMESTAMP
|
|
64
|
+
WHERE id = ${user.id}
|
|
65
|
+
`;
|
|
66
|
+
user.profile_picture = googleUser.picture;
|
|
67
|
+
}
|
|
68
|
+
return user;
|
|
69
|
+
}
|
|
70
|
+
userResult = await db.sql`
|
|
71
|
+
SELECT * FROM {${usersTable}}
|
|
72
|
+
WHERE email = ${googleUser.email}
|
|
73
|
+
`;
|
|
74
|
+
if (userResult.rows.length > 0) {
|
|
75
|
+
const user = userResult.rows[0];
|
|
76
|
+
if (!user) {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
await db.sql`
|
|
80
|
+
UPDATE {${usersTable}}
|
|
81
|
+
SET google_id = ${googleUser.id},
|
|
82
|
+
profile_picture = ${googleUser.picture},
|
|
83
|
+
updated_at = CURRENT_TIMESTAMP
|
|
84
|
+
WHERE id = ${user.id}
|
|
85
|
+
`;
|
|
86
|
+
user.google_id = googleUser.id;
|
|
87
|
+
user.profile_picture = googleUser.picture;
|
|
88
|
+
return user;
|
|
89
|
+
}
|
|
90
|
+
if (!allowAutoRegistration) {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
const securePassword = await generateSecurePassword();
|
|
94
|
+
await db.sql`
|
|
95
|
+
INSERT INTO {${usersTable}} (
|
|
96
|
+
email, name, password, role, google_id, profile_picture,
|
|
97
|
+
active, created_at, updated_at
|
|
98
|
+
) VALUES (
|
|
99
|
+
${googleUser.email},
|
|
100
|
+
${googleUser.name},
|
|
101
|
+
${securePassword},
|
|
102
|
+
'user',
|
|
103
|
+
${googleUser.id},
|
|
104
|
+
${googleUser.picture || null},
|
|
105
|
+
true,
|
|
106
|
+
CURRENT_TIMESTAMP,
|
|
107
|
+
CURRENT_TIMESTAMP
|
|
108
|
+
)
|
|
109
|
+
`;
|
|
110
|
+
const createdUserResult = await db.sql`
|
|
111
|
+
SELECT * FROM {${usersTable}}
|
|
112
|
+
WHERE google_id = ${googleUser.id}
|
|
113
|
+
`;
|
|
114
|
+
return createdUserResult.rows[0] ?? null;
|
|
115
|
+
}
|
|
116
|
+
export async function createAuthTokenForUser(user, moduleOptions, rememberMe = false) {
|
|
117
|
+
const db = await useDb(moduleOptions);
|
|
118
|
+
const personalAccessTokensTable = moduleOptions.tables.personalAccessTokens;
|
|
119
|
+
const token = crypto.randomBytes(64).toString("hex");
|
|
120
|
+
const tokenName = "oauth_auth_token";
|
|
121
|
+
const expiresAt = /* @__PURE__ */ new Date();
|
|
122
|
+
if (rememberMe) {
|
|
123
|
+
const longTermDays = moduleOptions.auth.rememberMeExpiration || 30;
|
|
124
|
+
expiresAt.setDate(expiresAt.getDate() + longTermDays);
|
|
125
|
+
} else {
|
|
126
|
+
expiresAt.setMinutes(expiresAt.getMinutes() + moduleOptions.auth.tokenExpiration);
|
|
127
|
+
}
|
|
128
|
+
await db.sql`
|
|
129
|
+
INSERT INTO {${personalAccessTokensTable}} (
|
|
130
|
+
tokenable_type, tokenable_id, name, token, expires_at, created_at, updated_at
|
|
131
|
+
) VALUES (
|
|
132
|
+
'user',
|
|
133
|
+
${user.id},
|
|
134
|
+
${tokenName},
|
|
135
|
+
${token},
|
|
136
|
+
${expiresAt.toISOString().slice(0, 19).replace("T", " ")},
|
|
137
|
+
CURRENT_TIMESTAMP,
|
|
138
|
+
CURRENT_TIMESTAMP
|
|
139
|
+
)
|
|
140
|
+
`;
|
|
141
|
+
return token;
|
|
142
|
+
}
|
|
@@ -6,3 +6,4 @@ export { createPersonalAccessTokensTable } from './create-personal-access-tokens
|
|
|
6
6
|
export { createPasswordResetTokensTable } from './create-password-reset-tokens-table.js';
|
|
7
7
|
export { createMigrationsTable } from './create-migrations-table.js';
|
|
8
8
|
export { runMigrations } from './migrate.js';
|
|
9
|
+
export { createGoogleOAuth2Client, getGoogleAuthUrl, getGoogleUserFromCode, findOrCreateGoogleUser, createAuthTokenForUser, generateSecurePassword } from './google-oauth.js';
|
|
@@ -6,3 +6,4 @@ export { createPersonalAccessTokensTable } from "./create-personal-access-tokens
|
|
|
6
6
|
export { createPasswordResetTokensTable } from "./create-password-reset-tokens-table.js";
|
|
7
7
|
export { createMigrationsTable } from "./create-migrations-table.js";
|
|
8
8
|
export { runMigrations } from "./migrate.js";
|
|
9
|
+
export { createGoogleOAuth2Client, getGoogleAuthUrl, getGoogleUserFromCode, findOrCreateGoogleUser, createAuthTokenForUser, generateSecurePassword } from "./google-oauth.js";
|
|
@@ -4,6 +4,7 @@ import { createPersonalAccessTokensTable } from "./create-personal-access-tokens
|
|
|
4
4
|
import { createPasswordResetTokensTable } from "./create-password-reset-tokens-table.js";
|
|
5
5
|
import { createMigrationsTable } from "./create-migrations-table.js";
|
|
6
6
|
import { addActiveToUsers } from "./add-active-to-users.js";
|
|
7
|
+
import { addGoogleOauthFields } from "./add-google-oauth-fields.js";
|
|
7
8
|
const migrations = [
|
|
8
9
|
{
|
|
9
10
|
name: "create_migrations_table",
|
|
@@ -24,6 +25,10 @@ const migrations = [
|
|
|
24
25
|
{
|
|
25
26
|
name: "add_active_to_users",
|
|
26
27
|
run: addActiveToUsers
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: "add_google_oauth_fields",
|
|
31
|
+
run: addGoogleOauthFields
|
|
27
32
|
}
|
|
28
33
|
];
|
|
29
34
|
export const getAppliedMigrations = async (options) => {
|
|
@@ -20,6 +20,9 @@ export const createUser = async (userData, options) => {
|
|
|
20
20
|
throw new Error("Failed to retrieve created user.");
|
|
21
21
|
}
|
|
22
22
|
const user = result.rows[0];
|
|
23
|
+
if (!user) {
|
|
24
|
+
throw new Error("Failed to retrieve created user.");
|
|
25
|
+
}
|
|
23
26
|
return {
|
|
24
27
|
id: user.id,
|
|
25
28
|
email: user.email,
|
|
@@ -38,6 +41,9 @@ export const findUserByEmail = async (email, options) => {
|
|
|
38
41
|
return null;
|
|
39
42
|
}
|
|
40
43
|
const user = result.rows[0];
|
|
44
|
+
if (!user) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
41
47
|
return {
|
|
42
48
|
id: user.id,
|
|
43
49
|
email: user.email,
|
|
@@ -57,6 +63,9 @@ export const findUserById = async (id, options, withPass = false) => {
|
|
|
57
63
|
return null;
|
|
58
64
|
}
|
|
59
65
|
const user = result.rows[0];
|
|
66
|
+
if (!user) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
60
69
|
const normalizedUser = {
|
|
61
70
|
...user,
|
|
62
71
|
created_at: typeof user.created_at === "object" ? user.created_at.toISOString() : user.created_at,
|
|
@@ -126,7 +135,8 @@ export const hasAnyUsers = async (options) => {
|
|
|
126
135
|
try {
|
|
127
136
|
const db = await useDb(options);
|
|
128
137
|
const users = await db.sql`SELECT COUNT(*) as count FROM {${options.tables.users}}`;
|
|
129
|
-
|
|
138
|
+
const firstRow = users.rows?.[0];
|
|
139
|
+
return firstRow ? firstRow.count > 0 : false;
|
|
130
140
|
} catch {
|
|
131
141
|
return false;
|
|
132
142
|
}
|
|
@@ -143,7 +153,11 @@ export const getCurrentUserFromToken = async (token, options, withPass = false)
|
|
|
143
153
|
if (tokenResult.rows.length === 0) {
|
|
144
154
|
return null;
|
|
145
155
|
}
|
|
146
|
-
const
|
|
156
|
+
const tokenRow = tokenResult.rows[0];
|
|
157
|
+
if (!tokenRow) {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
const userId = tokenRow.tokenable_id;
|
|
147
161
|
await db.sql`
|
|
148
162
|
UPDATE {${personalAccessTokensTable}}
|
|
149
163
|
SET last_used_at = CURRENT_TIMESTAMP
|
|
@@ -157,6 +171,9 @@ export const getCurrentUserFromToken = async (token, options, withPass = false)
|
|
|
157
171
|
return null;
|
|
158
172
|
}
|
|
159
173
|
const user = userResult.rows[0];
|
|
174
|
+
if (!user) {
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
160
177
|
if (!user.active) {
|
|
161
178
|
return null;
|
|
162
179
|
}
|
|
@@ -217,6 +234,10 @@ export const getLastLoginTime = async (userId, options) => {
|
|
|
217
234
|
if (result.rows.length === 0) {
|
|
218
235
|
return null;
|
|
219
236
|
}
|
|
220
|
-
const
|
|
237
|
+
const resultRow = result.rows[0];
|
|
238
|
+
if (!resultRow) {
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
const lastLogin = resultRow.created_at;
|
|
221
242
|
return lastLogin instanceof Date ? lastLogin.toISOString() : lastLogin;
|
|
222
243
|
};
|
|
@@ -12,6 +12,42 @@ type DatabaseConfig = {
|
|
|
12
12
|
password?: string;
|
|
13
13
|
database?: string;
|
|
14
14
|
};
|
|
15
|
+
interface GoogleOAuthOptions {
|
|
16
|
+
/**
|
|
17
|
+
* Google OAuth client ID from Google Cloud Console
|
|
18
|
+
*/
|
|
19
|
+
clientId: string;
|
|
20
|
+
/**
|
|
21
|
+
* Google OAuth client secret from Google Cloud Console
|
|
22
|
+
*/
|
|
23
|
+
clientSecret: string;
|
|
24
|
+
/**
|
|
25
|
+
* Callback URL for Google OAuth (must match what's configured in Google Cloud Console)
|
|
26
|
+
* @default '/api/nuxt-users/auth/google/callback'
|
|
27
|
+
*/
|
|
28
|
+
callbackUrl?: string;
|
|
29
|
+
/**
|
|
30
|
+
* Redirect URL after successful authentication
|
|
31
|
+
* @default '/'
|
|
32
|
+
*/
|
|
33
|
+
successRedirect?: string;
|
|
34
|
+
/**
|
|
35
|
+
* Redirect URL after failed authentication
|
|
36
|
+
* @default '/login?error=oauth_failed'
|
|
37
|
+
*/
|
|
38
|
+
errorRedirect?: string;
|
|
39
|
+
/**
|
|
40
|
+
* Google OAuth scopes to request
|
|
41
|
+
* @default ['openid', 'profile', 'email']
|
|
42
|
+
*/
|
|
43
|
+
scopes?: string[];
|
|
44
|
+
/**
|
|
45
|
+
* Allow automatic user registration when logging in with Google for the first time
|
|
46
|
+
* If false, only existing users with matching email can log in with Google
|
|
47
|
+
* @default false
|
|
48
|
+
*/
|
|
49
|
+
allowAutoRegistration?: boolean;
|
|
50
|
+
}
|
|
15
51
|
interface RuntimeModuleOptions {
|
|
16
52
|
connector?: {
|
|
17
53
|
name: DatabaseType;
|
|
@@ -75,6 +111,11 @@ interface RuntimeModuleOptions {
|
|
|
75
111
|
* }
|
|
76
112
|
*/
|
|
77
113
|
permissions?: Record<string, (string | Permission)[]>;
|
|
114
|
+
/**
|
|
115
|
+
* Google OAuth configuration
|
|
116
|
+
* Enable Google OAuth login/registration
|
|
117
|
+
*/
|
|
118
|
+
google?: GoogleOAuthOptions;
|
|
78
119
|
};
|
|
79
120
|
/**
|
|
80
121
|
* Password validation configuration
|
|
@@ -139,6 +180,7 @@ interface ModuleOptions {
|
|
|
139
180
|
tokenExpiration: number;
|
|
140
181
|
rememberMeExpiration: number;
|
|
141
182
|
permissions: Record<string, (string | Permission)[]>;
|
|
183
|
+
google?: GoogleOAuthOptions;
|
|
142
184
|
};
|
|
143
185
|
passwordValidation: {
|
|
144
186
|
minLength: number;
|
|
@@ -169,6 +211,8 @@ interface User {
|
|
|
169
211
|
password: string;
|
|
170
212
|
role: string;
|
|
171
213
|
active: boolean;
|
|
214
|
+
google_id?: string;
|
|
215
|
+
profile_picture?: string;
|
|
172
216
|
created_at: string;
|
|
173
217
|
updated_at: string;
|
|
174
218
|
last_login_at?: string;
|
|
@@ -225,4 +269,4 @@ declare const defaultFieldLabels: {
|
|
|
225
269
|
};
|
|
226
270
|
|
|
227
271
|
export { defaultDisplayFields as h, defaultFieldLabels as i };
|
|
228
|
-
export type { DisplayFieldsProps as D, LoginFormData as L, ModuleOptions as M, PersonalAccessToken as P, RuntimeModuleOptions as R, User as U, UserWithoutPassword as a, PasswordResetToken as b, LoginFormProps as c, ResetPasswordFormProps as d, Permission as e, DatabaseType as f, DatabaseConfig as g };
|
|
272
|
+
export type { DisplayFieldsProps as D, GoogleOAuthOptions as G, LoginFormData as L, ModuleOptions as M, PersonalAccessToken as P, RuntimeModuleOptions as R, User as U, UserWithoutPassword as a, PasswordResetToken as b, LoginFormProps as c, ResetPasswordFormProps as d, Permission as e, DatabaseType as f, DatabaseConfig as g };
|
|
@@ -12,6 +12,42 @@ type DatabaseConfig = {
|
|
|
12
12
|
password?: string;
|
|
13
13
|
database?: string;
|
|
14
14
|
};
|
|
15
|
+
interface GoogleOAuthOptions {
|
|
16
|
+
/**
|
|
17
|
+
* Google OAuth client ID from Google Cloud Console
|
|
18
|
+
*/
|
|
19
|
+
clientId: string;
|
|
20
|
+
/**
|
|
21
|
+
* Google OAuth client secret from Google Cloud Console
|
|
22
|
+
*/
|
|
23
|
+
clientSecret: string;
|
|
24
|
+
/**
|
|
25
|
+
* Callback URL for Google OAuth (must match what's configured in Google Cloud Console)
|
|
26
|
+
* @default '/api/nuxt-users/auth/google/callback'
|
|
27
|
+
*/
|
|
28
|
+
callbackUrl?: string;
|
|
29
|
+
/**
|
|
30
|
+
* Redirect URL after successful authentication
|
|
31
|
+
* @default '/'
|
|
32
|
+
*/
|
|
33
|
+
successRedirect?: string;
|
|
34
|
+
/**
|
|
35
|
+
* Redirect URL after failed authentication
|
|
36
|
+
* @default '/login?error=oauth_failed'
|
|
37
|
+
*/
|
|
38
|
+
errorRedirect?: string;
|
|
39
|
+
/**
|
|
40
|
+
* Google OAuth scopes to request
|
|
41
|
+
* @default ['openid', 'profile', 'email']
|
|
42
|
+
*/
|
|
43
|
+
scopes?: string[];
|
|
44
|
+
/**
|
|
45
|
+
* Allow automatic user registration when logging in with Google for the first time
|
|
46
|
+
* If false, only existing users with matching email can log in with Google
|
|
47
|
+
* @default false
|
|
48
|
+
*/
|
|
49
|
+
allowAutoRegistration?: boolean;
|
|
50
|
+
}
|
|
15
51
|
interface RuntimeModuleOptions {
|
|
16
52
|
connector?: {
|
|
17
53
|
name: DatabaseType;
|
|
@@ -75,6 +111,11 @@ interface RuntimeModuleOptions {
|
|
|
75
111
|
* }
|
|
76
112
|
*/
|
|
77
113
|
permissions?: Record<string, (string | Permission)[]>;
|
|
114
|
+
/**
|
|
115
|
+
* Google OAuth configuration
|
|
116
|
+
* Enable Google OAuth login/registration
|
|
117
|
+
*/
|
|
118
|
+
google?: GoogleOAuthOptions;
|
|
78
119
|
};
|
|
79
120
|
/**
|
|
80
121
|
* Password validation configuration
|
|
@@ -139,6 +180,7 @@ interface ModuleOptions {
|
|
|
139
180
|
tokenExpiration: number;
|
|
140
181
|
rememberMeExpiration: number;
|
|
141
182
|
permissions: Record<string, (string | Permission)[]>;
|
|
183
|
+
google?: GoogleOAuthOptions;
|
|
142
184
|
};
|
|
143
185
|
passwordValidation: {
|
|
144
186
|
minLength: number;
|
|
@@ -169,6 +211,8 @@ interface User {
|
|
|
169
211
|
password: string;
|
|
170
212
|
role: string;
|
|
171
213
|
active: boolean;
|
|
214
|
+
google_id?: string;
|
|
215
|
+
profile_picture?: string;
|
|
172
216
|
created_at: string;
|
|
173
217
|
updated_at: string;
|
|
174
218
|
last_login_at?: string;
|
|
@@ -225,4 +269,4 @@ declare const defaultFieldLabels: {
|
|
|
225
269
|
};
|
|
226
270
|
|
|
227
271
|
export { defaultDisplayFields as h, defaultFieldLabels as i };
|
|
228
|
-
export type { DisplayFieldsProps as D, LoginFormData as L, ModuleOptions as M, PersonalAccessToken as P, RuntimeModuleOptions as R, User as U, UserWithoutPassword as a, PasswordResetToken as b, LoginFormProps as c, ResetPasswordFormProps as d, Permission as e, DatabaseType as f, DatabaseConfig as g };
|
|
272
|
+
export type { DisplayFieldsProps as D, GoogleOAuthOptions as G, LoginFormData as L, ModuleOptions as M, PersonalAccessToken as P, RuntimeModuleOptions as R, User as U, UserWithoutPassword as a, PasswordResetToken as b, LoginFormProps as c, ResetPasswordFormProps as d, Permission as e, DatabaseType as f, DatabaseConfig as g };
|
package/dist/utils.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { g as DatabaseConfig, f as DatabaseType, D as DisplayFieldsProps, L as LoginFormData, c as LoginFormProps, M as ModuleOptions, b as PasswordResetToken, e as Permission, P as PersonalAccessToken, d as ResetPasswordFormProps, R as RuntimeModuleOptions, U as User, a as UserWithoutPassword, h as defaultDisplayFields, i as defaultFieldLabels } from './shared/nuxt-users.
|
|
1
|
+
export { g as DatabaseConfig, f as DatabaseType, D as DisplayFieldsProps, G as GoogleOAuthOptions, L as LoginFormData, c as LoginFormProps, M as ModuleOptions, b as PasswordResetToken, e as Permission, P as PersonalAccessToken, d as ResetPasswordFormProps, R as RuntimeModuleOptions, U as User, a as UserWithoutPassword, h as defaultDisplayFields, i as defaultFieldLabels } from './shared/nuxt-users.SXaMnaEL.mjs';
|
|
2
2
|
|
|
3
3
|
interface PasswordValidationResult {
|
|
4
4
|
isValid: boolean;
|
package/dist/utils.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { g as DatabaseConfig, f as DatabaseType, D as DisplayFieldsProps, L as LoginFormData, c as LoginFormProps, M as ModuleOptions, b as PasswordResetToken, e as Permission, P as PersonalAccessToken, d as ResetPasswordFormProps, R as RuntimeModuleOptions, U as User, a as UserWithoutPassword, h as defaultDisplayFields, i as defaultFieldLabels } from './shared/nuxt-users.
|
|
1
|
+
export { g as DatabaseConfig, f as DatabaseType, D as DisplayFieldsProps, G as GoogleOAuthOptions, L as LoginFormData, c as LoginFormProps, M as ModuleOptions, b as PasswordResetToken, e as Permission, P as PersonalAccessToken, d as ResetPasswordFormProps, R as RuntimeModuleOptions, U as User, a as UserWithoutPassword, h as defaultDisplayFields, i as defaultFieldLabels } from './shared/nuxt-users.SXaMnaEL.js';
|
|
2
2
|
|
|
3
3
|
interface PasswordValidationResult {
|
|
4
4
|
isValid: boolean;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nuxt-users",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.36.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",
|
|
@@ -86,12 +86,13 @@
|
|
|
86
86
|
"docs:dev": "vitepress dev docs",
|
|
87
87
|
"docs:build": "vitepress build docs",
|
|
88
88
|
"docs:preview": "vitepress preview docs",
|
|
89
|
-
"docs:release": "yarn docs:build && ./scripts/deploy-docs.sh"
|
|
89
|
+
"docs:release": "date +%Y%m%d%H%M%S > docs/.lastRelease && yarn docs:build && ./scripts/deploy-docs.sh"
|
|
90
90
|
},
|
|
91
91
|
"dependencies": {
|
|
92
92
|
"bcrypt": "^6.0.0",
|
|
93
93
|
"citty": "^0.1.6",
|
|
94
94
|
"defu": "^6.1.4",
|
|
95
|
+
"google-auth-library": "^10.4.0",
|
|
95
96
|
"nodemailer": "^7.0.5"
|
|
96
97
|
},
|
|
97
98
|
"optionalDependencies": {
|
|
@@ -113,22 +114,23 @@
|
|
|
113
114
|
"devDependencies": {
|
|
114
115
|
"@nuxt/devtools": "^2.6.2",
|
|
115
116
|
"@nuxt/eslint-config": "^1.5.2",
|
|
116
|
-
"@nuxt/kit": "^
|
|
117
|
+
"@nuxt/kit": "^4.0.0",
|
|
117
118
|
"@nuxt/module-builder": "^1.0.2",
|
|
118
|
-
"@nuxt/schema": "^
|
|
119
|
+
"@nuxt/schema": "^4.0.0",
|
|
119
120
|
"@nuxt/test-utils": "^3.19.2",
|
|
121
|
+
"@nuxt/vite-builder": "^4.2.0",
|
|
120
122
|
"@types/bcrypt": "^6.0.0",
|
|
121
123
|
"@types/node": "latest",
|
|
122
124
|
"@types/nodemailer": "^6.4.17",
|
|
123
125
|
"@types/pg": "^8.15.4",
|
|
124
126
|
"changelogen": "^0.6.2",
|
|
125
127
|
"eslint": "^9.30.1",
|
|
126
|
-
"nuxt": "^
|
|
128
|
+
"nuxt": "^4.0.0",
|
|
127
129
|
"tsx": "^4.20.3",
|
|
128
130
|
"typescript": "~5.9.0",
|
|
129
131
|
"unbuild": "^3.0.0",
|
|
130
132
|
"vitepress": "^1.6.3",
|
|
131
|
-
"vitest": "^
|
|
133
|
+
"vitest": "^4.0.0",
|
|
132
134
|
"vue-tsc": "^3.0.1"
|
|
133
135
|
}
|
|
134
136
|
}
|
|
File without changes
|
package/scripts/post-build.js
CHANGED
|
File without changes
|