From 2dc89b99b652b62f3196587871d4d9b0c269db18 Mon Sep 17 00:00:00 2001 From: Clayton Wilson Date: Sun, 29 Sep 2019 23:31:33 -0400 Subject: [PATCH 01/11] Added edit page. --- twistter-frontend/src/App.js | 4 +++- twistter-frontend/src/pages/edit.js | 13 +++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 twistter-frontend/src/pages/edit.js diff --git a/twistter-frontend/src/App.js b/twistter-frontend/src/App.js index 3b0a077..7d104c5 100644 --- a/twistter-frontend/src/App.js +++ b/twistter-frontend/src/App.js @@ -6,12 +6,13 @@ import './App.css'; import { BrowserRouter as Router } from 'react-router-dom'; import Route from 'react-router-dom/Route'; - +// Pages import home from './Home.js'; import register from './Register.js'; import login from './Login.js'; import user from './pages/user'; import writeMicroblog from './Writing_Microblogs.js'; +import edit from './pages/edit.js'; class App extends Component { render() { @@ -25,6 +26,7 @@ class App extends Component { + diff --git a/twistter-frontend/src/pages/edit.js b/twistter-frontend/src/pages/edit.js new file mode 100644 index 0000000..e4b9085 --- /dev/null +++ b/twistter-frontend/src/pages/edit.js @@ -0,0 +1,13 @@ +import React, { Component } from 'react' + +export class edit extends Component { + render() { + return ( +
+ yo, this the edit profile page +
+ ) + } +} + +export default edit; From 0f127521caa79e7b11d1ae6bc7e23d20c262011d Mon Sep 17 00:00:00 2001 From: Clayton Wilson Date: Sun, 29 Sep 2019 23:31:33 -0400 Subject: [PATCH 02/11] Added edit page. --- twistter-frontend/src/App.js | 4 +++- twistter-frontend/src/pages/edit.js | 13 +++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 twistter-frontend/src/pages/edit.js diff --git a/twistter-frontend/src/App.js b/twistter-frontend/src/App.js index 3b0a077..7d104c5 100644 --- a/twistter-frontend/src/App.js +++ b/twistter-frontend/src/App.js @@ -6,12 +6,13 @@ import './App.css'; import { BrowserRouter as Router } from 'react-router-dom'; import Route from 'react-router-dom/Route'; - +// Pages import home from './Home.js'; import register from './Register.js'; import login from './Login.js'; import user from './pages/user'; import writeMicroblog from './Writing_Microblogs.js'; +import edit from './pages/edit.js'; class App extends Component { render() { @@ -25,6 +26,7 @@ class App extends Component { + diff --git a/twistter-frontend/src/pages/edit.js b/twistter-frontend/src/pages/edit.js new file mode 100644 index 0000000..e4b9085 --- /dev/null +++ b/twistter-frontend/src/pages/edit.js @@ -0,0 +1,13 @@ +import React, { Component } from 'react' + +export class edit extends Component { + render() { + return ( +
+ yo, this the edit profile page +
+ ) + } +} + +export default edit; From 696b00e304ad637eed8fe1333f2c1423f04843d6 Mon Sep 17 00:00:00 2001 From: Clayton Wilson Date: Mon, 30 Sep 2019 22:30:29 -0400 Subject: [PATCH 03/11] Edit profile front-end almost finished. --- functions/package-lock.json | 50 +++++-- functions/package.json | 1 + twistter-frontend/package.json | 4 +- twistter-frontend/src/pages/edit.js | 221 +++++++++++++++++++++++++++- 4 files changed, 254 insertions(+), 22 deletions(-) diff --git a/functions/package-lock.json b/functions/package-lock.json index c1ee09a..a119731 100644 --- a/functions/package-lock.json +++ b/functions/package-lock.json @@ -180,11 +180,6 @@ "resolved": "https://registry.npmjs.org/@firebase/installations-types/-/installations-types-0.1.2.tgz", "integrity": "sha512-fQaWIW8hyX1XUN7+FCSPjvM1agFjGidVuF4Sxi7aFwfyh5t+4fD2VpM4wCQbWmodnx4fZLvsuQd9mkxxU+lGYQ==" }, - "@firebase/logger": { - "version": "0.1.24", - "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.1.24.tgz", - "integrity": "sha512-wPwhWCepEjWiTIqeC9U+7Hcw4XwezKPdXmyXbYSPiWNDcVekNgMPkntwSK+/2ufJO/1nMwAL2n6fL12oQG/PpQ==" - }, "@firebase/messaging": { "version": "0.4.11", "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.4.11.tgz", @@ -284,14 +279,6 @@ "resolved": "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.3.3.tgz", "integrity": "sha512-fUp4kpbxwDiWs/aIBJqBvXgFHZvgoND2JA0gJYSEsXtWtVwfgzY/710plErgZDeQKopX5eOR1sHskZkQUy0U6w==" }, - "@firebase/util": { - "version": "0.2.27", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.2.27.tgz", - "integrity": "sha512-kFlbWNX1OuLfHrDXZ5QLmNNiLtMyxzbBgMo1DY1tXMjKK1AMYsHnyjInA8esvO0SCDp5XN3Pt9EDlhY4sRiLsw==", - "requires": { - "tslib": "1.10.0" - } - }, "@firebase/webchannel-wrapper": { "version": "0.2.26", "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-0.2.26.tgz", @@ -660,6 +647,15 @@ "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", "dev": true }, + "axios": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.0.tgz", + "integrity": "sha512-1uvKqKQta3KBxIz14F2v06AEHZ/dIoeKfbTRkK1E5oqjDnuEerLmYTgJB5AiQZHJcljpg1TuRzdjDR06qNk0DQ==", + "requires": { + "follow-redirects": "1.5.10", + "is-buffer": "^2.0.2" + } + }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", @@ -1650,6 +1646,29 @@ "integrity": "sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==", "dev": true }, + "follow-redirects": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", + "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", + "requires": { + "debug": "=3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, "forwarded": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", @@ -2470,6 +2489,11 @@ "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==", "optional": true }, + "is-buffer": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.3.tgz", + "integrity": "sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw==" + }, "is-date-object": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", diff --git a/functions/package.json b/functions/package.json index 8ca2723..c5049dd 100644 --- a/functions/package.json +++ b/functions/package.json @@ -13,6 +13,7 @@ "node": "8" }, "dependencies": { + "axios": "^0.19.0", "firebase": "^6.6.2", "firebase-admin": "^8.6.0", "firebase-functions": "^3.1.0" diff --git a/twistter-frontend/package.json b/twistter-frontend/package.json index 8176b1a..0cb0dda 100644 --- a/twistter-frontend/package.json +++ b/twistter-frontend/package.json @@ -5,6 +5,7 @@ "dependencies": { "@material-ui/core": "^4.4.3", "axios": "^0.19.0", + "clsx": "^1.0.4", "create-react-app": "^3.1.2", "install": "^0.13.0", "node-pre-gyp": "^0.13.0", @@ -22,5 +23,6 @@ "build": "react-scripts build", "test": "react-scripts test --env=jsdom", "eject": "react-scripts eject" - } + }, + "proxy": "https://us-central1-twistter-e4649.cloudfunctions.net/api" } diff --git a/twistter-frontend/src/pages/edit.js b/twistter-frontend/src/pages/edit.js index e4b9085..b3d0a85 100644 --- a/twistter-frontend/src/pages/edit.js +++ b/twistter-frontend/src/pages/edit.js @@ -1,13 +1,218 @@ -import React, { Component } from 'react' +import React, { Component } from "react"; +import axios from "axios"; +import PropTypes from "prop-types"; + +// Material-UI stuff +import TextField from "@material-ui/core/TextField"; +import Typography from "@material-ui/core/Typography"; +import withStyles from "@material-ui/core/styles/withStyles"; +import Grid from "@material-ui/core/Grid"; +import Button from "@material-ui/core/Button"; +const styles = { + form: { + textAlign: "center" + }, + textField: { + marginBottom: 40 + }, + pageTitle: { + marginTop: 40, + marginBottom: 40 + } + // firstName: { + // marginRight: 10, + // }, + // lastname: { + // marginLeft: 10, + // } + // name: { + // width: '40%' + // } +}; + +// const classes = useStyles(); export class edit extends Component { - render() { - return ( -
- yo, this the edit profile page -
- ) + // profileData = { + // firstName: null, + // lastName: null, + // email: null, + // handle: null, + // bio: null, + // valid: false, + // }; + + componentDidMount() { + axios + .get("/getProfileInfo") + .then(res => { + // console.log(res.data); + // this.state.firstName = res.data.firstName; + // this.state.lastName = res.data.lastName; + // this.state.email = res.data.email; + // this.state.handle = res.data.handle; + // this.state.bio = res.data.bio; + // this.state.valid = true; + this.setState({ + firstName: res.data.firstName, + lastName: res.data.lastName, + email: res.data.email, + handle: res.data.handle, + bio: res.data.bio + }); + }) + .catch(err => { + console.error(err); + }); + } + + constructor() { + super(); + this.state = { + firstName: "", + lastName: "", + email: "", + handle: "", + bio: "", + loading: false, + errors: {} + }; + } + + handleSubmit = (event) => { + event.preventDefault(); + this.setState({ + loading: true, + }); + const newProfileData = { + firstName: this.state.firstName, + lastName: this.state.lastName, + email: this.state.email, + handle: this.state.handle, + bio: this.state.bio, } + axios.post('updateProfileInfo', newProfileData) + .then((res) => { + this.setState({ + loading: false, + }); + // this.props.history.push('/'); + }) + .catch((err) => { + this.setState({ + errors: err.response.data, + loading: false, + }); + }) + }; + + handleChange = (event) => { + this.setState({ + [event.target.name]: event.target.value + }); + }; + + render() { + const { classes } = this.props; + const { errors, loading } = this.state; + // let editProfileMarkup = this.state.profileData ? ( + // // this.state.profileData.map(profileData =>

{profileData}

) + //
+ + //

{this.state.profileData.firstName}

+ //

{this.state.profileData.lastName}

+ //

{this.state.profileData.email}

+ //

{this.state.profileData.bio}

+ //
+ // ) :

Loading...

+ return ( + + + + + Edit Profile + +
+ + + + + + + + + + + + + +
+ + + ); + } } -export default edit; +edit.propTypes = { + classes: PropTypes.object.isRequired +}; + +export default withStyles(styles)(edit); From 721aeeb350d75b60b8b9b3dc7afec4211a3ff9a7 Mon Sep 17 00:00:00 2001 From: Clayton Wilson Date: Mon, 30 Sep 2019 23:39:35 -0400 Subject: [PATCH 04/11] Edit Profile front-end finished and connected to back-end --- twistter-frontend/src/pages/edit.js | 106 +++++++++++++++------------- 1 file changed, 55 insertions(+), 51 deletions(-) diff --git a/twistter-frontend/src/pages/edit.js b/twistter-frontend/src/pages/edit.js index b3d0a85..3bffcfa 100644 --- a/twistter-frontend/src/pages/edit.js +++ b/twistter-frontend/src/pages/edit.js @@ -1,6 +1,8 @@ import React, { Component } from "react"; import axios from "axios"; import PropTypes from "prop-types"; +// TODO: Add a read-only '@' in the left side of the handle input +// TODO: Add a cancel button, that takes the user back to their profile page // Material-UI stuff import TextField from "@material-ui/core/TextField"; @@ -8,6 +10,8 @@ import Typography from "@material-ui/core/Typography"; import withStyles from "@material-ui/core/styles/withStyles"; import Grid from "@material-ui/core/Grid"; import Button from "@material-ui/core/Button"; +import CircularProgress from '@material-ui/core/CircularProgress'; + const styles = { form: { textAlign: "center" @@ -18,16 +22,13 @@ const styles = { pageTitle: { marginTop: 40, marginBottom: 40 + }, + button: { + positon: 'relative', + }, + progress: { + position: 'absolute', } - // firstName: { - // marginRight: 10, - // }, - // lastname: { - // marginLeft: 10, - // } - // name: { - // width: '40%' - // } }; // const classes = useStyles(); @@ -45,14 +46,7 @@ export class edit extends Component { componentDidMount() { axios .get("/getProfileInfo") - .then(res => { - // console.log(res.data); - // this.state.firstName = res.data.firstName; - // this.state.lastName = res.data.lastName; - // this.state.email = res.data.email; - // this.state.handle = res.data.handle; - // this.state.bio = res.data.bio; - // this.state.valid = true; + .then((res) => { this.setState({ firstName: res.data.firstName, lastName: res.data.lastName, @@ -61,7 +55,7 @@ export class edit extends Component { bio: res.data.bio }); }) - .catch(err => { + .catch((err) => { console.error(err); }); } @@ -82,49 +76,45 @@ export class edit extends Component { handleSubmit = (event) => { event.preventDefault(); this.setState({ - loading: true, + loading: true }); const newProfileData = { - firstName: this.state.firstName, - lastName: this.state.lastName, - email: this.state.email, - handle: this.state.handle, - bio: this.state.bio, - } - axios.post('updateProfileInfo', newProfileData) - .then((res) => { - this.setState({ - loading: false, - }); - // this.props.history.push('/'); - }) - .catch((err) => { - this.setState({ - errors: err.response.data, - loading: false, - }); - }) + firstName: this.state.firstName, + lastName: this.state.lastName, + email: this.state.email, + handle: this.state.handle, + bio: this.state.bio + }; + axios + .post("/updateProfileInfo", newProfileData) + .then((res) => { + this.setState({ + loading: false + }); + // this.props.history.push('/'); + // TODO: Need to redirect user to their profile page + }) + .catch((err) => { + this.setState({ + errors: err.response.data, + loading: false + }); + }); }; handleChange = (event) => { this.setState({ - [event.target.name]: event.target.value + [event.target.name]: event.target.value, + errors: { + [event.target.name]: null, + } }); }; render() { const { classes } = this.props; - const { errors, loading } = this.state; - // let editProfileMarkup = this.state.profileData ? ( - // // this.state.profileData.map(profileData =>

{profileData}

) - //
+ const { errors, loading } = this.state; - //

{this.state.profileData.firstName}

- //

{this.state.profileData.lastName}

- //

{this.state.profileData.email}

- //

{this.state.profileData.bio}

- //
- // ) :

Loading...

return ( @@ -141,6 +131,8 @@ export class edit extends Component { label="First Name" className={classes.textField} value={this.state.firstName} + helperText={errors.firstName} + error={errors.firstName ? true : false} variant="outlined" onChange={this.handleChange} fullWidth @@ -153,6 +145,8 @@ export class edit extends Component { label="Last Name" className={classes.textField} value={this.state.lastName} + helperText={errors.lastname} + error={errors.lastName ? true : false} variant="outlined" onChange={this.handleChange} fullWidth @@ -162,11 +156,13 @@ export class edit extends Component { Submit + {loading && ( + + )} From 5116a705bcd289511d78491987245ae4ff115396 Mon Sep 17 00:00:00 2001 From: Clayton Wilson Date: Tue, 1 Oct 2019 14:32:27 -0400 Subject: [PATCH 05/11] Save before trying to fix master. --- twistter-frontend/src/App.css | 4 ++++ twistter-frontend/src/pages/edit.js | 13 +++---------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/twistter-frontend/src/App.css b/twistter-frontend/src/App.css index 5935fb8..55d854d 100644 --- a/twistter-frontend/src/App.css +++ b/twistter-frontend/src/App.css @@ -1,3 +1,7 @@ +/* body { + +} */ + .app { font-family: "Segoe UI"; font-size: large; diff --git a/twistter-frontend/src/pages/edit.js b/twistter-frontend/src/pages/edit.js index 3bffcfa..9166379 100644 --- a/twistter-frontend/src/pages/edit.js +++ b/twistter-frontend/src/pages/edit.js @@ -1,8 +1,11 @@ import React, { Component } from "react"; import axios from "axios"; import PropTypes from "prop-types"; +// TODO: Fix font, so that it is roboto // TODO: Add a read-only '@' in the left side of the handle input // TODO: Add a cancel button, that takes the user back to their profile page +// TODO: Sort imports +// TODO: Add comments // Material-UI stuff import TextField from "@material-ui/core/TextField"; @@ -31,17 +34,7 @@ const styles = { } }; -// const classes = useStyles(); - export class edit extends Component { - // profileData = { - // firstName: null, - // lastName: null, - // email: null, - // handle: null, - // bio: null, - // valid: false, - // }; componentDidMount() { axios From cc98fa788433ebdcc5c5bf5b00670cd696f2e94e Mon Sep 17 00:00:00 2001 From: Clayton Wilson Date: Tue, 1 Oct 2019 15:27:24 -0400 Subject: [PATCH 06/11] Fixed index.js and refactored it. --- functions/handlers/users.js | 133 ++++++++++++++++++++- functions/index.js | 232 ++---------------------------------- 2 files changed, 143 insertions(+), 222 deletions(-) diff --git a/functions/handlers/users.js b/functions/handlers/users.js index 6c5941f..70d6cbd 100644 --- a/functions/handlers/users.js +++ b/functions/handlers/users.js @@ -1,7 +1,138 @@ /* eslint-disable promise/catch-or-return */ -const {db} = require('../util/admin'); +const {admin, db} = require('../util/admin'); +const config = require('../util/config'); + const {validateUpdateProfileInfo} = require('../util/validator'); +const firebase = require('firebase'); +firebase.initializeApp(config); + + + +exports.signup = (req, res) => { + const newUser = { + email: req.body.email, + handle: req.body.handle, + password: req.body.password, + confirmPassword: req.body.confirmPassword, + time: new Date().toISOString() + }; + + // console.log(newUser); + + let errors = {}; + + const emailRegEx = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@(([[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; + + //Email check + if(newUser.email.trim() === '') { + errors.email = 'Email must not be blank.'; + } + else if(!newUser.email.match(emailRegEx)) { + errors.email = 'Email is invalid.'; + } + + //handle check + if(newUser.handle.trim() === '') { + errors.handle = 'Username must not be blank.'; + } + else if(newUser.handle.length < 4 || newUser.handle.length > 30) { + errors.handle = 'Username must be between 4-30 characters long.'; + } + + //Password check + if(newUser.password.trim() === '') { + errors.password = 'Password must not be blank.'; + } + else if(newUser.password.length < 8 || newUser.password.length > 20) { + errors.password = 'Password must be between 8-20 characters long.'; + } + + //Confirm password check + if(newUser.confirmPassword !== newUser.password) { + errors.confirmPassword = 'Passwords must match.'; + } + + //Overall check + if(Object.keys(errors).length > 0) { + return res.status(400).json(errors); + } + + let idToken, userId; + + db.doc(`/users/${newUser.handle}`).get() + .then(doc => { + if(doc.exists) { + return res.status(400).json({ handle: 'This username is already taken.' }); + } + return firebase.auth().createUserWithEmailAndPassword(newUser.email, newUser.password); + }) + .then(data => { + userId = data.user.uid; + return data.user.getIdToken(); + }) + .then(token => { + idToken = token; + const userCred = { + email: req.body.email, + handle: newUser.handle, + time: newUser.time, + userId + } + return db.doc(`/users/${newUser.handle}`).set(userCred); + }) + .then(() => { + return res.status(201).json({ idToken }); + }) + .catch(err => { + console.error(err); + if(err.code === 'auth/email-already-in-use') { + return res.status(500).json({ email: 'This email is already taken.' }); + } + return res.status(500).json({ error: err.code }); + }); +}; + +exports.login = (req, res) => { + const user = { + email: req.body.email, + password: req.body.password + } + + //Auth validation + let invalidCred = {}; + + //Email check + if(user.email.trim() === '') { + invalidCred.email = 'Email must not be blank.'; + } + + //Password check + if(user.password.trim() === '') { + invalidCred.password = 'Password must not be blank.'; + } + + //Overall check + if(Object.keys(invalidCred).length > 0) { + return res.status(400).json(errors); + } + + firebase.auth().signInWithEmailAndPassword(user.email, user.password) + .then(data => { + return data.user.getIdToken(); + }) + .then(token => { + return res.json({token}); + }) + .catch(err => { + console.error(err); + if(err.code === 'auth/wrong-password') { + return res.status(403).json({ general: 'Invalid credentials. Please try again.' }); + } + return res.status(500).json({ error: err.code }); + }); + }; + exports.getProfileInfo = (req, res) => { // FIXME: Delete this after login is implemented req.user = {}; diff --git a/functions/index.js b/functions/index.js index 6ff3bd3..04d3b4e 100644 --- a/functions/index.js +++ b/functions/index.js @@ -4,236 +4,26 @@ const app = require('express')(); const cors = require('cors'); app.use(cors()); -var config = { - apiKey: "AIzaSyCvsWetg4qFdsPGfJ3LCw_QaaYzoan7Q34", - authDomain: "twistter-e4649.firebaseapp.com", - databaseURL: "https://twistter-e4649.firebaseio.com", - projectId: "twistter-e4649", - storageBucket: "twistter-e4649.appspot.com", - messagingSenderId: "20131817365", - appId: "1:20131817365:web:633c95fb08b16d4526b89c" -}; +const fbAuth = require('./util/fbAuth'); -const firebase = require('firebase'); -firebase.initializeApp(config); +const {db} = require('./util/admin'); -// Acts as a middleman between the client and any function that you use it with -// The function will only execute if the user is logged in, or rather, they have -// a valid token -const firebaseAuth = (req, res, next) => { - let idToken; +// const firebase = require('firebase'); +// firebase.initializeApp(config); - if (req.headers.authorization && req.headers.authorization.startsWith('Bearer ')) { - idToken = req.headers.authorization.split('Bearer ')[1]; - } else { - console.error('No token found'); - return res.status(403).json({ error: 'Unauthorized' }); - } - admin.auth().verifyIdToken(idToken) - .then(decodedToken => { - req.user = decodedToken; - console.log(decodedToken); - return db.collection('users') - .where('userId', '==', req.user.uid) - .limit(1) - .get(); - }) - .then(data => { - req.user.username = data.docs[0].data().username; - return next(); - }) - .catch(err => { - console.error("Token verfication failed.", err); - return res.status(403).json(err); - }); -}; -app.post('/scream', firebaseAuth, (req, res) => { - const newScream = { - username: req.user.username, - body: req.body.body, - numLikes: 0, - numComments: 0, - time: new Date().toISOString() - }; - let invalidCred = {}; - - //Body check - if(req.body.body.trim() === '') { - invalidCred.body = 'Body must not be blank'; - } - - //Overall check - if(Object.keys(invalidCred).length > 0) { - return res.status(400).json(errors); - } - - db - .collection('screams') - .add(newScream) - .then(doc => { - res.json({ message: `Document ${doc.id} created successfully!` }); - }) - .catch(err => { - console.error(err); - return res.status(500).json({ error: 'Someting went wrong.' }); - }); -}); - -app.get('/screams', (req, res) => { - db - .collection('screams') - .orderBy('time', 'desc') - .get() - .then(data => { - let screams = []; - data.forEach(doc => { - screams.push({ - username: doc.data().username, - body: doc.data().body, - numLikes: doc.data().numLikes, - numComments: doc.data().numComments, - time: doc.data().time, - screamId: doc.id - }); - }); - return res.json(screams); - }) - .catch(err => { - console.error(err); - return res.status(500).json({ error: err.code }); - }); -}); - -app.post('/signup', (req, res) => { - const newUser = { - email: req.body.email, - username: req.body.username, - password: req.body.password, - confirmPassword: req.body.confirmPassword, - time: new Date().toISOString() - }; - - let invalidCred = {}; - - const emailRegEx = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; - - //Email check - if(newUser.email.trim() === '') { - invalidCred.email = 'Email must not be blank.'; - } - else if(!newUser.email.match(emailRegEx)) { - invalidCred.email = 'Email is invalid.'; - } - - //Username check - if(newUser.username.trim() === '') { - invalidCred.username = 'Username must not be blank.'; - } - else if(newUser.username.length < 4 || newUser.username.length > 30) { - invalidCred.username = 'Username must be between 4-30 characters long.'; - } - - //Password check - if(newUser.password.trim() === '') { - invalidCred.password = 'Password must not be blank.'; - } - else if(newUser.password.length < 8 || newUser.password.length > 20) { - invalidCred.password = 'Password must be between 8-20 characters long.'; - } - - //Confirm password check - if(newUser.confirmPassword !== newUser.password) { - invalidCred.confirmPassword = 'Passwords must match.'; - } - - //Overall check - if(Object.keys(invalidCred).length > 0) { - return res.status(400).json(errors); - } - - let idToken, userId; - - db.doc(`/users/${newUser.username}`).get() - .then(doc => { - if(doc.exists) { - return res.status(400).json({ username: 'This username is already taken.' }); - } - return firebase.auth().createUserWithEmailAndPassword(newUser.email, newUser.password); - }) - .then(data => { - userId = data.user.uid; - return data.user.getIdToken(); - }) - .then(token => { - idToken = token; - const userCred = { - email: req.body.email, - username: newUser.username, - time: newUser.time, - userId - } - return db.doc(`/users/${newUser.username}`).set(userCred); - }) - .then(() => { - return res.status(201).json({ idToken }); - }) - .catch(err => { - console.error(err); - if(err.code === 'auth/email-already-in-use') { - return res.status(500).json({ email: 'This email is already taken.' }); - } - return res.status(500).json({ error: err.code }); - }); -}); - -app.post('/login', (req, res) => { - const user = { - email: req.body.email, - password: req.body.password - } - - //Auth validation - let invalidCred = {}; - - //Email check - if(user.email.trim() === '') { - invalidCred.email = 'Email must not be blank.'; - } - - //Password check - if(user.password.trim() === '') { - invalidCred.password = 'Password must not be blank.'; - } - - //Overall check - if(Object.keys(invalidCred).length > 0) { - return res.status(400).json(errors); - } - - firebase.auth().signInWithEmailAndPassword(user.email, user.password) - .then(data => { - return data.user.getIdToken(); - }) - .then(token => { - return res.json({token}); - }) - .catch(err => { - console.error(err); - if(err.code === 'auth/wrong-password') { - return res.status(403).json({ general: 'Invalid credentials. Please try again.' }); - } - return res.status(500).json({ error: err.code }); - }); -}); /*------------------------------------------------------------------* - * handlers/users.js * - *------------------------------------------------------------------*/ -const {getUserDetails, getProfileInfo, updateProfileInfo} = require('./handlers/users'); +* handlers/users.js * +*------------------------------------------------------------------*/ +const {getUserDetails, getProfileInfo, updateProfileInfo, signup, login} = require('./handlers/users'); + +app.post('/signup', signup); + +app.post('/login', login); app.get('/getUser/:handle', getUserDetails); From c5bec46b52794c9133ef716e37504242942bc7cf Mon Sep 17 00:00:00 2001 From: Clayton Wilson Date: Tue, 1 Oct 2019 16:08:53 -0400 Subject: [PATCH 07/11] Fix fbAuth function not extracting 'Bearer ' --- functions/util/fbAuth.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/functions/util/fbAuth.js b/functions/util/fbAuth.js index 04d71ed..a9243cc 100644 --- a/functions/util/fbAuth.js +++ b/functions/util/fbAuth.js @@ -7,8 +7,8 @@ module.exports = (req, res, next) => { let idToken; // Checking that the token exists in the header of the request - if (req.headers.authorization) { - idToken = req.headers.authorization; + if (req.headers.authorization && req.headers.authorization.startsWith('Bearer ')) { + idToken = req.headers.authorization.split('Bearer ')[1]; } else { console.error('No token found'); return res.status(403).json({ error: 'Unauthorized'}); From eb945fa0cfcc9b52b4c01eedb7dcc2cfd319dfe7 Mon Sep 17 00:00:00 2001 From: Clayton Wilson Date: Tue, 1 Oct 2019 16:31:37 -0400 Subject: [PATCH 08/11] Finished fixing login and signup backend. --- functions/handlers/users.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/functions/handlers/users.js b/functions/handlers/users.js index 70d6cbd..54dcf00 100644 --- a/functions/handlers/users.js +++ b/functions/handlers/users.js @@ -15,7 +15,7 @@ exports.signup = (req, res) => { handle: req.body.handle, password: req.body.password, confirmPassword: req.body.confirmPassword, - time: new Date().toISOString() + createdAt: new Date().toISOString() }; // console.log(newUser); @@ -76,7 +76,7 @@ exports.signup = (req, res) => { const userCred = { email: req.body.email, handle: newUser.handle, - time: newUser.time, + createdAt: newUser.createdAt, userId } return db.doc(`/users/${newUser.handle}`).set(userCred); @@ -100,20 +100,20 @@ exports.login = (req, res) => { } //Auth validation - let invalidCred = {}; + let errors = {}; //Email check if(user.email.trim() === '') { - invalidCred.email = 'Email must not be blank.'; + errors.email = 'Email must not be blank.'; } //Password check if(user.password.trim() === '') { - invalidCred.password = 'Password must not be blank.'; + errors.password = 'Password must not be blank.'; } //Overall check - if(Object.keys(invalidCred).length > 0) { + if(Object.keys(errors).length > 0) { return res.status(400).json(errors); } From b711521b4b5771b422a9431416ef82cd8bd81af5 Mon Sep 17 00:00:00 2001 From: Clayton Wilson Date: Tue, 1 Oct 2019 17:01:59 -0400 Subject: [PATCH 09/11] Fix unnecessary escape deploy error. --- functions/handlers/users.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/functions/handlers/users.js b/functions/handlers/users.js index 54dcf00..2a83ee7 100644 --- a/functions/handlers/users.js +++ b/functions/handlers/users.js @@ -22,7 +22,7 @@ exports.signup = (req, res) => { let errors = {}; - const emailRegEx = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@(([[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; + const emailRegEx = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; //Email check if(newUser.email.trim() === '') { From 8eb0872d16be44038a29f05a276af01aa090d83b Mon Sep 17 00:00:00 2001 From: Danny Voltz Date: Tue, 1 Oct 2019 17:27:52 -0500 Subject: [PATCH 10/11] logout ui --- twistter-frontend/src/Logout.js | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 twistter-frontend/src/Logout.js diff --git a/twistter-frontend/src/Logout.js b/twistter-frontend/src/Logout.js new file mode 100644 index 0000000..df36575 --- /dev/null +++ b/twistter-frontend/src/Logout.js @@ -0,0 +1,22 @@ +/* eslint-disable */ +import React, { Component} from 'react'; +import './App.css'; +import logo from './images/twistter-logo.png'; +import TextField from '@material-ui/core/TextField'; + +class Logout extends Component { + render() { + return( +
+ logo +

+ Logout of your Twistter Account +

+

+ +
+ ); + }; +} + +export default Logout; From fadb0f0399c47b217d6a77eeffaba88c30fa6b67 Mon Sep 17 00:00:00 2001 From: Danny Voltz Date: Tue, 1 Oct 2019 17:34:19 -0500 Subject: [PATCH 11/11] added logout support --- twistter-frontend/src/components/layout/NavBar.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/twistter-frontend/src/components/layout/NavBar.js b/twistter-frontend/src/components/layout/NavBar.js index e4ee5c5..0b6ba22 100644 --- a/twistter-frontend/src/components/layout/NavBar.js +++ b/twistter-frontend/src/components/layout/NavBar.js @@ -19,7 +19,9 @@ export class Navbar extends Component { - + )