Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
2ba9a53
feat: implement user icon, login and signup UI; refs #111
elainefan331 Nov 18, 2025
bfa0ee9
feat: add login and logout functionalities; refs #111
elainefan331 Nov 18, 2025
e0bd72e
feat: add auth service and redux integration; refs #111
elainefan331 Nov 19, 2025
4afb588
feat: add logout functionality redux integration; refs #111
elainefan331 Nov 19, 2025
bf367ce
feat: integrate user signup feature into redux; refs #111
elainefan331 Nov 19, 2025
1d7c14a
feat: add oauth routes and controller, install and configure passport…
elainefan331 Nov 20, 2025
8f0c27d
adjust passport configure file for correct module import
elainefan331 Nov 21, 2025
314e4e9
feat: add sign in with google button; refs #111
elainefan331 Nov 21, 2025
5daa481
fix: add AuthHandler component to prevent cached login state from dis…
elainefan331 Nov 24, 2025
210e258
feat: create custom button for continue with google button; refs #111
elainefan331 Nov 24, 2025
fc4a533
feat: update user panel when user log in; refs #111
elainefan331 Nov 24, 2025
44bf3bc
fix: adjust app.tsx to listen to event persisted
elainefan331 Nov 25, 2025
2bb8f0c
feat: add email verification service file, modify user model and migr…
elainefan331 Dec 4, 2025
16db515
feat: add email verification controller to verify email
elainefan331 Dec 9, 2025
1492148
feat: add email verification routes
elainefan331 Dec 9, 2025
792fe9f
feat: create a verify email page
elainefan331 Dec 9, 2025
04fe697
feat: implement email verification workflow for user registration
elainefan331 Dec 12, 2025
ffa8166
feat: modify welcome email template
elainefan331 Dec 15, 2025
907ee4c
feat: add first name, last name, company, interests and password rese…
elainefan331 Dec 17, 2025
0a5d0ec
feat: updata sign up and add completeProfile controller
elainefan331 Dec 17, 2025
ab427b9
feat: add change password controller
elainefan331 Dec 17, 2025
e8e0420
feat: add forgotPassword controller
elainefan331 Dec 18, 2025
cbf3f2b
feat: add resetPassword controller
elainefan331 Dec 18, 2025
b3948c2
feat: add reset passoword email template to email service; add backen…
elainefan331 Dec 18, 2025
55f2c32
feat: add needs profile completion method in user model
elainefan331 Dec 18, 2025
7631c31
feat: modify google OAuth strategy and callback controller
elainefan331 Dec 22, 2025
e0b7a4c
feat: add complete profile component
elainefan331 Dec 22, 2025
a0d63e8
feat: add complete profile route for OAuth users
elainefan331 Dec 22, 2025
45019fc
modified backend url set up in complete profile component
elainefan331 Jan 3, 2026
36ee9fc
fix: correct redirect path after OAuth profile completion; closes #111
elainefan331 Jan 5, 2026
40a5ba0
fix the double path redirect issue after fill out the form
elainefan331 Jan 5, 2026
5450a88
update the value of REACT_APP_API_URL in yml file
elainefan331 Jan 5, 2026
f7cbaf7
update auth TypeScript interfaces
elainefan331 Jan 6, 2026
4645efc
update user sign up form to includes new fields
elainefan331 Jan 6, 2026
bab1f03
remove debug console.log
elainefan331 Jan 6, 2026
7147b3d
feat: add user profile tab
elainefan331 Jan 9, 2026
0a95bac
feat: add user dashboard page
elainefan331 Jan 9, 2026
6e30a44
feat: add change password functionality to user dashboard
elainefan331 Jan 9, 2026
bd8c10c
feat: add forgot password functionality
elainefan331 Jan 9, 2026
4d1e771
feat: modify theme color for user dashboard
elainefan331 Jan 12, 2026
e0a214f
fix: allow hybrid users(password + OAuth) to change password
elainefan331 Jan 13, 2026
5b09814
feat: add password validator function
elainefan331 Jan 14, 2026
06305b7
apply password validator to auth controllers
elainefan331 Jan 14, 2026
7fa9eaa
feat: add password strength indicator util and component
elainefan331 Jan 14, 2026
950b75e
feat: apply password validator UI in security tab, reset password and…
elainefan331 Jan 14, 2026
047916f
fix: add haspassword field to login controller
elainefan331 Jan 14, 2026
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
1 change: 1 addition & 0 deletions .env.development
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
REACT_APP_API_URL = http://localhost:5000/api/v1
5 changes: 4 additions & 1 deletion .github/workflows/build-deploy-neurojsonio.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ jobs:
- name: Build React App for production
run: |
echo "Building for production at root /"
PUBLIC_URL="/" yarn build
# PUBLIC_URL="/" yarn build
export PUBLIC_URL="/"
export REACT_APP_API_URL="/api/v1"
yarn build

- name: Copy JS libraries
run: |
Expand Down
5 changes: 4 additions & 1 deletion .github/workflows/build-deploy-zodiac.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@ jobs:
- name: Build React App with Dynamic PUBLIC_URL
run: |
echo "Building for /dev/${{ env.BRANCH_NAME }}/"
PUBLIC_URL="/dev/${{ env.BRANCH_NAME }}/" yarn build
# PUBLIC_URL="/dev/${{ env.BRANCH_NAME }}/" yarn build
export PUBLIC_URL="/dev/${{ env.BRANCH_NAME }}/"
export REACT_APP_API_URL="/dev/${{ env.BRANCH_NAME }}/api/v1"
yarn build

- name: Copy JS libraries (jdata, bjdata etc.)
run: |
Expand Down
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
.env
.env.local
.env.*.local
.env.production
node_modules
.DS_Store
package-lock.json
Expand All @@ -8,5 +11,8 @@ build
#backend
backend/node_modules/
backend/.env
backend/.env.local
backend/.env.*.local
backend/*.sqlite
backend/*.db
!backend/package-lock.json
160 changes: 160 additions & 0 deletions backend/config/passport.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
// backend/config/passport.config.js
const passport = require("passport");
const GoogleStrategy = require("passport-google-oauth20").Strategy;
// const { User } = require("../models");
const User = require("../src/models/User");

// Google OAuth Strategy
passport.use(
new GoogleStrategy(
{
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL:
process.env.GOOGLE_CALLBACK_URL ||
"http://localhost:5000/api/v1/auth/google/callback",
scope: ["profile", "email"],
},
async (accessToken, refreshToken, profile, done) => {
try {
// Extract user info from Google profile
const email = profile.emails[0].value;
const googleId = profile.id;
const username = profile.displayName || email.split("@")[0];

// NEW: Extract name from Google profile
const firstName = profile.name?.givenName || "";
const lastName = profile.name?.familyName || "";

// Check if user already exists with this Google ID
let user = await User.findOne({
where: { google_id: googleId },
});

if (user) {
// User exists, return user
return done(null, user);
}

// Check if user exists with this email (linking accounts)
user = await User.findOne({
where: { email },
});

if (user) {
// User exists with email but no Google ID - link the accounts
user.google_id = googleId;
// NEW: Update profile fields if they were empty
if (!user.first_name && firstName) user.first_name = firstName;
if (!user.last_name && lastName) user.last_name = lastName;
await user.save();
return done(null, user);
}

// Create new user
user = await User.create({
username: username,
email: email,
google_id: googleId,
hashed_password: null, // OAuth users don't have passwords
email_verified: true,
// Set from Google profile, or empty string if not available
first_name: firstName || "",
last_name: lastName || "",
company: "", // Always empty for new OAuth users - must be completed
});

return done(null, user);
} catch (error) {
console.error("Google OAuth error:", error);
return done(error, null);
}
}
)
);

// ORCID OAuth Strategy (using OAuth2Strategy as base)
const OAuth2Strategy = require("passport-oauth2");

// passport.use(
// "orcid",
// new OAuth2Strategy(
// {
// authorizationURL: "https://orcid.org/oauth/authorize",
// tokenURL: "https://orcid.org/oauth/token",
// clientID: process.env.ORCID_CLIENT_ID,
// clientSecret: process.env.ORCID_CLIENT_SECRET,
// callbackURL: process.env.ORCID_CALLBACK_URL || "http://localhost:5000/api/v1/auth/orcid/callback",
// scope: "/authenticate",
// },
// async (accessToken, refreshToken, params, profile, done) => {
// try {
// // ORCID returns user info in params, not profile
// const orcidId = params.orcid;
// const name = params.name || `ORCID User ${orcidId}`;

// // ORCID doesn't always provide email in the basic scope
// // You might need to make an additional API call to get email
// // For now, we'll use ORCID ID as identifier

// // Check if user exists with this ORCID ID
// let user = await User.findOne({
// where: { orcid_id: orcidId },
// });

// if (user) {
// return done(null, user);
// }

// // If we have email from ORCID, check for existing user
// if (params.email) {
// user = await User.findOne({
// where: { email: params.email },
// });

// if (user) {
// // Link ORCID to existing account
// user.orcid_id = orcidId;
// await user.save();
// return done(null, user);
// }
// }

// // Create new user
// // Note: ORCID might not provide email, so we use ORCID ID as part of email
// const email = params.email || `${orcidId}@orcid.placeholder`;
// const username = name.replace(/\s+/g, "_").toLowerCase() || `orcid_${orcidId}`;

// user = await User.create({
// username: username,
// email: email,
// orcid_id: orcidId,
// hashed_password: null,
// email_verified: true,
// });

// return done(null, user);
// } catch (error) {
// console.error("ORCID OAuth error:", error);
// return done(error, null);
// }
// }
// )
// );

// Serialize user for session
passport.serializeUser((user, done) => {
done(null, user.id);
});

// Deserialize user from session
passport.deserializeUser(async (id, done) => {
try {
const user = await User.findByPk(id);
done(null, user);
} catch (error) {
done(error, null);
}
});

module.exports = passport;
40 changes: 40 additions & 0 deletions backend/migrations/20251028184256-create-users.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,46 @@ module.exports = {
allowNull: false,
unique: true,
},
email_verified: {
type: Sequelize.BOOLEAN,
defaultValue: false,
allowNull: false,
},
verification_token: {
type: Sequelize.STRING(255),
allowNull: true,
unique: true,
},
verification_token_expires: {
type: Sequelize.DATE,
allowNull: true,
},
// NEW FIELDS FOR PASSWORD RESET
reset_password_token: {
type: Sequelize.STRING(255),
allowNull: true,
unique: true,
},
reset_password_expires: {
type: Sequelize.DATE,
allowNull: true,
},
first_name: {
type: Sequelize.STRING(255),
allowNull: false,
},
last_name: {
type: Sequelize.STRING(255),
allowNull: false,
},
company: {
type: Sequelize.STRING(255),
allowNull: false,
},
interests: {
type: Sequelize.TEXT,
allowNull: true,
},
created_at: {
type: Sequelize.DATE,
allowNull: false,
Expand Down
41 changes: 24 additions & 17 deletions backend/models/index.js
Original file line number Diff line number Diff line change
@@ -1,37 +1,44 @@
'use strict';
"use strict";

const fs = require('fs');
const path = require('path');
const Sequelize = require('sequelize');
const process = require('process');
const fs = require("fs");
const path = require("path");
const Sequelize = require("sequelize");
const process = require("process");
const basename = path.basename(__filename);
const env = process.env.NODE_ENV || 'development';
const config = require(__dirname + '/../config/config.json')[env];
const env = process.env.NODE_ENV || "development";
const config = require(__dirname + "/../config/config.js")[env];
const db = {};

let sequelize;
if (config.use_env_variable) {
sequelize = new Sequelize(process.env[config.use_env_variable], config);
} else {
sequelize = new Sequelize(config.database, config.username, config.password, config);
sequelize = new Sequelize(
config.database,
config.username,
config.password,
config
);
}

fs
.readdirSync(__dirname)
.filter(file => {
fs.readdirSync(__dirname)
.filter((file) => {
return (
file.indexOf('.') !== 0 &&
file.indexOf(".") !== 0 &&
file !== basename &&
file.slice(-3) === '.js' &&
file.indexOf('.test.js') === -1
file.slice(-3) === ".js" &&
file.indexOf(".test.js") === -1
);
})
.forEach(file => {
const model = require(path.join(__dirname, file))(sequelize, Sequelize.DataTypes);
.forEach((file) => {
const model = require(path.join(__dirname, file))(
sequelize,
Sequelize.DataTypes
);
db[model.name] = model;
});

Object.keys(db).forEach(modelName => {
Object.keys(db).forEach((modelName) => {
if (db[modelName].associate) {
db[modelName].associate(db);
}
Expand Down
Loading