diff options
author | Tyler Schicke <tyler_schicke@brown.edu> | 2019-02-14 05:54:50 -0500 |
---|---|---|
committer | Tyler Schicke <tyler_schicke@brown.edu> | 2019-02-14 05:54:50 -0500 |
commit | c33295b5f98bc53a6a1f2cdf91e440cede3b4a5d (patch) | |
tree | d3b6ccb3003b939930ffe592894fb3d48c7aacff /src | |
parent | 4bcc62fd164c5ee6c4fc50077753ba7d969478e3 (diff) | |
parent | ddd503f21dc4b3368d80b4be475817cd9a13fcd1 (diff) |
Merge branch 'authentication' of github-tsch-brown:browngraphicslab/Dash-Web into server_database_merge
Diffstat (limited to 'src')
-rw-r--r-- | src/server/Message.ts | 1 | ||||
-rw-r--r-- | src/server/authentication/config/passport.ts | 49 | ||||
-rw-r--r-- | src/server/authentication/controllers/user.ts | 107 | ||||
-rw-r--r-- | src/server/authentication/models/User.ts | 89 | ||||
-rw-r--r-- | src/server/index.ts | 53 |
5 files changed, 295 insertions, 4 deletions
diff --git a/src/server/Message.ts b/src/server/Message.ts index 1ec6e25f3..e78c36ef1 100644 --- a/src/server/Message.ts +++ b/src/server/Message.ts @@ -1,6 +1,5 @@ import { Utils } from "../Utils"; import { FIELD_ID, Field } from "../fields/Field"; -import { ObjectId } from "bson"; export class Message<T> { private name: string; diff --git a/src/server/authentication/config/passport.ts b/src/server/authentication/config/passport.ts new file mode 100644 index 000000000..05f6c3133 --- /dev/null +++ b/src/server/authentication/config/passport.ts @@ -0,0 +1,49 @@ +import * as passport from 'passport' +import * as passportLocal from 'passport-local'; +import * as mongodb from 'mongodb'; +import * as _ from "lodash"; +import { default as User } from '../models/User'; +import { Request, Response, NextFunction } from "express"; + +const LocalStrategy = passportLocal.Strategy; + +passport.serializeUser<any, any>((user, done) => { + done(undefined, user.id); +}); + +passport.deserializeUser<any, any>((id, done) => { + User.findById(id, (err, user) => { + done(err, user); + }); +}); + +// AUTHENTICATE JUST WITH EMAIL AND PASSWORD +passport.use(new LocalStrategy({ usernameField: 'email' }, (email, password, done) => { + User.findOne({ email: email.toLowerCase() }, (error: any, user: any) => { + if (error) return done(error); + if (!user) return done(undefined, false, { message: "Invalid email or password" }) // invalid email + user.comparePassword(password, (error: Error, isMatch: boolean) => { + if (error) return done(error); + if (!isMatch) return done(undefined, false, { message: "Invalid email or password" }); // invalid password + // valid authentication HERE + return done(undefined, user); + }); + }); +})); + +export let isAuthenticated = (req: Request, res: Response, next: NextFunction) => { + if (req.isAuthenticated()) { + return next(); + } + return res.redirect("/login"); +} + +export let isAuthorized = (req: Request, res: Response, next: NextFunction) => { + const provider = req.path.split("/").slice(-1)[0]; + + if (_.find(req.user.tokens, { kind: provider })) { + next(); + } else { + res.redirect(`/auth/${provider}`); + } +};
\ No newline at end of file diff --git a/src/server/authentication/controllers/user.ts b/src/server/authentication/controllers/user.ts new file mode 100644 index 000000000..f74ff9039 --- /dev/null +++ b/src/server/authentication/controllers/user.ts @@ -0,0 +1,107 @@ +import { default as User, UserModel, AuthToken } from "../models/User"; +import { Request, Response, NextFunction } from "express"; +import * as passport from "passport"; +import { IVerifyOptions } from "passport-local"; +import "../config/passport"; +import * as request from "express-validator"; +const flash = require("express-flash"); +import * as session from "express-session"; +import * as pug from 'pug'; + +/** + * GET /signup + * Signup page. + */ +export let getSignup = (req: Request, res: Response) => { + if (req.user) { + return res.redirect("/"); + } + res.render("signup.pug", { + title: "Sign Up" + }); +}; + +/** + * POST /signup + * Create a new local account. + */ +export let postSignup = (req: Request, res: Response, next: NextFunction) => { + req.assert("email", "Email is not valid").isEmail(); + req.assert("password", "Password must be at least 4 characters long").len({ min: 4 }); + req.assert("confirmPassword", "Passwords do not match").equals(req.body.password); + req.sanitize("email").normalizeEmail({ gmail_remove_dots: false }); + + const errors = req.validationErrors(); + + if (errors) { + req.flash("errors", "Unable to facilitate sign up. Please try again."); + return res.redirect("/signup"); + } + + const user = new User({ + email: req.body.email, + password: req.body.password + }); + + User.findOne({ email: req.body.email }, (err, existingUser) => { + if (err) { return next(err); } + if (existingUser) { + req.flash("errors", "Account with that email address already exists."); + return res.redirect("/signup"); + } + user.save((err) => { + if (err) { return next(err); } + req.logIn(user, (err) => { + if (err) { + return next(err); + } + res.redirect("/"); + }); + }); + }); +}; + + +/** + * GET /login + * Login page. + */ +export let getLogin = (req: Request, res: Response) => { + if (req.user) { + return res.redirect("/"); + } + res.send("<p>dear lord please render</p>"); + // res.render("account/login", { + // title: "Login" + // }); +}; + +/** + * POST /login + * Sign in using email and password. + */ +export let postLogin = (req: Request, res: Response, next: NextFunction) => { + req.assert("email", "Email is not valid").isEmail(); + req.assert("password", "Password cannot be blank").notEmpty(); + req.sanitize("email").normalizeEmail({ gmail_remove_dots: false }); + + const errors = req.validationErrors(); + + if (errors) { + req.flash("errors", "Unable to login at this time. Please try again."); + return res.redirect("/login"); + } + + passport.authenticate("local", (err: Error, user: UserModel, info: IVerifyOptions) => { + if (err) { return next(err); } + if (!user) { + req.flash("errors", info.message); + return res.redirect("/login"); + } + req.logIn(user, (err) => { + if (err) { return next(err); } + req.flash("success", "Success! You are logged in."); + res.redirect("/"); + }); + })(req, res, next); +};
\ No newline at end of file diff --git a/src/server/authentication/models/User.ts b/src/server/authentication/models/User.ts new file mode 100644 index 000000000..9752c4260 --- /dev/null +++ b/src/server/authentication/models/User.ts @@ -0,0 +1,89 @@ +//@ts-ignore +import * as bcrypt from "bcrypt-nodejs"; +import * as crypto from "crypto"; +//@ts-ignore +import * as mongoose from "mongoose"; +var url = 'mongodb://localhost:27017/Dash' + +mongoose.connect(url, { useNewUrlParser: true }); + +mongoose.connection.on('connected', function () { + console.log('Stablished connection on ' + url); +}); +mongoose.connection.on('error', function (error) { + console.log('Something wrong happened: ' + error); +}); +mongoose.connection.on('disconnected', function () { + console.log('connection closed'); +}); +export type UserModel = mongoose.Document & { + email: string, + password: string, + passwordResetToken: string, + passwordResetExpires: Date, + tokens: AuthToken[], + + profile: { + name: string, + gender: string, + location: string, + website: string, + picture: string + }, + + comparePassword: comparePasswordFunction, +}; + +type comparePasswordFunction = (candidatePassword: string, cb: (err: any, isMatch: any) => {}) => void; + +export type AuthToken = { + accessToken: string, + kind: string +}; + +const userSchema = new mongoose.Schema({ + email: { type: String, unique: true }, + password: String, + passwordResetToken: String, + passwordResetExpires: Date, + + facebook: String, + twitter: String, + google: String, + tokens: Array, + + profile: { + name: String, + gender: String, + location: String, + website: String, + picture: String + } +}, { timestamps: true }); + +/** + * Password hash middleware. + */ +userSchema.pre("save", function save(next) { + const user = this as UserModel; + if (!user.isModified("password")) { return next(); } + bcrypt.genSalt(10, (err, salt) => { + if (err) { return next(err); } + bcrypt.hash(user.password, salt, () => void {}, (err: mongoose.Error, hash) => { + if (err) { return next(err); } + user.password = hash; + next(); + }); + }); +}); + +const comparePassword: comparePasswordFunction = function (this: UserModel, candidatePassword, cb) { + bcrypt.compare(candidatePassword, this.password, (err: mongoose.Error, isMatch: boolean) => { + cb(err, isMatch); + }); +}; + +userSchema.methods.comparePassword = comparePassword; + +const User = mongoose.model("User", userSchema); +export default User;
\ No newline at end of file diff --git a/src/server/index.ts b/src/server/index.ts index 812338080..eb26aa233 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -4,6 +4,7 @@ import * as webpack from 'webpack' import * as wdm from 'webpack-dev-middleware'; import * as whm from 'webpack-hot-middleware'; import * as path from 'path' +import * as passport from 'passport'; import { MessageStore, Message, SetFieldArgs, GetFieldArgs, Transferable } from "./Message"; import { Client } from './Client'; import { Socket } from 'socket.io'; @@ -15,10 +16,56 @@ import { ServerUtils } from './ServerUtil'; import { ObjectID } from 'mongodb'; import { Document } from '../fields/Document'; import * as io from 'socket.io' -const config = require('../../webpack.config') -const compiler = webpack(config) +import * as passportConfig from './authentication/config/passport'; +import { getLogin, postLogin, getSignup, postSignup } from './authentication/controllers/user'; +const config = require('../../webpack.config'); +const compiler = webpack(config); const port = 1050; // default port to listen const serverPort = 1234; +import * as expressValidator from 'express-validator'; +import expressFlash = require('express-flash'); +import * as bodyParser from 'body-parser'; +import * as session from 'express-session'; +import c = require("crypto"); +const MongoStore = require('connect-mongo')(session); +const mongoose = require('mongoose'); +const bluebird = require('bluebird'); + +const mongoUrl = 'mongodb://localhost:27017/Dash'; +// mongoose.Promise = bluebird; +mongoose.connect(mongoUrl)//.then( +// () => { /** ready to use. The `mongoose.connect()` promise resolves to undefined. */ }, +// ).catch((err: any) => { +// console.log("MongoDB connection error. Please make sure MongoDB is running. " + err); +// process.exit(); +// }); +mongoose.connection.on('connected', function () { + console.log("connected"); +}) + +app.use(bodyParser.json()); +app.use(bodyParser.urlencoded({ extended: true })); +app.use(expressValidator()); +app.use(expressFlash()); +app.use(require('express-session')({ + secret: `${c.randomBytes(64)}`, + resave: true, + saveUninitialized: true, + store: new MongoStore({ + url: 'mongodb://localhost:27017/Dash' + }) +})); +app.use(passport.initialize()); +app.use(passport.session()); +app.use((req, res, next) => { + res.locals.user = req.user; + next(); +}); + +app.get("/signup", getSignup); +app.post("/signup", postSignup); +app.get("/login", getLogin); +app.post("/login", postLogin); let FieldStore: ObservableMap<FIELD_ID, Field> = new ObservableMap(); @@ -71,7 +118,7 @@ function deleteAll() { function barReceived(guid: String) { clients[guid.toString()] = new Client(guid.toString()); - Database.Instance.print() + // Database.Instance.print() } function addDocument(document: Document) { |