nuxt-users 1.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 +69 -0
- package/dist/module.d.mts +49 -0
- package/dist/module.json +9 -0
- package/dist/module.mjs +122 -0
- package/dist/runtime/components/ForgotPasswordForm.vue +79 -0
- package/dist/runtime/components/ForgotPasswordForm.vue.d.ts +2 -0
- package/dist/runtime/components/LoginForm.vue +133 -0
- package/dist/runtime/components/LoginForm.vue.d.ts +35 -0
- package/dist/runtime/components/ResetPasswordForm.vue +123 -0
- package/dist/runtime/components/ResetPasswordForm.vue.d.ts +2 -0
- package/dist/runtime/plugin.d.ts +2 -0
- package/dist/runtime/plugin.js +4 -0
- package/dist/runtime/server/api/auth/forgot-password.post.d.ts +4 -0
- package/dist/runtime/server/api/auth/forgot-password.post.js +26 -0
- package/dist/runtime/server/api/auth/reset-password.post.d.ts +4 -0
- package/dist/runtime/server/api/auth/reset-password.post.js +48 -0
- package/dist/runtime/server/api/login.post.d.ts +10 -0
- package/dist/runtime/server/api/login.post.js +57 -0
- package/dist/runtime/server/services/password.d.ts +14 -0
- package/dist/runtime/server/services/password.js +113 -0
- package/dist/runtime/server/tsconfig.json +3 -0
- package/dist/runtime/server/utils/create-migrations-table.d.ts +2 -0
- package/dist/runtime/server/utils/create-migrations-table.js +37 -0
- package/dist/runtime/server/utils/create-password-reset-tokens-table.d.ts +2 -0
- package/dist/runtime/server/utils/create-password-reset-tokens-table.js +42 -0
- package/dist/runtime/server/utils/create-personal-access-tokens-table.d.ts +2 -0
- package/dist/runtime/server/utils/create-personal-access-tokens-table.js +58 -0
- package/dist/runtime/server/utils/create-user.d.ts +1 -0
- package/dist/runtime/server/utils/create-user.js +1 -0
- package/dist/runtime/server/utils/create-users-table.d.ts +2 -0
- package/dist/runtime/server/utils/create-users-table.js +46 -0
- package/dist/runtime/server/utils/db.d.ts +7 -0
- package/dist/runtime/server/utils/db.js +45 -0
- package/dist/runtime/server/utils/migrate.d.ts +4 -0
- package/dist/runtime/server/utils/migrate.js +68 -0
- package/dist/runtime/server/utils/user.d.ts +21 -0
- package/dist/runtime/server/utils/user.js +57 -0
- package/dist/types.d.mts +7 -0
- package/package.json +82 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { defineEventHandler, readBody, createError, H3Error } from "h3";
|
|
2
|
+
import { resetPassword } from "../../services/password.js";
|
|
3
|
+
import { useRuntimeConfig } from "#imports";
|
|
4
|
+
export default defineEventHandler(async (event) => {
|
|
5
|
+
const body = await readBody(event);
|
|
6
|
+
const { token, email, password, password_confirmation } = body;
|
|
7
|
+
if (!token || typeof token !== "string") {
|
|
8
|
+
throw createError({ statusCode: 400, statusMessage: "Token is required." });
|
|
9
|
+
}
|
|
10
|
+
if (!email || typeof email !== "string") {
|
|
11
|
+
throw createError({ statusCode: 400, statusMessage: "Email is required." });
|
|
12
|
+
}
|
|
13
|
+
if (!password || typeof password !== "string") {
|
|
14
|
+
throw createError({ statusCode: 400, statusMessage: "Password is required." });
|
|
15
|
+
}
|
|
16
|
+
if (password !== password_confirmation) {
|
|
17
|
+
throw createError({ statusCode: 400, statusMessage: "Passwords do not match." });
|
|
18
|
+
}
|
|
19
|
+
if (password.length < 8) {
|
|
20
|
+
throw createError({ statusCode: 400, statusMessage: "Password must be at least 8 characters long." });
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
const options = useRuntimeConfig().nuxtUsers;
|
|
24
|
+
const success = await resetPassword(token, email, password, options);
|
|
25
|
+
if (success) {
|
|
26
|
+
return { message: "Password has been reset successfully. You can now log in with your new password." };
|
|
27
|
+
} else {
|
|
28
|
+
throw createError({
|
|
29
|
+
statusCode: 400,
|
|
30
|
+
// Or 422 if you prefer for semantic validation errors
|
|
31
|
+
statusMessage: "Invalid or expired token, or email mismatch. Please request a new password reset link."
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
} catch (error) {
|
|
35
|
+
if (error instanceof Error) {
|
|
36
|
+
console.error("Error in reset-password endpoint:", error.message);
|
|
37
|
+
} else {
|
|
38
|
+
console.error("Error in reset-password endpoint:", error);
|
|
39
|
+
}
|
|
40
|
+
if (error instanceof H3Error) {
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
throw createError({
|
|
44
|
+
statusCode: 500,
|
|
45
|
+
statusMessage: "An internal server error occurred."
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { createError, defineEventHandler, readBody, setCookie } from "h3";
|
|
2
|
+
import { getConnector } from "../utils/db.js";
|
|
3
|
+
import { createDatabase } from "db0";
|
|
4
|
+
import bcrypt from "bcrypt";
|
|
5
|
+
import crypto from "node:crypto";
|
|
6
|
+
import { useRuntimeConfig } from "#imports";
|
|
7
|
+
export default defineEventHandler(async (event) => {
|
|
8
|
+
const body = await readBody(event);
|
|
9
|
+
const { email, password } = body;
|
|
10
|
+
if (!email || !password) {
|
|
11
|
+
throw createError({
|
|
12
|
+
statusCode: 400,
|
|
13
|
+
statusMessage: "Email and password are required"
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
const { nuxtUsers } = useRuntimeConfig();
|
|
17
|
+
const options = nuxtUsers;
|
|
18
|
+
const connectorName = options.connector.name;
|
|
19
|
+
const connector = await getConnector(connectorName);
|
|
20
|
+
const db = createDatabase(connector(options.connector.options));
|
|
21
|
+
const usersTable = options.tables.users;
|
|
22
|
+
const personalAccessTokensTable = options.tables.personalAccessTokens;
|
|
23
|
+
const userResult = await db.sql`SELECT * FROM {${usersTable}} WHERE email = ${email}`;
|
|
24
|
+
if (userResult.rows.length === 0) {
|
|
25
|
+
throw createError({
|
|
26
|
+
statusCode: 401,
|
|
27
|
+
statusMessage: "Invalid email or password"
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
const user = userResult.rows[0];
|
|
31
|
+
const storedPassword = user.password;
|
|
32
|
+
const passwordMatch = await bcrypt.compare(password, storedPassword);
|
|
33
|
+
if (!passwordMatch) {
|
|
34
|
+
throw createError({
|
|
35
|
+
statusCode: 401,
|
|
36
|
+
statusMessage: "Invalid email or password"
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
const token = crypto.randomBytes(64).toString("hex");
|
|
40
|
+
const tokenName = "auth_token";
|
|
41
|
+
await db.sql`
|
|
42
|
+
INSERT INTO {${personalAccessTokensTable}} (tokenable_type, tokenable_id, name, token, created_at, updated_at)
|
|
43
|
+
VALUES ('user', ${user.id}, ${tokenName}, ${token}, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
|
|
44
|
+
`;
|
|
45
|
+
setCookie(event, "auth_token", token, {
|
|
46
|
+
httpOnly: true,
|
|
47
|
+
secure: process.env.NODE_ENV === "production",
|
|
48
|
+
// Use secure cookies in production
|
|
49
|
+
sameSite: "lax",
|
|
50
|
+
// Adjust as needed
|
|
51
|
+
maxAge: 60 * 60 * 24 * 7,
|
|
52
|
+
// Example: 7 days
|
|
53
|
+
path: "/"
|
|
54
|
+
});
|
|
55
|
+
const { password: _, ...userWithoutPassword } = user;
|
|
56
|
+
return { user: userWithoutPassword };
|
|
57
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ModuleOptions } from '../../../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Generates a password reset link and sends it to the user's email.
|
|
4
|
+
*/
|
|
5
|
+
export declare const sendPasswordResetLink: (email: string, options: ModuleOptions) => Promise<void>;
|
|
6
|
+
/**
|
|
7
|
+
* Resets the user's password using a valid token.
|
|
8
|
+
*/
|
|
9
|
+
export declare const resetPassword: (token: string, email: string, // Added email to quickly find the token
|
|
10
|
+
newPassword: string, options: ModuleOptions) => Promise<boolean>;
|
|
11
|
+
/**
|
|
12
|
+
* Deletes expired password reset tokens from the database.
|
|
13
|
+
*/
|
|
14
|
+
export declare const deleteExpiredPasswordResetTokens: (options: ModuleOptions) => Promise<void>;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { createTransport } from "nodemailer";
|
|
2
|
+
import crypto from "node:crypto";
|
|
3
|
+
import bcrypt from "bcrypt";
|
|
4
|
+
import { findUserByEmail, updateUserPassword } from "../utils/user.js";
|
|
5
|
+
import { getConnector } from "../utils/db.js";
|
|
6
|
+
import { createDatabase } from "db0";
|
|
7
|
+
const TOKEN_EXPIRATION_HOURS = 1;
|
|
8
|
+
export const sendPasswordResetLink = async (email, options) => {
|
|
9
|
+
const user = await findUserByEmail(email, options);
|
|
10
|
+
if (!user) {
|
|
11
|
+
console.log(`Password reset requested for non-existent email: ${email}`);
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
const connectorName = options.connector.name;
|
|
15
|
+
const connector = await getConnector(connectorName);
|
|
16
|
+
const db = createDatabase(connector(options.connector.options));
|
|
17
|
+
const token = crypto.randomBytes(32).toString("hex");
|
|
18
|
+
const hashedToken = await bcrypt.hash(token, 10);
|
|
19
|
+
const passwordResetTokensTable = options.tables.passwordResetTokens;
|
|
20
|
+
await db.sql`
|
|
21
|
+
INSERT INTO {${passwordResetTokensTable}} (email, token, created_at)
|
|
22
|
+
VALUES (${email}, ${hashedToken}, CURRENT_TIMESTAMP)
|
|
23
|
+
`;
|
|
24
|
+
if (!options.mailer) {
|
|
25
|
+
console.error("Mailer configuration is missing. Cannot send password reset email.");
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const transporter = createTransport({
|
|
29
|
+
host: options.mailer.host,
|
|
30
|
+
port: options.mailer.port,
|
|
31
|
+
secure: options.mailer.secure,
|
|
32
|
+
auth: {
|
|
33
|
+
user: options.mailer.auth.user,
|
|
34
|
+
pass: options.mailer.auth.pass
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
try {
|
|
38
|
+
await transporter.sendMail({
|
|
39
|
+
from: options.mailer.defaults?.from || '"Nuxt Users" <noreply@example.com>',
|
|
40
|
+
to: email,
|
|
41
|
+
subject: "Password Reset Request",
|
|
42
|
+
text: `Please click the following link to reset your password: ${options.passwordResetBaseUrl}`,
|
|
43
|
+
html: `<p>Please click the following link to reset your password:</p><p><a href="${options.passwordResetBaseUrl}">${options.passwordResetBaseUrl}</a></p><p>This link will expire in ${TOKEN_EXPIRATION_HOURS} hour(s).</p>`
|
|
44
|
+
});
|
|
45
|
+
console.log(`Password reset email sent to ${email}`);
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.error(`Failed to send password reset email to ${email}:`, error);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
export const resetPassword = async (token, email, newPassword, options) => {
|
|
51
|
+
const connectorName = options.connector.name;
|
|
52
|
+
const connector = await getConnector(connectorName);
|
|
53
|
+
const db = createDatabase(connector(options.connector.options));
|
|
54
|
+
const passwordResetTokensTable = options.tables.passwordResetTokens;
|
|
55
|
+
const tokenRecords = await db.sql`
|
|
56
|
+
SELECT * FROM {${passwordResetTokensTable}}
|
|
57
|
+
WHERE email = ${email}
|
|
58
|
+
ORDER BY created_at DESC
|
|
59
|
+
`;
|
|
60
|
+
if (tokenRecords.rows.length === 0) {
|
|
61
|
+
console.log(`No password reset tokens found for email: ${email}`);
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
let validTokenRecord = null;
|
|
65
|
+
for (const record of tokenRecords.rows) {
|
|
66
|
+
const tokenMatch = await bcrypt.compare(token, record.token);
|
|
67
|
+
if (tokenMatch) {
|
|
68
|
+
validTokenRecord = record;
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (!validTokenRecord || !validTokenRecord.created_at) {
|
|
73
|
+
console.log(`Invalid password reset token provided for email: ${email}`);
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
const now = /* @__PURE__ */ new Date();
|
|
77
|
+
const currentTimeString = now.toISOString().slice(0, 19).replace("T", " ");
|
|
78
|
+
const [datePart, timePart] = validTokenRecord.created_at.split(/[ T]/);
|
|
79
|
+
const [year, month, day] = datePart.split("-").map(Number);
|
|
80
|
+
const [hour, minute, second] = timePart.split(":").map(Number);
|
|
81
|
+
let expirationHour = hour + TOKEN_EXPIRATION_HOURS;
|
|
82
|
+
let expirationDay = day;
|
|
83
|
+
const expirationMonth = month;
|
|
84
|
+
const expirationYear = year;
|
|
85
|
+
if (expirationHour >= 24) {
|
|
86
|
+
expirationHour -= 24;
|
|
87
|
+
expirationDay++;
|
|
88
|
+
}
|
|
89
|
+
const expirationTimeString = `${expirationYear.toString().padStart(4, "0")}-${expirationMonth.toString().padStart(2, "0")}-${expirationDay.toString().padStart(2, "0")} ${expirationHour.toString().padStart(2, "0")}:${minute.toString().padStart(2, "0")}:${second.toString().padStart(2, "0")}`;
|
|
90
|
+
if (currentTimeString > expirationTimeString) {
|
|
91
|
+
console.log(`Expired password reset token for email: ${email}`);
|
|
92
|
+
await db.sql`DELETE FROM {${passwordResetTokensTable}} WHERE id = ${validTokenRecord.id}`;
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
await updateUserPassword(email, newPassword, options);
|
|
96
|
+
await db.sql`DELETE FROM {${passwordResetTokensTable}} WHERE email = ${email}`;
|
|
97
|
+
console.log(`Password reset successful for email: ${email}`);
|
|
98
|
+
return true;
|
|
99
|
+
};
|
|
100
|
+
export const deleteExpiredPasswordResetTokens = async (options) => {
|
|
101
|
+
const connectorName = options.connector.name;
|
|
102
|
+
const connector = await getConnector(connectorName);
|
|
103
|
+
const db = createDatabase(connector(options.connector.options));
|
|
104
|
+
const passwordResetTokensTable = options.tables.passwordResetTokens;
|
|
105
|
+
const expirationDate = /* @__PURE__ */ new Date();
|
|
106
|
+
expirationDate.setHours(expirationDate.getHours() - TOKEN_EXPIRATION_HOURS);
|
|
107
|
+
const expirationDateString = expirationDate.toISOString();
|
|
108
|
+
await db.sql`
|
|
109
|
+
DELETE FROM {${passwordResetTokensTable}}
|
|
110
|
+
WHERE created_at < ${expirationDateString}
|
|
111
|
+
`;
|
|
112
|
+
console.log(`Expired password reset tokens older than ${expirationDateString} deleted.`);
|
|
113
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { createDatabase } from "db0";
|
|
2
|
+
import { getConnector } from "./db.js";
|
|
3
|
+
export const createMigrationsTable = async (options) => {
|
|
4
|
+
const connectorName = options.connector.name;
|
|
5
|
+
const connector = await getConnector(connectorName);
|
|
6
|
+
const db = createDatabase(connector(options.connector.options));
|
|
7
|
+
const tableName = "migrations";
|
|
8
|
+
console.log(`[DB:Create Migrations ${connectorName} Table] Creating ${tableName} table with ${connectorName} connector...`);
|
|
9
|
+
if (connectorName === "sqlite") {
|
|
10
|
+
await db.sql`
|
|
11
|
+
CREATE TABLE IF NOT EXISTS {${tableName}} (
|
|
12
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
13
|
+
name TEXT NOT NULL UNIQUE,
|
|
14
|
+
executed_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
15
|
+
)
|
|
16
|
+
`;
|
|
17
|
+
}
|
|
18
|
+
if (connectorName === "mysql") {
|
|
19
|
+
await db.sql`
|
|
20
|
+
CREATE TABLE IF NOT EXISTS {${tableName}} (
|
|
21
|
+
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
22
|
+
name VARCHAR(255) NOT NULL UNIQUE,
|
|
23
|
+
executed_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
24
|
+
)
|
|
25
|
+
`;
|
|
26
|
+
}
|
|
27
|
+
if (connectorName === "postgresql") {
|
|
28
|
+
await db.sql`
|
|
29
|
+
CREATE TABLE IF NOT EXISTS {${tableName}} (
|
|
30
|
+
id SERIAL PRIMARY KEY,
|
|
31
|
+
name VARCHAR(255) NOT NULL UNIQUE,
|
|
32
|
+
executed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
33
|
+
)
|
|
34
|
+
`;
|
|
35
|
+
}
|
|
36
|
+
console.log(`[DB:Create Migrations ${connectorName} Table] successfull \u2705`);
|
|
37
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { createDatabase } from "db0";
|
|
2
|
+
import { getConnector } from "./db.js";
|
|
3
|
+
export const createPasswordResetTokensTable = async (options) => {
|
|
4
|
+
const connectorName = options.connector.name;
|
|
5
|
+
const connector = await getConnector(connectorName);
|
|
6
|
+
const db = createDatabase(connector(options.connector.options));
|
|
7
|
+
const tableName = options.tables.passwordResetTokens;
|
|
8
|
+
console.log(`[DB:Create Password Reset Tokens ${connectorName} Table] Creating ${tableName} table with ${connectorName} connector...`);
|
|
9
|
+
if (connectorName === "sqlite") {
|
|
10
|
+
await db.sql`
|
|
11
|
+
CREATE TABLE IF NOT EXISTS {${tableName}} (
|
|
12
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
13
|
+
email TEXT NOT NULL,
|
|
14
|
+
token TEXT NOT NULL UNIQUE,
|
|
15
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
16
|
+
)
|
|
17
|
+
`;
|
|
18
|
+
}
|
|
19
|
+
if (connectorName === "mysql") {
|
|
20
|
+
await db.sql`
|
|
21
|
+
CREATE TABLE IF NOT EXISTS {${tableName}} (
|
|
22
|
+
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
23
|
+
email VARCHAR(255) NOT NULL,
|
|
24
|
+
token VARCHAR(255) NOT NULL UNIQUE,
|
|
25
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
26
|
+
)
|
|
27
|
+
`;
|
|
28
|
+
}
|
|
29
|
+
if (connectorName === "postgresql") {
|
|
30
|
+
await db.sql`
|
|
31
|
+
CREATE TABLE IF NOT EXISTS {${tableName}} (
|
|
32
|
+
id SERIAL PRIMARY KEY,
|
|
33
|
+
email VARCHAR(255) NOT NULL,
|
|
34
|
+
token VARCHAR(255) NOT NULL UNIQUE,
|
|
35
|
+
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
|
36
|
+
)
|
|
37
|
+
`;
|
|
38
|
+
}
|
|
39
|
+
await db.sql`CREATE INDEX IF NOT EXISTS idx_password_reset_tokens_email ON {${tableName}} (email)`;
|
|
40
|
+
await db.sql`CREATE INDEX IF NOT EXISTS idx_password_reset_tokens_token ON {${tableName}} (token)`;
|
|
41
|
+
console.log(`[DB:Create Password Reset Tokens ${connectorName} Table] Fields: id, email, token, created_at \u2705`);
|
|
42
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { createDatabase } from "db0";
|
|
2
|
+
import { getConnector } from "./db.js";
|
|
3
|
+
export const createPersonalAccessTokensTable = async (options) => {
|
|
4
|
+
const connectorName = options.connector.name;
|
|
5
|
+
const connector = await getConnector(connectorName);
|
|
6
|
+
const db = createDatabase(connector(options.connector.options));
|
|
7
|
+
const tableName = options.tables.personalAccessTokens;
|
|
8
|
+
console.log(`[DB:Create Personal Access Tokens ${connectorName} Table] Creating ${tableName} table with ${connectorName} connector...`);
|
|
9
|
+
if (connectorName === "sqlite") {
|
|
10
|
+
await db.sql`
|
|
11
|
+
CREATE TABLE IF NOT EXISTS {${tableName}} (
|
|
12
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
13
|
+
tokenable_type TEXT NOT NULL,
|
|
14
|
+
tokenable_id INTEGER NOT NULL,
|
|
15
|
+
name TEXT NOT NULL,
|
|
16
|
+
token TEXT NOT NULL UNIQUE,
|
|
17
|
+
abilities TEXT,
|
|
18
|
+
last_used_at DATETIME,
|
|
19
|
+
expires_at DATETIME,
|
|
20
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
21
|
+
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
22
|
+
)
|
|
23
|
+
`;
|
|
24
|
+
}
|
|
25
|
+
if (connectorName === "mysql") {
|
|
26
|
+
await db.sql`
|
|
27
|
+
CREATE TABLE IF NOT EXISTS {${tableName}} (
|
|
28
|
+
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
29
|
+
tokenable_type VARCHAR(255) NOT NULL,
|
|
30
|
+
tokenable_id INT NOT NULL,
|
|
31
|
+
name VARCHAR(255) NOT NULL,
|
|
32
|
+
token VARCHAR(255) NOT NULL UNIQUE,
|
|
33
|
+
abilities TEXT,
|
|
34
|
+
last_used_at DATETIME,
|
|
35
|
+
expires_at DATETIME,
|
|
36
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
37
|
+
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
|
38
|
+
)
|
|
39
|
+
`;
|
|
40
|
+
}
|
|
41
|
+
if (connectorName === "postgresql") {
|
|
42
|
+
await db.sql`
|
|
43
|
+
CREATE TABLE IF NOT EXISTS {${tableName}} (
|
|
44
|
+
id SERIAL PRIMARY KEY,
|
|
45
|
+
tokenable_type VARCHAR(255) NOT NULL,
|
|
46
|
+
tokenable_id INT NOT NULL,
|
|
47
|
+
name VARCHAR(255) NOT NULL,
|
|
48
|
+
token VARCHAR(255) NOT NULL UNIQUE,
|
|
49
|
+
abilities TEXT,
|
|
50
|
+
last_used_at TIMESTAMPTZ,
|
|
51
|
+
expires_at TIMESTAMPTZ,
|
|
52
|
+
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
|
53
|
+
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
|
54
|
+
)
|
|
55
|
+
`;
|
|
56
|
+
}
|
|
57
|
+
console.log(`[DB:Create Personal Access Tokens ${connectorName} Table] Fields: id, tokenable_type, tokenable_id, name, token, abilities, last_used_at, expires_at, created_at, updated_at \u2705`);
|
|
58
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createUser } from './user.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createUser } from "./user.js";
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { createDatabase } from "db0";
|
|
2
|
+
import { getConnector } from "./db.js";
|
|
3
|
+
export const createUsersTable = async (options) => {
|
|
4
|
+
const connectorName = options.connector.name;
|
|
5
|
+
const connector = await getConnector(connectorName);
|
|
6
|
+
const db = createDatabase(connector(options.connector.options));
|
|
7
|
+
const tableName = options.tables.users;
|
|
8
|
+
console.log(`[DB:Create ${connectorName} Users Table] Creating ${tableName}...`);
|
|
9
|
+
if (connectorName === "sqlite") {
|
|
10
|
+
await db.sql`
|
|
11
|
+
CREATE TABLE IF NOT EXISTS {${tableName}} (
|
|
12
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
13
|
+
email TEXT NOT NULL UNIQUE,
|
|
14
|
+
name TEXT NOT NULL,
|
|
15
|
+
password TEXT NOT NULL,
|
|
16
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
17
|
+
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
18
|
+
)
|
|
19
|
+
`;
|
|
20
|
+
}
|
|
21
|
+
if (connectorName === "mysql") {
|
|
22
|
+
await db.sql`
|
|
23
|
+
CREATE TABLE IF NOT EXISTS {${tableName}} (
|
|
24
|
+
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
25
|
+
email VARCHAR(255) NOT NULL UNIQUE,
|
|
26
|
+
name VARCHAR(255) NOT NULL,
|
|
27
|
+
password VARCHAR(255) NOT NULL,
|
|
28
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
29
|
+
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
30
|
+
)
|
|
31
|
+
`;
|
|
32
|
+
}
|
|
33
|
+
if (connectorName === "postgresql") {
|
|
34
|
+
await db.sql`
|
|
35
|
+
CREATE TABLE IF NOT EXISTS {${tableName}} (
|
|
36
|
+
id SERIAL PRIMARY KEY,
|
|
37
|
+
email VARCHAR(255) NOT NULL UNIQUE,
|
|
38
|
+
name VARCHAR(255) NOT NULL,
|
|
39
|
+
password VARCHAR(255) NOT NULL,
|
|
40
|
+
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
|
41
|
+
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
|
42
|
+
)
|
|
43
|
+
`;
|
|
44
|
+
}
|
|
45
|
+
console.log(`[DB:Create ${connectorName} Users Table] Fields: id, email, name, password, created_at, updated_at \u2705`);
|
|
46
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ModuleOptions } from '../../../types.js';
|
|
2
|
+
export declare const getConnector: (name: string) => Promise<typeof import("db0/connectors/mysql2").default | typeof import("db0/connectors/postgresql").default | typeof import("db0/connectors/better-sqlite3").default>;
|
|
3
|
+
export declare const checkPasswordResetTokensTableExists: (options: ModuleOptions) => Promise<boolean>;
|
|
4
|
+
export declare const checkUsersTableExists: (options: ModuleOptions) => Promise<boolean>;
|
|
5
|
+
export declare const checkPersonalAccessTokensTableExists: (options: ModuleOptions) => Promise<boolean>;
|
|
6
|
+
export declare const checkTableExists: (options: ModuleOptions, tableName: string) => Promise<boolean>;
|
|
7
|
+
export declare const hasAnyUsers: (options: ModuleOptions) => Promise<boolean>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { createDatabase } from "db0";
|
|
2
|
+
export const getConnector = async (name) => {
|
|
3
|
+
switch (name) {
|
|
4
|
+
case "mysql":
|
|
5
|
+
return (await import("db0/connectors/mysql2")).default;
|
|
6
|
+
case "postgresql":
|
|
7
|
+
return (await import("db0/connectors/postgresql")).default;
|
|
8
|
+
case "sqlite":
|
|
9
|
+
return (await import("db0/connectors/better-sqlite3")).default;
|
|
10
|
+
default:
|
|
11
|
+
throw new Error(`Unsupported database connector: ${name}`);
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
const useDb = async (options) => {
|
|
15
|
+
const connectorName = options.connector.name;
|
|
16
|
+
const connector = await getConnector(connectorName);
|
|
17
|
+
return createDatabase(connector(options.connector.options));
|
|
18
|
+
};
|
|
19
|
+
export const checkPasswordResetTokensTableExists = async (options) => {
|
|
20
|
+
return await checkTableExists(options, options.tables.passwordResetTokens);
|
|
21
|
+
};
|
|
22
|
+
export const checkUsersTableExists = async (options) => {
|
|
23
|
+
return await checkTableExists(options, options.tables.users);
|
|
24
|
+
};
|
|
25
|
+
export const checkPersonalAccessTokensTableExists = async (options) => {
|
|
26
|
+
return await checkTableExists(options, options.tables.personalAccessTokens);
|
|
27
|
+
};
|
|
28
|
+
export const checkTableExists = async (options, tableName) => {
|
|
29
|
+
const db = await useDb(options);
|
|
30
|
+
try {
|
|
31
|
+
await db.sql`SELECT 1 FROM {${tableName}} LIMIT 1`;
|
|
32
|
+
return true;
|
|
33
|
+
} catch {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
export const hasAnyUsers = async (options) => {
|
|
38
|
+
const db = await useDb(options);
|
|
39
|
+
try {
|
|
40
|
+
const users = await db.sql`SELECT COUNT(*) as count FROM {${options.tables.users}}`;
|
|
41
|
+
return users.rows?.[0]?.count > 0;
|
|
42
|
+
} catch {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ModuleOptions } from '../../../types.js';
|
|
2
|
+
export declare const getAppliedMigrations: (options: ModuleOptions) => Promise<string[]>;
|
|
3
|
+
export declare const markMigrationAsApplied: (options: ModuleOptions, migrationName: string) => Promise<void>;
|
|
4
|
+
export declare const runMigrations: (options: ModuleOptions) => Promise<void>;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { createDatabase } from "db0";
|
|
2
|
+
import { getConnector } from "./db.js";
|
|
3
|
+
import { createUsersTable } from "./create-users-table.js";
|
|
4
|
+
import { createPersonalAccessTokensTable } from "./create-personal-access-tokens-table.js";
|
|
5
|
+
import { createPasswordResetTokensTable } from "./create-password-reset-tokens-table.js";
|
|
6
|
+
import { createMigrationsTable } from "./create-migrations-table.js";
|
|
7
|
+
const migrations = [
|
|
8
|
+
{
|
|
9
|
+
name: "create_migrations_table",
|
|
10
|
+
run: createMigrationsTable
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
name: "create_users_table",
|
|
14
|
+
run: createUsersTable
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
name: "create_personal_access_tokens_table",
|
|
18
|
+
run: createPersonalAccessTokensTable
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
name: "create_password_reset_tokens_table",
|
|
22
|
+
run: createPasswordResetTokensTable
|
|
23
|
+
}
|
|
24
|
+
];
|
|
25
|
+
export const getAppliedMigrations = async (options) => {
|
|
26
|
+
const connectorName = options.connector.name;
|
|
27
|
+
const connector = await getConnector(connectorName);
|
|
28
|
+
const db = createDatabase(connector(options.connector.options));
|
|
29
|
+
try {
|
|
30
|
+
const result = await db.sql`SELECT name FROM migrations ORDER BY id`;
|
|
31
|
+
return result.rows.map((row) => row.name);
|
|
32
|
+
} catch {
|
|
33
|
+
return [];
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
export const markMigrationAsApplied = async (options, migrationName) => {
|
|
37
|
+
const connectorName = options.connector.name;
|
|
38
|
+
const connector = await getConnector(connectorName);
|
|
39
|
+
const db = createDatabase(connector(options.connector.options));
|
|
40
|
+
await db.sql`INSERT INTO migrations (name) VALUES (${migrationName})`;
|
|
41
|
+
};
|
|
42
|
+
export const runMigrations = async (options) => {
|
|
43
|
+
console.log("[Nuxt Users] Starting migration system...");
|
|
44
|
+
await createMigrationsTable(options);
|
|
45
|
+
const appliedMigrations = await getAppliedMigrations(options);
|
|
46
|
+
console.log(`[Nuxt Users] Applied migrations: ${appliedMigrations.join(", ")}`);
|
|
47
|
+
const pendingMigrations = migrations.filter((migration) => !appliedMigrations.includes(migration.name));
|
|
48
|
+
if (pendingMigrations.length === 0) {
|
|
49
|
+
console.log("[Nuxt Users] No pending migrations to run.");
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
console.log(`[Nuxt Users] Found ${pendingMigrations.length} pending migrations:`);
|
|
53
|
+
pendingMigrations.forEach((migration) => {
|
|
54
|
+
console.log(` - ${migration.name}`);
|
|
55
|
+
});
|
|
56
|
+
for (const migration of pendingMigrations) {
|
|
57
|
+
console.log(`[Nuxt Users] Running migration: ${migration.name}`);
|
|
58
|
+
try {
|
|
59
|
+
await migration.run(options);
|
|
60
|
+
await markMigrationAsApplied(options, migration.name);
|
|
61
|
+
console.log(`[Nuxt Users] \u2713 Migration ${migration.name} successfull \u2705`);
|
|
62
|
+
} catch (error) {
|
|
63
|
+
console.error(`[Nuxt Users] \u2717 Migration ${migration.name} failed:`, error);
|
|
64
|
+
throw error;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
console.log("[Nuxt Users] All migrations successfull \u2705");
|
|
68
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ModuleOptions, User } from '../../../types.js';
|
|
2
|
+
interface CreateUserParams {
|
|
3
|
+
email: string;
|
|
4
|
+
name: string;
|
|
5
|
+
password: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Creates a new user in the database.
|
|
9
|
+
* Hashes the password before storing.
|
|
10
|
+
*/
|
|
11
|
+
export declare const createUser: (userData: CreateUserParams, options: ModuleOptions) => Promise<Omit<User, "password">>;
|
|
12
|
+
/**
|
|
13
|
+
* Finds a user by their email address.
|
|
14
|
+
*/
|
|
15
|
+
export declare const findUserByEmail: (email: string, options: ModuleOptions) => Promise<User | null>;
|
|
16
|
+
/**
|
|
17
|
+
* Updates a user's password.
|
|
18
|
+
* Hashes the new password before storing.
|
|
19
|
+
*/
|
|
20
|
+
export declare const updateUserPassword: (email: string, newPassword: string, options: ModuleOptions) => Promise<void>;
|
|
21
|
+
export {};
|