diff --git a/.firebaserc b/.firebaserc index 0b3cf99..2e9e776 100644 --- a/.firebaserc +++ b/.firebaserc @@ -5,4 +5,4 @@ "Twistter1": "twistter-6a42d", "SuperTiger69": "twistter-e4649" } -} \ No newline at end of file +} diff --git a/firebase.json b/firebase.json index ad362e4..0e2c6a7 100644 --- a/firebase.json +++ b/firebase.json @@ -1,25 +1,30 @@ { - "database": { - "rules": "database.rules.json" - }, - "firestore": { - "rules": "firestore.rules", - "indexes": "firestore.indexes.json" - }, - "functions": { - "predeploy": [ - "npm --prefix \"$RESOURCE_DIR\" run lint" - ] - }, - "hosting": { - "public": "public", - "ignore": [ - "firebase.json", - "**/.*", - "**/node_modules/**" - ] - }, - "storage": { - "rules": "storage.rules" + "database": { + "rules": "database.rules.json" + }, + "firestore": { + "rules": "firestore.rules", + "indexes": "firestore.indexes.json" + }, + "functions": { + "predeploy": [ + "npm --prefix \"$RESOURCE_DIR\" run lint" + ] + }, + "hosting": { + "public": "public", + "ignore": [ + "firebase.json", + "**/.*", + "**/node_modules/**" + ], + "rewrites": [{ + "source": "/feed", + "destination": "/feed.html" + }] + }, + "storage": { + "rules": "storage.rules" + } } -} + \ No newline at end of file diff --git a/functions/handlers/users.js b/functions/handlers/users.js index c165ae0..6c5941f 100644 --- a/functions/handlers/users.js +++ b/functions/handlers/users.js @@ -1,3 +1,4 @@ +/* eslint-disable promise/catch-or-return */ const {db} = require('../util/admin'); const {validateUpdateProfileInfo} = require('../util/validator'); diff --git a/functions/index.js b/functions/index.js index f93af4c..6ff3bd3 100644 --- a/functions/index.js +++ b/functions/index.js @@ -4,7 +4,231 @@ const app = require('express')(); const cors = require('cors'); app.use(cors()); -const fbAuth = require('./util/fbAuth'); +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 firebase = require('firebase'); +firebase.initializeApp(config); + +// 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; + + 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 * diff --git a/functions/package-lock.json b/functions/package-lock.json index c1ee09a..75b8c4e 100644 --- a/functions/package-lock.json +++ b/functions/package-lock.json @@ -70,9 +70,15 @@ "integrity": "sha512-foQHhvyB0RR+mb/+wmHXd/VOU+D8fruFEW1k79Q9wzyTPpovMBa1Mcns5fwEWBhUfi8bmoEtaGB8RSAHnTFzTg==" }, "@firebase/database": { +<<<<<<< HEAD + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.5.3.tgz", + "integrity": "sha512-LnXKRE1AmjlS+iRF7j8vx+Ni8x85CmLP5u5Pw5rDKhKLn2eTR1tJKD937mUeeGEtDHwR1rrrkLYOqRR2cSG3hQ==", +======= "version": "0.5.4", "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.5.4.tgz", "integrity": "sha512-Hz1Bi3fzIcNNocE4EhvvwoEQGurG2BGssWD3/6a2bzty+K1e57SLea2Ied8QYNBUU1zt/4McHfa3Y71EQIyn/w==", +>>>>>>> 7969d3b10bc35a9078834c5ee2ba8c8fd60d338f "requires": { "@firebase/database-types": "0.4.3", "@firebase/logger": "0.1.25", @@ -107,7 +113,11 @@ "@firebase/firestore": { "version": "1.5.3", "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-1.5.3.tgz", +<<<<<<< HEAD + "integrity": "sha512-CPYLvkGZBKE47oQC9a0q13UMVRj3LvnSbB1nOerktE3CGRHKy44LxDumamN8Kj067hV/80mKK9FdbeUufwO/Rg==", +======= "integrity": "sha512-O/yAbXpitOA6g627cUl0/FHYlkTy1EiEKMKOlnlMOJF2fH+nLVZREXjsrCC7N2tIvTn7yYwfpZ4zpSNvrhwiTA==", +>>>>>>> 7969d3b10bc35a9078834c5ee2ba8c8fd60d338f "requires": { "@firebase/firestore-types": "1.5.0", "@firebase/logger": "0.1.25", @@ -613,7 +623,7 @@ "ansi-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", "dev": true }, "ansi-styles": { @@ -824,28 +834,28 @@ "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "requires": { "number-is-nan": "^1.0.0" - } - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "requires": { - "ansi-regex": "^2.0.0" - } - } } }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + } + } +}, "code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", @@ -1184,9 +1194,15 @@ } }, "end-of-stream": { +<<<<<<< HEAD + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", + "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", +======= "version": "1.4.3", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.3.tgz", "integrity": "sha512-cbNhPFS6MlYlWTGncSiDYbdqKhwWFy7kNeb1YSOG6K65i/wPTkLVCJQj0hXA4j0m5Da+hBWnqopEnu1FFelisQ==", +>>>>>>> 7969d3b10bc35a9078834c5ee2ba8c8fd60d338f "optional": true, "requires": { "once": "^1.4.0" @@ -1675,7 +1691,8 @@ "functional-red-black-tree": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=" + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "optional": true }, "gaxios": { "version": "2.0.1", @@ -2223,9 +2240,15 @@ } }, "gtoken": { +<<<<<<< HEAD + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-4.0.0.tgz", + "integrity": "sha512-XaRCfHJxhj06LmnWNBzVTAr85NfAErq0W1oabkdqwbq3uL/QTB1kyvGog361Uu2FMG/8e3115sIy/97Rnd4GjQ==", +======= "version": "4.1.0", "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-4.1.0.tgz", "integrity": "sha512-wqyn2gf5buzEZN4QNmmiiW2i2JkEdZnL7Z/9p44RtZqgt4077m4khRgAYNuu8cBwHWCc6MsP6eDUn/KkF6jFIw==", +>>>>>>> 7969d3b10bc35a9078834c5ee2ba8c8fd60d338f "optional": true, "requires": { "gaxios": "^2.0.0", @@ -2399,7 +2422,8 @@ "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "optional": true }, "inflight": { "version": "1.0.6", @@ -2414,7 +2438,8 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "optional": true }, "inquirer": { "version": "6.5.2", @@ -2479,8 +2504,10 @@ "is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "^1.0.0" + } }, "is-obj": { "version": "2.0.0", @@ -2878,6 +2905,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "optional": true, "requires": { "wrappy": "1" } @@ -3322,7 +3350,8 @@ "signal-exit": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "optional": true }, "slice-ansi": { "version": "2.1.0", @@ -3375,9 +3404,9 @@ "string-width": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "requires": { + "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^2.0.0", "strip-ansi": "^4.0.0" } @@ -3391,7 +3420,7 @@ "strip-ansi": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { "ansi-regex": "^3.0.0" @@ -3702,7 +3731,8 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "optional": true }, "write": { "version": "1.0.3", diff --git a/functions/package.json b/functions/package.json index 8ca2723..ef1db23 100644 --- a/functions/package.json +++ b/functions/package.json @@ -10,7 +10,7 @@ "logs": "firebase functions:log" }, "engines": { - "node": "8" + "node": "10" }, "dependencies": { "firebase": "^6.6.2", diff --git a/twistter-frontend/package.json b/twistter-frontend/package.json index 8176b1a..3561137 100644 --- a/twistter-frontend/package.json +++ b/twistter-frontend/package.json @@ -22,5 +22,17 @@ "build": "react-scripts build", "test": "react-scripts test --env=jsdom", "eject": "react-scripts eject" + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] } } diff --git a/twistter-frontend/src/App.js b/twistter-frontend/src/App.js index 1fea229..e6efa6c 100644 --- a/twistter-frontend/src/App.js +++ b/twistter-frontend/src/App.js @@ -6,6 +6,7 @@ import './App.css'; import { BrowserRouter as Router } from 'react-router-dom'; import Route from 'react-router-dom/Route'; +import NavBar, { Navbar } from './components/layout/NavBar'; import home from './Home.js'; import register from './Register.js'; @@ -18,7 +19,9 @@ class App extends Component { render() { return ( - +
+ +
diff --git a/twistter-frontend/src/Login.js b/twistter-frontend/src/Login.js index 1c615b7..550ed54 100644 --- a/twistter-frontend/src/Login.js +++ b/twistter-frontend/src/Login.js @@ -5,8 +5,6 @@ import './App.css'; import logo from './images/twistter-logo.png'; import TextField from '@material-ui/core/TextField'; -import axios from 'axios'; - class Login extends Component { render() { return ( diff --git a/twistter-frontend/src/Register.js b/twistter-frontend/src/Register.js index 7f06806..c67d8c2 100644 --- a/twistter-frontend/src/Register.js +++ b/twistter-frontend/src/Register.js @@ -4,8 +4,6 @@ import './App.css'; import logo from './images/twistter-logo.png'; import TextField from '@material-ui/core/TextField'; -//import axios from 'axios'; - class Register extends Component { render() { return ( diff --git a/twistter-frontend/src/components/layout/NavBar.js b/twistter-frontend/src/components/layout/NavBar.js index 9a7d6eb..e4ee5c5 100644 --- a/twistter-frontend/src/components/layout/NavBar.js +++ b/twistter-frontend/src/components/layout/NavBar.js @@ -10,8 +10,8 @@ export class Navbar extends Component { return ( - - + )