Skip to content

Commit b4d4e91

Browse files
authored
Merge pull request #112 from NeuroJSON/dev-fan
Implement Sign Up, Login, Logout, and Authentication Flow
2 parents 4149012 + 047916f commit b4d4e91

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+5097
-321
lines changed

.env.development

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
REACT_APP_API_URL = http://localhost:5000/api/v1

.github/workflows/build-deploy-neurojsonio.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,10 @@ jobs:
2424
- name: Build React App for production
2525
run: |
2626
echo "Building for production at root /"
27-
PUBLIC_URL="/" yarn build
27+
# PUBLIC_URL="/" yarn build
28+
export PUBLIC_URL="/"
29+
export REACT_APP_API_URL="/api/v1"
30+
yarn build
2831
2932
- name: Copy JS libraries
3033
run: |

.github/workflows/build-deploy-zodiac.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,10 @@ jobs:
3333
- name: Build React App with Dynamic PUBLIC_URL
3434
run: |
3535
echo "Building for /dev/${{ env.BRANCH_NAME }}/"
36-
PUBLIC_URL="/dev/${{ env.BRANCH_NAME }}/" yarn build
36+
# PUBLIC_URL="/dev/${{ env.BRANCH_NAME }}/" yarn build
37+
export PUBLIC_URL="/dev/${{ env.BRANCH_NAME }}/"
38+
export REACT_APP_API_URL="/dev/${{ env.BRANCH_NAME }}/api/v1"
39+
yarn build
3740
3841
- name: Copy JS libraries (jdata, bjdata etc.)
3942
run: |

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
.env
2+
.env.local
3+
.env.*.local
4+
.env.production
25
node_modules
36
.DS_Store
47
package-lock.json
@@ -8,5 +11,8 @@ build
811
#backend
912
backend/node_modules/
1013
backend/.env
14+
backend/.env.local
15+
backend/.env.*.local
1116
backend/*.sqlite
17+
backend/*.db
1218
!backend/package-lock.json

backend/config/passport.config.js

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
// backend/config/passport.config.js
2+
const passport = require("passport");
3+
const GoogleStrategy = require("passport-google-oauth20").Strategy;
4+
// const { User } = require("../models");
5+
const User = require("../src/models/User");
6+
7+
// Google OAuth Strategy
8+
passport.use(
9+
new GoogleStrategy(
10+
{
11+
clientID: process.env.GOOGLE_CLIENT_ID,
12+
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
13+
callbackURL:
14+
process.env.GOOGLE_CALLBACK_URL ||
15+
"http://localhost:5000/api/v1/auth/google/callback",
16+
scope: ["profile", "email"],
17+
},
18+
async (accessToken, refreshToken, profile, done) => {
19+
try {
20+
// Extract user info from Google profile
21+
const email = profile.emails[0].value;
22+
const googleId = profile.id;
23+
const username = profile.displayName || email.split("@")[0];
24+
25+
// NEW: Extract name from Google profile
26+
const firstName = profile.name?.givenName || "";
27+
const lastName = profile.name?.familyName || "";
28+
29+
// Check if user already exists with this Google ID
30+
let user = await User.findOne({
31+
where: { google_id: googleId },
32+
});
33+
34+
if (user) {
35+
// User exists, return user
36+
return done(null, user);
37+
}
38+
39+
// Check if user exists with this email (linking accounts)
40+
user = await User.findOne({
41+
where: { email },
42+
});
43+
44+
if (user) {
45+
// User exists with email but no Google ID - link the accounts
46+
user.google_id = googleId;
47+
// NEW: Update profile fields if they were empty
48+
if (!user.first_name && firstName) user.first_name = firstName;
49+
if (!user.last_name && lastName) user.last_name = lastName;
50+
await user.save();
51+
return done(null, user);
52+
}
53+
54+
// Create new user
55+
user = await User.create({
56+
username: username,
57+
email: email,
58+
google_id: googleId,
59+
hashed_password: null, // OAuth users don't have passwords
60+
email_verified: true,
61+
// Set from Google profile, or empty string if not available
62+
first_name: firstName || "",
63+
last_name: lastName || "",
64+
company: "", // Always empty for new OAuth users - must be completed
65+
});
66+
67+
return done(null, user);
68+
} catch (error) {
69+
console.error("Google OAuth error:", error);
70+
return done(error, null);
71+
}
72+
}
73+
)
74+
);
75+
76+
// ORCID OAuth Strategy (using OAuth2Strategy as base)
77+
const OAuth2Strategy = require("passport-oauth2");
78+
79+
// passport.use(
80+
// "orcid",
81+
// new OAuth2Strategy(
82+
// {
83+
// authorizationURL: "https://orcid.org/oauth/authorize",
84+
// tokenURL: "https://orcid.org/oauth/token",
85+
// clientID: process.env.ORCID_CLIENT_ID,
86+
// clientSecret: process.env.ORCID_CLIENT_SECRET,
87+
// callbackURL: process.env.ORCID_CALLBACK_URL || "http://localhost:5000/api/v1/auth/orcid/callback",
88+
// scope: "/authenticate",
89+
// },
90+
// async (accessToken, refreshToken, params, profile, done) => {
91+
// try {
92+
// // ORCID returns user info in params, not profile
93+
// const orcidId = params.orcid;
94+
// const name = params.name || `ORCID User ${orcidId}`;
95+
96+
// // ORCID doesn't always provide email in the basic scope
97+
// // You might need to make an additional API call to get email
98+
// // For now, we'll use ORCID ID as identifier
99+
100+
// // Check if user exists with this ORCID ID
101+
// let user = await User.findOne({
102+
// where: { orcid_id: orcidId },
103+
// });
104+
105+
// if (user) {
106+
// return done(null, user);
107+
// }
108+
109+
// // If we have email from ORCID, check for existing user
110+
// if (params.email) {
111+
// user = await User.findOne({
112+
// where: { email: params.email },
113+
// });
114+
115+
// if (user) {
116+
// // Link ORCID to existing account
117+
// user.orcid_id = orcidId;
118+
// await user.save();
119+
// return done(null, user);
120+
// }
121+
// }
122+
123+
// // Create new user
124+
// // Note: ORCID might not provide email, so we use ORCID ID as part of email
125+
// const email = params.email || `${orcidId}@orcid.placeholder`;
126+
// const username = name.replace(/\s+/g, "_").toLowerCase() || `orcid_${orcidId}`;
127+
128+
// user = await User.create({
129+
// username: username,
130+
// email: email,
131+
// orcid_id: orcidId,
132+
// hashed_password: null,
133+
// email_verified: true,
134+
// });
135+
136+
// return done(null, user);
137+
// } catch (error) {
138+
// console.error("ORCID OAuth error:", error);
139+
// return done(error, null);
140+
// }
141+
// }
142+
// )
143+
// );
144+
145+
// Serialize user for session
146+
passport.serializeUser((user, done) => {
147+
done(null, user.id);
148+
});
149+
150+
// Deserialize user from session
151+
passport.deserializeUser(async (id, done) => {
152+
try {
153+
const user = await User.findByPk(id);
154+
done(null, user);
155+
} catch (error) {
156+
done(error, null);
157+
}
158+
});
159+
160+
module.exports = passport;

backend/migrations/20251028184256-create-users.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,46 @@ module.exports = {
4545
allowNull: false,
4646
unique: true,
4747
},
48+
email_verified: {
49+
type: Sequelize.BOOLEAN,
50+
defaultValue: false,
51+
allowNull: false,
52+
},
53+
verification_token: {
54+
type: Sequelize.STRING(255),
55+
allowNull: true,
56+
unique: true,
57+
},
58+
verification_token_expires: {
59+
type: Sequelize.DATE,
60+
allowNull: true,
61+
},
62+
// NEW FIELDS FOR PASSWORD RESET
63+
reset_password_token: {
64+
type: Sequelize.STRING(255),
65+
allowNull: true,
66+
unique: true,
67+
},
68+
reset_password_expires: {
69+
type: Sequelize.DATE,
70+
allowNull: true,
71+
},
72+
first_name: {
73+
type: Sequelize.STRING(255),
74+
allowNull: false,
75+
},
76+
last_name: {
77+
type: Sequelize.STRING(255),
78+
allowNull: false,
79+
},
80+
company: {
81+
type: Sequelize.STRING(255),
82+
allowNull: false,
83+
},
84+
interests: {
85+
type: Sequelize.TEXT,
86+
allowNull: true,
87+
},
4888
created_at: {
4989
type: Sequelize.DATE,
5090
allowNull: false,

backend/models/index.js

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,44 @@
1-
'use strict';
1+
"use strict";
22

3-
const fs = require('fs');
4-
const path = require('path');
5-
const Sequelize = require('sequelize');
6-
const process = require('process');
3+
const fs = require("fs");
4+
const path = require("path");
5+
const Sequelize = require("sequelize");
6+
const process = require("process");
77
const basename = path.basename(__filename);
8-
const env = process.env.NODE_ENV || 'development';
9-
const config = require(__dirname + '/../config/config.json')[env];
8+
const env = process.env.NODE_ENV || "development";
9+
const config = require(__dirname + "/../config/config.js")[env];
1010
const db = {};
1111

1212
let sequelize;
1313
if (config.use_env_variable) {
1414
sequelize = new Sequelize(process.env[config.use_env_variable], config);
1515
} else {
16-
sequelize = new Sequelize(config.database, config.username, config.password, config);
16+
sequelize = new Sequelize(
17+
config.database,
18+
config.username,
19+
config.password,
20+
config
21+
);
1722
}
1823

19-
fs
20-
.readdirSync(__dirname)
21-
.filter(file => {
24+
fs.readdirSync(__dirname)
25+
.filter((file) => {
2226
return (
23-
file.indexOf('.') !== 0 &&
27+
file.indexOf(".") !== 0 &&
2428
file !== basename &&
25-
file.slice(-3) === '.js' &&
26-
file.indexOf('.test.js') === -1
29+
file.slice(-3) === ".js" &&
30+
file.indexOf(".test.js") === -1
2731
);
2832
})
29-
.forEach(file => {
30-
const model = require(path.join(__dirname, file))(sequelize, Sequelize.DataTypes);
33+
.forEach((file) => {
34+
const model = require(path.join(__dirname, file))(
35+
sequelize,
36+
Sequelize.DataTypes
37+
);
3138
db[model.name] = model;
3239
});
3340

34-
Object.keys(db).forEach(modelName => {
41+
Object.keys(db).forEach((modelName) => {
3542
if (db[modelName].associate) {
3643
db[modelName].associate(db);
3744
}

0 commit comments

Comments
 (0)