diff --git a/env.dist b/env.dist index 00c2a37..7ce1b83 100644 --- a/env.dist +++ b/env.dist @@ -13,4 +13,5 @@ INSTAGRAM_CLIENT_SECRET=your-instagram-client-secret DATABASE_URL=./water_stations.db # Server configuration -PORT=3000 \ No newline at end of file +PORT=3000 +FORCE_HTTPS=false \ No newline at end of file diff --git a/server.js b/server.js index caac7a0..63516aa 100644 --- a/server.js +++ b/server.js @@ -15,6 +15,7 @@ require('dotenv').config(); const app = express(); const HOST = process.env.HOST || "0.0.0.0"; const PORT = process.env.PORT || 3000; +const FORCE_HTTPS = process.env.FORCE_HTTPS === 'true'; // Database setup const db = new sqlite3.Database('./water_stations.db'); @@ -91,6 +92,17 @@ app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(express.static(path.join(__dirname, 'public'))); +// HTTPS enforcement middleware +if (FORCE_HTTPS) { + app.use((req, res, next) => { + if (req.header('x-forwarded-proto') !== 'https') { + res.redirect(`https://${req.header('host')}${req.url}`); + } else { + next(); + } + }); +} + app.use(session({ store: new SQLiteStore({ db: 'water_stations.db', @@ -100,7 +112,7 @@ app.use(session({ resave: false, saveUninitialized: false, cookie: { - secure: false, // Set to true in production with HTTPS + secure: FORCE_HTTPS, // Use secure cookies when HTTPS is forced maxAge: 7 * 24 * 60 * 60 * 1000 // 7 days } })); @@ -108,6 +120,13 @@ app.use(session({ app.use(passport.initialize()); app.use(passport.session()); +// Helper function to get base URL +function getBaseUrl(req) { + const protocol = FORCE_HTTPS || req.secure || req.header('x-forwarded-proto') === 'https' ? 'https' : 'http'; + const host = req.header('host'); + return `${protocol}://${host}`; +} + // Passport configuration passport.use(new LocalStrategy( { usernameField: 'username' }, @@ -124,55 +143,67 @@ passport.use(new LocalStrategy( } )); -passport.use(new GoogleStrategy({ - clientID: process.env.GOOGLE_CLIENT_ID, - clientSecret: process.env.GOOGLE_CLIENT_SECRET, - callbackURL: "/auth/google/callback" -}, (accessToken, refreshToken, profile, done) => { - db.get('SELECT * FROM users WHERE google_id = ?', [profile.id], (err, user) => { - if (err) return done(err); - - if (user) { - return done(null, user); - } else { - db.run('INSERT INTO users (google_id, display_name, email) VALUES (?, ?, ?)', - [profile.id, profile.displayName, profile.emails[0].value], - function(err) { - if (err) return done(err); - - db.get('SELECT * FROM users WHERE id = ?', [this.lastID], (err, user) => { - return done(err, user); - }); - } - ); - } - }); -})); +// Initialize OAuth strategies with dynamic callback URLs +function initializeOAuthStrategies(baseUrl = '') { + // Clear existing strategies + passport.unuse('google'); + passport.unuse('instagram'); + + // Google OAuth Strategy + passport.use(new GoogleStrategy({ + clientID: process.env.GOOGLE_CLIENT_ID, + clientSecret: process.env.GOOGLE_CLIENT_SECRET, + callbackURL: `${baseUrl}/auth/google/callback` + }, (accessToken, refreshToken, profile, done) => { + db.get('SELECT * FROM users WHERE google_id = ?', [profile.id], (err, user) => { + if (err) return done(err); + + if (user) { + return done(null, user); + } else { + db.run('INSERT INTO users (google_id, display_name, email) VALUES (?, ?, ?)', + [profile.id, profile.displayName, profile.emails[0].value], + function(err) { + if (err) return done(err); + + db.get('SELECT * FROM users WHERE id = ?', [this.lastID], (err, user) => { + return done(err, user); + }); + } + ); + } + }); + })); -passport.use(new InstagramStrategy({ - clientID: process.env.INSTAGRAM_CLIENT_ID, - clientSecret: process.env.INSTAGRAM_CLIENT_SECRET, - callbackURL: "/auth/instagram/callback" -}, (accessToken, refreshToken, profile, done) => { - db.get('SELECT * FROM users WHERE instagram_id = ?', [profile.id], (err, user) => { - if (err) return done(err); - - if (user) { - return done(null, user); - } else { - db.run('INSERT INTO users (instagram_id, display_name) VALUES (?, ?)', - [profile.id, profile.displayName], - function(err) { - if (err) return done(err); - - db.get('SELECT * FROM users WHERE id = ?', [this.lastID], (err, user) => { - return done(err, user); - }); - } - ); - } - }); -})); + // Instagram OAuth Strategy + passport.use(new InstagramStrategy({ + clientID: process.env.INSTAGRAM_CLIENT_ID, + clientSecret: process.env.INSTAGRAM_CLIENT_SECRET, + callbackURL: `${baseUrl}/auth/instagram/callback` + }, (accessToken, refreshToken, profile, done) => { + db.get('SELECT * FROM users WHERE instagram_id = ?', [profile.id], (err, user) => { + if (err) return done(err); + + if (user) { + return done(null, user); + } else { + db.run('INSERT INTO users (instagram_id, display_name) VALUES (?, ?)', + [profile.id, profile.displayName], + function(err) { + if (err) return done(err); + + db.get('SELECT * FROM users WHERE id = ?', [this.lastID], (err, user) => { + return done(err, user); + }); + } + ); + } + }); + })); +} + +// Initialize OAuth strategies with empty base URL (will be updated per request) +initializeOAuthStrategies(); passport.serializeUser((user, done) => { done(null, user.id); @@ -198,6 +229,11 @@ app.get('/auth/google', (req, res, next) => { if (req.query.redirect) { req.session.redirectUrl = req.query.redirect; } + + // Reinitialize strategies with current request's base URL + const baseUrl = getBaseUrl(req); + initializeOAuthStrategies(baseUrl); + passport.authenticate('google', { scope: ['profile', 'email'] })(req, res, next); }); @@ -214,6 +250,11 @@ app.get('/auth/instagram', (req, res, next) => { if (req.query.redirect) { req.session.redirectUrl = req.query.redirect; } + + // Reinitialize strategies with current request's base URL + const baseUrl = getBaseUrl(req); + initializeOAuthStrategies(baseUrl); + passport.authenticate('instagram')(req, res, next); });