- Signing in...
+ Zaloguj...
) : (
"Sign in"
diff --git a/src/components/ProjectForm.js b/src/components/ProjectForm.js
index 492b7dd..788bdc9 100644
--- a/src/components/ProjectForm.js
+++ b/src/components/ProjectForm.js
@@ -178,7 +178,7 @@ export default function ProjectForm({ initialData = null }) {
{users.map((user) => (
))}
diff --git a/src/lib/auth.js b/src/lib/auth.js
index d906e92..35cb9fb 100644
--- a/src/lib/auth.js
+++ b/src/lib/auth.js
@@ -4,7 +4,7 @@ import bcrypt from "bcryptjs";
import { z } from "zod";
const loginSchema = z.object({
- email: z.string().email("Invalid email format"),
+ username: z.string().min(1, "Username is required"),
password: z.string().min(6, "Password must be at least 6 characters"),
});
@@ -13,7 +13,7 @@ export const { handlers, auth, signIn, signOut } = NextAuth({
Credentials({
name: "credentials",
credentials: {
- email: { label: "Email", type: "email" },
+ username: { label: "Username", type: "text" },
password: { label: "Password", type: "password" },
},
async authorize(credentials) {
@@ -28,13 +28,13 @@ export const { handlers, auth, signIn, signOut } = NextAuth({
const user = db
.prepare(
`
- SELECT id, email, name, password_hash, role, is_active,
+ SELECT id, username, name, password_hash, role, is_active,
failed_login_attempts, locked_until
FROM users
- WHERE email = ? AND is_active = 1
+ WHERE username = ? AND is_active = 1
`
)
- .get(validatedFields.email);
+ .get(validatedFields.username);
if (!user) {
throw new Error("Invalid credentials");
@@ -75,7 +75,7 @@ export const { handlers, auth, signIn, signOut } = NextAuth({
userId: user.id,
resourceType: RESOURCE_TYPES.SESSION,
details: {
- email: validatedFields.email,
+ username: validatedFields.username,
reason: "invalid_password",
failed_attempts: user.failed_login_attempts + 1,
},
@@ -107,7 +107,7 @@ export const { handlers, auth, signIn, signOut } = NextAuth({
userId: user.id,
resourceType: RESOURCE_TYPES.SESSION,
details: {
- email: user.email,
+ username: user.username,
role: user.role,
},
});
@@ -117,7 +117,7 @@ export const { handlers, auth, signIn, signOut } = NextAuth({
return {
id: user.id,
- email: user.email,
+ username: user.username,
name: user.name,
role: user.role,
};
@@ -128,30 +128,29 @@ export const { handlers, auth, signIn, signOut } = NextAuth({
},
}),
],
- session: {
- strategy: "jwt",
- maxAge: 30 * 24 * 60 * 60, // 30 days
- },
callbacks: {
async jwt({ token, user }) {
if (user) {
token.role = user.role;
- token.userId = user.id;
+ token.username = user.username;
}
return token;
},
async session({ session, token }) {
if (token) {
- session.user.id = token.userId;
+ session.user.id = token.sub;
session.user.role = token.role;
+ session.user.username = token.username;
}
return session;
},
},
pages: {
signIn: "/auth/signin",
- signOut: "/auth/signout",
- error: "/auth/error",
},
- debug: process.env.NODE_ENV === "development",
+ session: {
+ strategy: "jwt",
+ maxAge: 24 * 60 * 60, // 24 hours
+ },
+ secret: process.env.NEXTAUTH_SECRET,
});
diff --git a/src/lib/auth_backup.js b/src/lib/auth_backup.js
new file mode 100644
index 0000000..35cb9fb
--- /dev/null
+++ b/src/lib/auth_backup.js
@@ -0,0 +1,156 @@
+import NextAuth from "next-auth";
+import Credentials from "next-auth/providers/credentials";
+import bcrypt from "bcryptjs";
+import { z } from "zod";
+
+const loginSchema = z.object({
+ username: z.string().min(1, "Username is required"),
+ password: z.string().min(6, "Password must be at least 6 characters"),
+});
+
+export const { handlers, auth, signIn, signOut } = NextAuth({
+ providers: [
+ Credentials({
+ name: "credentials",
+ credentials: {
+ username: { label: "Username", type: "text" },
+ password: { label: "Password", type: "password" },
+ },
+ async authorize(credentials) {
+ try {
+ // Import database here to avoid edge runtime issues
+ const { default: db } = await import("./db.js");
+
+ // Validate input
+ const validatedFields = loginSchema.parse(credentials);
+
+ // Check if user exists and is active
+ const user = db
+ .prepare(
+ `
+ SELECT id, username, name, password_hash, role, is_active,
+ failed_login_attempts, locked_until
+ FROM users
+ WHERE username = ? AND is_active = 1
+ `
+ )
+ .get(validatedFields.username);
+
+ if (!user) {
+ throw new Error("Invalid credentials");
+ }
+
+ // Check if account is locked
+ if (user.locked_until && new Date(user.locked_until) > new Date()) {
+ throw new Error("Account temporarily locked");
+ }
+
+ // Verify password
+ const isValidPassword = await bcrypt.compare(
+ validatedFields.password,
+ user.password_hash
+ );
+
+ if (!isValidPassword) {
+ // Increment failed attempts
+ db.prepare(
+ `
+ UPDATE users
+ SET failed_login_attempts = failed_login_attempts + 1,
+ locked_until = CASE
+ WHEN failed_login_attempts >= 4
+ THEN datetime('now', '+15 minutes')
+ ELSE locked_until
+ END
+ WHERE id = ?
+ `
+ ).run(user.id);
+
+ // Log failed login attempt (only in Node.js runtime)
+ try {
+ const { logAuditEventSafe, AUDIT_ACTIONS, RESOURCE_TYPES } =
+ await import("./auditLogSafe.js");
+ await logAuditEventSafe({
+ action: AUDIT_ACTIONS.LOGIN_FAILED,
+ userId: user.id,
+ resourceType: RESOURCE_TYPES.SESSION,
+ details: {
+ username: validatedFields.username,
+ reason: "invalid_password",
+ failed_attempts: user.failed_login_attempts + 1,
+ },
+ });
+ } catch (auditError) {
+ console.error("Failed to log audit event:", auditError);
+ }
+
+ throw new Error("Invalid credentials");
+ }
+
+ // Reset failed attempts and update last login
+ db.prepare(
+ `
+ UPDATE users
+ SET failed_login_attempts = 0,
+ locked_until = NULL,
+ last_login = CURRENT_TIMESTAMP
+ WHERE id = ?
+ `
+ ).run(user.id);
+
+ // Log successful login (only in Node.js runtime)
+ try {
+ const { logAuditEventSafe, AUDIT_ACTIONS, RESOURCE_TYPES } =
+ await import("./auditLogSafe.js");
+ await logAuditEventSafe({
+ action: AUDIT_ACTIONS.LOGIN,
+ userId: user.id,
+ resourceType: RESOURCE_TYPES.SESSION,
+ details: {
+ username: user.username,
+ role: user.role,
+ },
+ });
+ } catch (auditError) {
+ console.error("Failed to log audit event:", auditError);
+ }
+
+ return {
+ id: user.id,
+ username: user.username,
+ name: user.name,
+ role: user.role,
+ };
+ } catch (error) {
+ console.error("Login error:", error);
+ return null;
+ }
+ },
+ }),
+ ],
+ callbacks: {
+ async jwt({ token, user }) {
+ if (user) {
+ token.role = user.role;
+ token.username = user.username;
+ }
+ return token;
+ },
+ async session({ session, token }) {
+ if (token) {
+ session.user.id = token.sub;
+ session.user.role = token.role;
+ session.user.username = token.username;
+ }
+ return session;
+ },
+ },
+ pages: {
+ signIn: "/auth/signin",
+ },
+ session: {
+ strategy: "jwt",
+ maxAge: 24 * 60 * 60, // 24 hours
+ },
+ secret: process.env.NEXTAUTH_SECRET,
+});
diff --git a/src/lib/init-db.js b/src/lib/init-db.js
index c8c87e0..d191011 100644
--- a/src/lib/init-db.js
+++ b/src/lib/init-db.js
@@ -273,7 +273,7 @@ export default function initializeDatabase() {
CREATE TABLE IF NOT EXISTS users (
id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
name TEXT NOT NULL,
- email TEXT UNIQUE NOT NULL,
+ username TEXT UNIQUE NOT NULL,
password_hash TEXT NOT NULL,
role TEXT CHECK(role IN ('admin', 'project_manager', 'user', 'read_only')) DEFAULT 'user',
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
@@ -309,9 +309,36 @@ export default function initializeDatabase() {
);
-- Create indexes for performance
- CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
+ CREATE INDEX IF NOT EXISTS idx_users_username ON users(username);
CREATE INDEX IF NOT EXISTS idx_sessions_token ON sessions(session_token);
CREATE INDEX IF NOT EXISTS idx_sessions_user ON sessions(user_id);
CREATE INDEX IF NOT EXISTS idx_audit_user_timestamp ON audit_logs(user_id, timestamp);
`);
+
+ // Migration: Add username column and migrate from email if needed
+ try {
+ // Check if username column exists
+ const columns = db.prepare("PRAGMA table_info(users)").all();
+ const hasUsername = columns.some(col => col.name === 'username');
+ const hasEmail = columns.some(col => col.name === 'email');
+
+ if (!hasUsername && hasEmail) {
+ // Add username column
+ db.exec(`ALTER TABLE users ADD COLUMN username TEXT;`);
+
+ // Migrate existing email data to username (for development/testing)
+ // In production, you might want to handle this differently
+ db.exec(`UPDATE users SET username = email WHERE username IS NULL;`);
+
+ // Create unique index on username
+ db.exec(`CREATE UNIQUE INDEX IF NOT EXISTS idx_users_username_unique ON users(username);`);
+
+ console.log("✅ Migrated users table from email to username");
+ } else if (!hasUsername) {
+ // If neither username nor email exists, something is wrong
+ console.warn("⚠️ Users table missing both username and email columns");
+ }
+ } catch (e) {
+ console.warn("Migration warning:", e.message);
+ }
}
diff --git a/src/lib/queries/projects.js b/src/lib/queries/projects.js
index 3839dc7..8206365 100644
--- a/src/lib/queries/projects.js
+++ b/src/lib/queries/projects.js
@@ -5,9 +5,9 @@ export function getAllProjects(contractId = null) {
SELECT
p.*,
creator.name as created_by_name,
- creator.email as created_by_email,
+ creator.username as created_by_username,
assignee.name as assigned_to_name,
- assignee.email as assigned_to_email
+ assignee.username as assigned_to_username
FROM projects p
LEFT JOIN users creator ON p.created_by = creator.id
LEFT JOIN users assignee ON p.assigned_to = assignee.id
@@ -30,9 +30,9 @@ export function getProjectById(id) {
SELECT
p.*,
creator.name as created_by_name,
- creator.email as created_by_email,
+ creator.username as created_by_username,
assignee.name as assigned_to_name,
- assignee.email as assigned_to_email
+ assignee.username as assigned_to_username
FROM projects p
LEFT JOIN users creator ON p.created_by = creator.id
LEFT JOIN users assignee ON p.assigned_to = assignee.id
@@ -136,7 +136,7 @@ export function getAllUsersForAssignment() {
return db
.prepare(
`
- SELECT id, name, email, role
+ SELECT id, name, username, role
FROM users
WHERE is_active = 1
ORDER BY name
@@ -153,9 +153,9 @@ export function getProjectsByAssignedUser(userId) {
SELECT
p.*,
creator.name as created_by_name,
- creator.email as created_by_email,
+ creator.username as created_by_username,
assignee.name as assigned_to_name,
- assignee.email as assigned_to_email
+ assignee.username as assigned_to_username
FROM projects p
LEFT JOIN users creator ON p.created_by = creator.id
LEFT JOIN users assignee ON p.assigned_to = assignee.id
@@ -174,9 +174,9 @@ export function getProjectsByCreator(userId) {
SELECT
p.*,
creator.name as created_by_name,
- creator.email as created_by_email,
+ creator.username as created_by_username,
assignee.name as assigned_to_name,
- assignee.email as assigned_to_email
+ assignee.username as assigned_to_username
FROM projects p
LEFT JOIN users creator ON p.created_by = creator.id
LEFT JOIN users assignee ON p.assigned_to = assignee.id
@@ -224,7 +224,7 @@ export function getNotesForProject(projectId) {
`
SELECT n.*,
u.name as created_by_name,
- u.email as created_by_email
+ u.username as created_by_username
FROM notes n
LEFT JOIN users u ON n.created_by = u.id
WHERE n.project_id = ?
diff --git a/src/lib/queries/tasks.js b/src/lib/queries/tasks.js
index c9d1abf..1364dda 100644
--- a/src/lib/queries/tasks.js
+++ b/src/lib/queries/tasks.js
@@ -29,9 +29,9 @@ export function getAllProjectTasks() {
p.address,
p.finish_date,
creator.name as created_by_name,
- creator.email as created_by_email,
+ creator.username as created_by_username,
assignee.name as assigned_to_name,
- assignee.email as assigned_to_email
+ assignee.username as assigned_to_username
FROM project_tasks pt
LEFT JOIN tasks t ON pt.task_template_id = t.task_id
LEFT JOIN projects p ON pt.project_id = p.project_id
@@ -58,9 +58,9 @@ export function getProjectTasks(projectId) {
ELSE 'custom'
END as task_type,
creator.name as created_by_name,
- creator.email as created_by_email,
+ creator.username as created_by_username,
assignee.name as assigned_to_name,
- assignee.email as assigned_to_email
+ assignee.username as assigned_to_username
FROM project_tasks pt
LEFT JOIN tasks t ON pt.task_template_id = t.task_id
LEFT JOIN users creator ON pt.created_by = creator.id
@@ -222,9 +222,9 @@ export function getProjectTasksByAssignedUser(userId) {
p.address,
p.finish_date,
creator.name as created_by_name,
- creator.email as created_by_email,
+ creator.username as created_by_username,
assignee.name as assigned_to_name,
- assignee.email as assigned_to_email
+ assignee.username as assigned_to_username
FROM project_tasks pt
LEFT JOIN tasks t ON pt.task_template_id = t.task_id
LEFT JOIN projects p ON pt.project_id = p.project_id
@@ -258,9 +258,9 @@ export function getProjectTasksByCreator(userId) {
p.address,
p.finish_date,
creator.name as created_by_name,
- creator.email as created_by_email,
+ creator.username as created_by_username,
assignee.name as assigned_to_name,
- assignee.email as assigned_to_email
+ assignee.username as assigned_to_username
FROM project_tasks pt
LEFT JOIN tasks t ON pt.task_template_id = t.task_id
LEFT JOIN projects p ON pt.project_id = p.project_id
@@ -288,7 +288,7 @@ export function getAllUsersForTaskAssignment() {
return db
.prepare(
`
- SELECT id, name, email, role
+ SELECT id, name, username, role
FROM users
WHERE is_active = 1
ORDER BY name ASC
diff --git a/src/lib/userManagement.js b/src/lib/userManagement.js
index 8c139c1..04404a2 100644
--- a/src/lib/userManagement.js
+++ b/src/lib/userManagement.js
@@ -3,22 +3,22 @@ import bcrypt from "bcryptjs"
import { randomBytes } from "crypto"
// Create a new user
-export async function createUser({ name, email, password, role = 'user', is_active = true }) {
- const existingUser = db.prepare("SELECT id FROM users WHERE email = ?").get(email)
+export async function createUser({ name, username, password, role = 'user', is_active = true }) {
+ const existingUser = db.prepare("SELECT id FROM users WHERE username = ?").get(username)
if (existingUser) {
- throw new Error("User with this email already exists")
+ throw new Error("User with this username already exists")
}
const passwordHash = await bcrypt.hash(password, 12)
const userId = randomBytes(16).toString('hex')
const result = db.prepare(`
- INSERT INTO users (id, name, email, password_hash, role, is_active)
+ INSERT INTO users (id, name, username, password_hash, role, is_active)
VALUES (?, ?, ?, ?, ?, ?)
- `).run(userId, name, email, passwordHash, role, is_active ? 1 : 0)
+ `).run(userId, name, username, passwordHash, role, is_active ? 1 : 0)
return db.prepare(`
- SELECT id, name, email, role, created_at, updated_at, last_login,
+ SELECT id, name, username, role, created_at, updated_at, last_login,
is_active, failed_login_attempts, locked_until
FROM users WHERE id = ?
`).get(userId)
@@ -27,24 +27,24 @@ export async function createUser({ name, email, password, role = 'user', is_acti
// Get user by ID
export function getUserById(id) {
return db.prepare(`
- SELECT id, name, email, password_hash, role, created_at, updated_at, last_login,
+ SELECT id, name, username, password_hash, role, created_at, updated_at, last_login,
is_active, failed_login_attempts, locked_until
FROM users WHERE id = ?
`).get(id)
}
-// Get user by email
-export function getUserByEmail(email) {
+// Get user by username
+export function getUserByUsername(username) {
return db.prepare(`
- SELECT id, name, email, role, created_at, last_login, is_active
- FROM users WHERE email = ?
- `).get(email)
+ SELECT id, name, username, role, created_at, last_login, is_active
+ FROM users WHERE username = ?
+ `).get(username)
}
// Get all users (for admin)
export function getAllUsers() {
return db.prepare(`
- SELECT id, name, email, password_hash, role, created_at, updated_at, last_login, is_active,
+ SELECT id, name, username, password_hash, role, created_at, updated_at, last_login, is_active,
failed_login_attempts, locked_until
FROM users
ORDER BY created_at DESC
@@ -136,11 +136,11 @@ export async function updateUser(userId, updates) {
return null;
}
- // Check if email is being changed and if it already exists
- if (updates.email && updates.email !== user.email) {
- const existingUser = db.prepare("SELECT id FROM users WHERE email = ? AND id != ?").get(updates.email, userId);
+ // Check if username is being changed and if it already exists
+ if (updates.username && updates.username !== user.username) {
+ const existingUser = db.prepare("SELECT id FROM users WHERE username = ? AND id != ?").get(updates.username, userId);
if (existingUser) {
- throw new Error("User with this email already exists");
+ throw new Error("User with this username already exists");
}
}
@@ -153,9 +153,9 @@ export async function updateUser(userId, updates) {
updateValues.push(updates.name);
}
- if (updates.email !== undefined) {
- updateFields.push("email = ?");
- updateValues.push(updates.email);
+ if (updates.username !== undefined) {
+ updateFields.push("username = ?");
+ updateValues.push(updates.username);
}
if (updates.role !== undefined) {
@@ -198,7 +198,7 @@ export async function updateUser(userId, updates) {
if (result.changes > 0) {
return db.prepare(`
- SELECT id, name, email, role, created_at, updated_at, last_login,
+ SELECT id, name, username, role, created_at, updated_at, last_login,
is_active, failed_login_attempts, locked_until
FROM users WHERE id = ?
`).get(userId);
diff --git a/update-queries.ps1 b/update-queries.ps1
new file mode 100644
index 0000000..dbb9512
--- /dev/null
+++ b/update-queries.ps1
@@ -0,0 +1,20 @@
+$files = @(
+ "d:\panel\src\lib\queries\tasks.js",
+ "d:\panel\src\lib\userManagement.js"
+)
+
+foreach ($file in $files) {
+ if (Test-Path $file) {
+ Write-Host "Updating $file..."
+ $content = Get-Content $file -Raw
+ $content = $content -replace "creator\.email as created_by_email", "creator.username as created_by_username"
+ $content = $content -replace "assignee\.email as assigned_to_email", "assignee.username as assigned_to_username"
+ $content = $content -replace "u\.email as created_by_email", "u.username as created_by_username"
+ $content = $content -replace "SELECT id, name, email, role", "SELECT id, name, username, role"
+ $content = $content -replace "name, email, role", "name, username, role"
+ Set-Content $file $content -NoNewline
+ Write-Host "Updated $file"
+ }
+}
+
+Write-Host "All files updated!"