Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 0 additions & 10 deletions lecture-pulse/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion lecture-pulse/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
"dependencies": {
"@radix-ui/react-slot": "^1.2.4",
"@tailwindcss/vite": "^4.1.18",
"bcryptjs": "^3.0.3",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"framer-motion": "^12.23.26",
Expand Down
31 changes: 15 additions & 16 deletions lecture-pulse/src/context/AuthContext.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { createContext, useContext, useState } from "react";
import { getCurrentTeacher } from "@/utils/storage";
import bcrypt from "bcryptjs";

const AuthContext = createContext();

Expand All @@ -10,6 +9,16 @@ export function AuthProvider({ children }) {
});
const [loading] = useState(false);

const hashPassword = async (password) => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for addressing the plaintext password storage issue. The migration logic looks good, but I have one concern: using plain SHA-256 for password hashing isn't recommended for passwords, as it's designed to be fast and is vulnerable to brute-force attacks. A password hashing algorithm like bcrypt, Argon2, or PBKDF2 (available via the Web Crypto API) would provide much stronger protection. Please consider using one of these instead. Once updated, push the changes and mark this conversation as resolved.

const encoder = new TextEncoder();
const data = encoder.encode(password);
const hashBuffer = await crypto.subtle.digest("SHA-256", data);

return Array.from(new Uint8Array(hashBuffer))
.map((b) => b.toString(16).padStart(2, "0"))
.join("");
};

const login = async (teacherId, password) => {
// Hackathon Logic: Retrieve from array of teachers or single object?
// Let's assume we store a "teachers" object in LS: { [id]: { password, name } }
Expand All @@ -21,19 +30,13 @@ export function AuthProvider({ children }) {

let isValidPassword = false;
if (user) {
const isHashed =
typeof user.password === "string" &&
user.password.startsWith("$2");
if (isHashed) {
isValidPassword = await bcrypt.compare(
password,
user.password
);
const looksHashed = /^[a-f0-9]{64}$/i.test(user.password);
if (looksHashed) {
isValidPassword = user.password === await hashPassword(password);
} else {
isValidPassword = user.password === password;
// Auto-migrate plaintext passwords
if (isValidPassword) {
user.password = await bcrypt.hash(password, 10);
user.password = await hashPassword(password);
localStorage.setItem(
"lecturePulse_teachers_db",
JSON.stringify(teachers)
Expand Down Expand Up @@ -67,13 +70,9 @@ export function AuthProvider({ children }) {
return { success: false, message: "Teacher ID already exists." };
}

const hashedPassword = await bcrypt.hash(
password,
10
);
teachers[teacherId] = {
name,
password: hashedPassword,
password: await hashPassword(password),
};
localStorage.setItem("lecturePulse_teachers_db", JSON.stringify(teachers));

Expand Down
33 changes: 19 additions & 14 deletions lecture-pulse/src/pages/Login.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,24 +26,29 @@ export default function Login() {
if (!formData.teacherId.trim()) return setError("Teacher ID is required.");
if (!formData.password.trim()) return setError("Password is required.");
if (formData.password.length < 6) return setError("Password must be at least 6 characters long.");

setLoading(true);
await new Promise((resolve) => setTimeout(resolve, 800));
let result;
if (isLogin) {
result = await login(formData.teacherId, formData.password);
} else {
if (!formData.name) {
result = { success: false, message: "Name is required." };
try {
await new Promise((resolve) => setTimeout(resolve, 800));
let result;
if (isLogin) {
result = await login(formData.teacherId, formData.password);
} else {
result = await register(formData.name, formData.teacherId, formData.password);
if (!formData.name) {
result = { success: false, message: "Name is required." };
} else {
result = await register(formData.name, formData.teacherId, formData.password);
}
}

if (result.success) {
navigate("/dashboard");
} else {
setError(result.message);
}
} finally {
setLoading(false);
}
if (result.success) {
navigate("/dashboard");
} else {
setError(result.message);
}
setLoading(false);
};

return (
Expand Down