diff --git a/app/config/passport.js b/app/config/passport.js index 9a3e072..b87fa07 100644 --- a/app/config/passport.js +++ b/app/config/passport.js @@ -25,28 +25,28 @@ module.exports = passport => { }, (req, accessToken, refreshToken, profile, done) => { - User.findOne({'google.id': profile.id}, (err, user) => { - - const name = { - first: profile.name.givenName, - last: profile.name.familyName - }, - email = profile.emails[0].value, - photo = profile.photos[0] && !profile._json.image.isDefault - ? profile.photos[0].value.replace("?sz=50", "?sz=200") - : strings.userPhotoUrl, - - google = { - id: profile.id, - accessToken: User.encryptAccessToken(accessToken), - refreshToken - }, - ip = req.clientIp; + const name = { + first: profile.name.givenName, + last: profile.name.familyName + }, + email = profile.emails[0].value, + photo = profile.photos[0] && !profile._json.image.isDefault + ? profile.photos[0].value.replace("?sz=50", "?sz=200") + : strings.userPhotoUrl, + + google = { + id: profile.id, + accessToken: User.encryptAccessToken(accessToken), + refreshToken + }, + ip = req.clientIp; + + User.findOne({'email': email}, (err, user) => { if (err) return done(err); if (!user) { - + console.log('did not find user creating new') user = new User({name, photo, email, google, ip}); user.save(err => { if (err) console.log(err); @@ -54,7 +54,7 @@ module.exports = passport => { }); } else { - + console.log('user already exists') User.findByIdAndUpdate(user._id, { 'google.accessToken': User.encryptAccessToken(accessToken), 'google.refreshToken': refreshToken, diff --git a/app/models/user.js b/app/models/user.js index f7f5df3..1663e5f 100644 --- a/app/models/user.js +++ b/app/models/user.js @@ -1,17 +1,26 @@ -var mongoose = require('mongoose'); -var Schema = mongoose.Schema; -var crypto = require('crypto'); +const mongoose = require('mongoose'); +const Schema = mongoose.Schema; +const crypto = require('crypto'); +const mongooseDelete = require('mongoose-delete'); -var keys = require.main.require('./app/config/keys'); -var strings = require.main.require('./app/config/strings'); +const keys = require.main.require('./app/config/keys'); +const strings = require.main.require('./app/config/strings'); -var UserSchema = new Schema({ +// Make email and phone docs unique except for those that are flagged as deleted +const uniquePartialIndex = { + unique: true, + partialFilterExpression: { + deleted: false + } +}; + +const UserSchema = new Schema({ name: { first: {type: String, required: true}, last: {type: String, required: true} }, - email: {type: String, unique: true, sparse: true}, - phone: {type: String, unique: true, sparse: true}, + email: {type: String}, + phone: {type: String}, photo: {type: String, default: strings.userPhotoUrl}, google: { id: {type: String}, @@ -44,6 +53,10 @@ UserSchema.statics.decryptAccessToken = function(cipher) { .update(cipher, 'hex', 'utf-8'); }; +UserSchema.plugin(mongooseDelete, {overrideMethods: true}); + +UserSchema.index({email: 1, phone: 1}, uniquePartialIndex); + var User = mongoose.model('User', UserSchema); module.exports = User; \ No newline at end of file diff --git a/app/routes/fact.routes.js b/app/routes/fact.routes.js index c993467..67717ac 100644 --- a/app/routes/fact.routes.js +++ b/app/routes/fact.routes.js @@ -79,7 +79,7 @@ router.get('/:factID', async (req, res) => { }); // Submit a fact -router.post('/submitted', async (req, res) => { +router.post('/', async (req, res) => { if (!req.user) { return res.status(401).json({message: strings.unauthenticated}); } diff --git a/app/routes/recipient.routes.js b/app/routes/recipient.routes.js index f135add..b8bce42 100644 --- a/app/routes/recipient.routes.js +++ b/app/routes/recipient.routes.js @@ -139,7 +139,7 @@ router.get('/:number/conversation', async (req, res) => { if (results[0]) { return res.status(200).json(results[1]); } else { - return res.status(401).json({message: "You aren't facting this person"}); + return res.status(403).json({message: "You aren't facting this person"}); } } diff --git a/app/routes/user.routes.js b/app/routes/user.routes.js index c1dd489..5162358 100644 --- a/app/routes/user.routes.js +++ b/app/routes/user.routes.js @@ -12,6 +12,25 @@ router.get('/me', (req, res) => { return res.status(401).json(false); }); +router.delete('/me', async (req, res) => { + // Confirm intention to delete by checking inputted email + if (!req.user) + return res.status(401).json({message: strings.unauthenticated}); + + if (req.user.email !== req.query.verificationEmail) + return res.status(403).json({message: 'Email addresses do not match'}); + + + try { + await User.delete({_id: req.user._id}); + return res.status(200).json({message: 'Account deleted'}); + } + catch (err) { + return res.status(200).json({message: err.message || 'Failed to delete account', err}); + } + +}); + router.put('/me/settings', async (req, res) => { if (!req.user) return res.status(401).json({message: strings.unauthenticated}); @@ -55,7 +74,7 @@ router.post('/me/profile/phone/verification-code', async (req, res) => { router.put('/me/profile/phone', async (req, res) => { if (!req.user) return res.status(401).json({message: strings.unauthenticated}); - if (!req.body.verificationCode) return res.status(401).json({message: strings.noVerificationCode}); + if (!req.body.verificationCode) return res.status(403).json({message: strings.noVerificationCode}); const submittedCode = req.body.verificationCode.trim(); const verificationCode = await VerificationCode.findOne({code: submittedCode}); diff --git a/docs/setup.md b/docs/setup.md index 9b50f49..a3ce3f1 100644 --- a/docs/setup.md +++ b/docs/setup.md @@ -110,7 +110,9 @@ 4. Send SMS - Number: `%RECIPIENT` - Message: `%DAILYFACT` - 5. End For + 5. Wait + - Seconds: 2 + 6. End For 6. Go back to the Profiles tab and create an event based profile for "Recieved Text Any" 7. Create a task for this profile called "CatBot Single" 8. This task will include these actions: diff --git a/public/js/controllers/profile.js b/public/js/controllers/profile.js index c6ef017..c6b76de 100644 --- a/public/js/controllers/profile.js +++ b/public/js/controllers/profile.js @@ -1,13 +1,14 @@ /* global angular */ var app = angular.module('catfacts'); -app.controller('ProfileCtrl', ['$scope', '$rootScope', 'ApiService', function($scope, $rootScope, ApiService) { +app.controller('ProfileCtrl', ['$scope', '$rootScope', 'ApiService', '$state', '$mdDialog', '$mdMedia', + function($scope, $rootScope, ApiService, $state, $mdDialog, $mdMedia) { $scope.newPhone = $rootScope.authenticatedUser ? $rootScope.authenticatedUser.phone : undefined; $scope.editField = $scope.editStep = null; - $scope.updatePhone = function(editStep) { + $scope.updatePhone = editStep => { switch (editStep) { case null: $scope.editField = 'phone'; @@ -19,7 +20,7 @@ app.controller('ProfileCtrl', ['$scope', '$rootScope', 'ApiService', function($s $scope.editStep = 'verify'; $rootScope.toast({message: "A verification code has been sent to your phone"}); }, err => { - $rootScope.toast({message: err.message || "Couldn't create verification code, try again later."}); + $rootScope.toast({message: err.message || "Couldn't create verification code, try again later"}); }); break; @@ -31,11 +32,40 @@ app.controller('ProfileCtrl', ['$scope', '$rootScope', 'ApiService', function($s $scope.newPhone = $scope.verificationCode = ''; $rootScope.toast({message: "New phone number saved"}); }, err => { - $rootScope.toast({message: err.message || "Couldn't update phone number, try again later."}); + $rootScope.toast({message: err.message || "Couldn't update phone number, try again later"}); }); break; } }; + + $scope.openDeleteAccountDialog = ev => { + $mdDialog.show({ + controller: ['$scope', '$mdDialog', function($scope, $mdDialog) { + + $scope.deleteAccount = () => { + ApiService.deleteAccount({verificationEmail: $scope.email}).then(data => { + $rootScope.authenticatedUser = undefined; + $mdDialog.hide(data); + }, err => { + $rootScope.toast({message: err.data.message || "Failed to delete account"}); + }); + }; + + $scope.cancel = $mdDialog.cancel; + }], + templateUrl: 'views/partials/delete-account.html', + parent: angular.element(document.body), + targetEvent: ev, + clickOutsideToClose: true, + fullscreen: $mdMedia('xs'), + scope: $scope, + preserveScope: true, + + }).then(data => { + $rootScope.toast({message: "Your account has been deleted"}); + $state.go('facts'); + }); + }; }]); \ No newline at end of file diff --git a/public/js/services/api.service.js b/public/js/services/api.service.js index a539de6..e87ac0c 100644 --- a/public/js/services/api.service.js +++ b/public/js/services/api.service.js @@ -9,6 +9,10 @@ app.service('ApiService', ['$rootScope', '$http', '$location', function($rootSco return $http.get('/users/me'); }; + this.deleteAccount = function({verificationEmail}) { + return $http.delete('/users/me', {params: {verificationEmail}}); + }; + this.updateUserSettings = function(data) { return $http.put('/users/me/settings', data).then(data => console.log(data), err => console.log(err)); }; diff --git a/public/views/partials/delete-account.html b/public/views/partials/delete-account.html new file mode 100644 index 0000000..5eb5270 --- /dev/null +++ b/public/views/partials/delete-account.html @@ -0,0 +1,25 @@ + + +

+ Permanently delete your account? +

+ +

This action cannot be undone

+ + + + + +
+ + + Cancel + + + Delete + + +
\ No newline at end of file diff --git a/public/views/partials/profile.html b/public/views/partials/profile.html index e318bf5..efcfe99 100644 --- a/public/views/partials/profile.html +++ b/public/views/partials/profile.html @@ -80,7 +80,7 @@

Delete account

Remove your account from Cat Facts

- + Delete account