From 5025c4bac6884cd7fccfac6f490f09c63a601a50 Mon Sep 17 00:00:00 2001 From: 3onyc <3onyc@x3tech.com> Date: Thu, 12 Jun 2014 16:34:10 +0200 Subject: [PATCH 01/16] Implement "Change username" feature --- public/js/controllers/settingsCtrl.js | 14 ++++++++++++++ src/controllers/auth.js | 25 +++++++++++++++++++++++++ src/routes/auth.js | 1 + views/options/settings.jade | 12 ++++++++++++ 4 files changed, 52 insertions(+) diff --git a/public/js/controllers/settingsCtrl.js b/public/js/controllers/settingsCtrl.js index 5bbb13be86..bff15f4911 100644 --- a/public/js/controllers/settingsCtrl.js +++ b/public/js/controllers/settingsCtrl.js @@ -65,6 +65,20 @@ habitrpg.controller('SettingsCtrl', $rootScope.$state.go('tasks'); } + $scope.changeUsername = function(changeUser){ + if (!changeUser.newUsername || !changeUser.password) { + return alert(window.env.t('fillAll')); + } + $http.post(API_URL + '/api/v2/user/change-username', changeUser) + .success(function(){ + alert(window.env.t('usernameSuccess')); + $scope.changeUser = {}; + }) + .error(function(data){ + alert(data); + }); + } + $scope.changePassword = function(changePass){ if (!changePass.oldPassword || !changePass.newPassword || !changePass.confirmNewPassword) { return alert(window.env.t('fillAll')); diff --git a/src/controllers/auth.js b/src/controllers/auth.js index 42cfe4da1b..50da8f6c60 100644 --- a/src/controllers/auth.js +++ b/src/controllers/auth.js @@ -195,6 +195,31 @@ api.resetPassword = function(req, res, next){ }); }; +api.changeUsername = function(req, res, next) { + var user = res.locals.user, + password = req.body.password + newUsername = req.body.newUsername; + + User.findOne({'auth.local.username': newUsername}, function(err, result) { + if (err) next(err); + + if(result) + return res.json(401, {err: "Username already taken"}); + + var salt = user.auth.local.salt, + hashed_password = utils.encryptPassword(password, salt); + + if (hashed_password !== user.auth.local.hashed_password) + return res.json(401, {err:"Incorrect password"}); + + user.auth.local.username = newUsername; + user.save(function(err, saved){ + if (err) next(err); + res.send(200); + }) + }); +} + api.changePassword = function(req, res, next) { var user = res.locals.user, oldPassword = req.body.oldPassword, diff --git a/src/routes/auth.js b/src/routes/auth.js index edc54bc81d..616f9cf7d6 100644 --- a/src/routes/auth.js +++ b/src/routes/auth.js @@ -10,6 +10,7 @@ router.post('/api/v2/user/auth/local', i18n.getUserLanguage, auth.loginLocal); router.post('/api/v2/user/auth/facebook', i18n.getUserLanguage, auth.loginFacebook); router.post('/api/v2/user/reset-password', i18n.getUserLanguage, auth.resetPassword); router.post('/api/v2/user/change-password', i18n.getUserLanguage, auth.auth, auth.changePassword); +router.post('/api/v2/user/change-username', i18n.getUserLanguage, auth.auth, auth.changeUsername); router.post('/api/v1/register', i18n.getUserLanguage, auth.registerUser); router.post('/api/v1/user/auth/local', i18n.getUserLanguage, auth.loginLocal); diff --git a/views/options/settings.jade b/views/options/settings.jade index 7aa325c224..f0fc9bbf24 100644 --- a/views/options/settings.jade +++ b/views/options/settings.jade @@ -79,6 +79,18 @@ script(type='text/ng-template', id='partials/options.settings.settings.html') input.form-control(type='text', ng-model='_couponCode', placeholder='Enter Coupon Code') button.btn.btn-primary(type='submit') Submit + div(ng-if='user.auth.local.username') + .panel.panel-default + .panel-heading + span=env.t('changeUsername') + .panel-body + form(ng-submit='changeUsername(changeUser)', ng-show='user.auth.local') + .form-group + input.form-control(type='text', placeholder=env.t('newUsername'), ng-model='changeUser.newUsername', required) + .form-group + input.form-control(type='password', placeholder=env.t('password'), ng-model='changeUser.password', required) + input.btn.btn-default(type='submit', value=env.t('submit')) + //- Why is ng-if='user.auth.local' validating for users *without* user.auth.local (facebook users)? adding .username here for extra div(ng-if='user.auth.local.username') .panel.panel-default From 34236536df4038a2999d34aba7be9083fbd03958 Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Thu, 17 Jul 2014 14:09:39 -0600 Subject: [PATCH 02/16] feat(admin): allow admins to update user's items (restoring lost items, granting quest scrolls, etc). @deilann :) --- public/js/controllers/rootCtrl.js | 1 + src/controllers/hall.js | 13 +++++++++- src/controllers/user.js | 7 ++++++ src/routes/apiv2.coffee | 4 +++ views/options/social/hall.jade | 41 +++++++++++++++++++------------ 5 files changed, 49 insertions(+), 17 deletions(-) diff --git a/public/js/controllers/rootCtrl.js b/public/js/controllers/rootCtrl.js index 7d5dd189b4..db264e7a7c 100644 --- a/public/js/controllers/rootCtrl.js +++ b/public/js/controllers/rootCtrl.js @@ -28,6 +28,7 @@ habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$ $rootScope.env = window.env; $rootScope.Math = Math; $rootScope.Groups = Groups; + $rootScope.toJson = angular.toJson; // Angular UI Router $rootScope.$state = $state; diff --git a/src/controllers/hall.js b/src/controllers/hall.js index 0edbd9e867..754af8649e 100644 --- a/src/controllers/hall.js +++ b/src/controllers/hall.js @@ -38,7 +38,7 @@ api.getPatrons = function(req,res,next){ api.getHero = function(req,res,next) { User.findById(req.params.uid) - .select('contributor balance profile.name purchased') + .select('contributor balance profile.name purchased items') .exec(function(err, user){ if (err) return next(err) if (!user) return res.json(400,{err:'User not found'}); @@ -61,6 +61,17 @@ api.updateHero = function(req,res,next) { member.contributor = req.body.contributor; member.purchased.ads = req.body.purchased.ads; if (member.contributor.level >= 6) member.items.pets['Dragon-Hydra'] = 5; + if (req.body.itemPath && req.body.itemVal + && req.body.itemPath.indexOf('items.')===0 + && User.schema.paths[req.body.itemPath]) { + // TODO remove below after verified. Seems express handles type-casting just fine + //var itemVal = req.body.itemVal;itemVal = + // itemVal === 'false' ? false: itemVal === 'true' ? true: // boolean + // _.isNaN(parseInt(itemVal)) ? itemVal : // string + // _.isDate(itemVal) ? itemVal : // date + // parseInt(itemVal); // number + shared.dotSet(member, req.body.itemPath, req.body.itemVal); + } member.save(cb); } ], function(err, saved){ diff --git a/src/controllers/user.js b/src/controllers/user.js index b43f259240..4988fff71c 100644 --- a/src/controllers/user.js +++ b/src/controllers/user.js @@ -29,6 +29,13 @@ api.getContent = function(req, res, next) { res.json(content); } +api.getModelPaths = function(req,res,next){ + res.json(_.reduce(User.schema.paths,function(m,v,k){ + m[k] = v.instance || 'Boolean'; + return m; + },{})); +} + /* ------------------------------------------------------------------------ Tasks diff --git a/src/routes/apiv2.coffee b/src/routes/apiv2.coffee index c126702d13..d40ca53e1e 100644 --- a/src/routes/apiv2.coffee +++ b/src/routes/apiv2.coffee @@ -46,6 +46,10 @@ module.exports = (swagger, v2) -> ] action: user.getContent + '/content/paths': + spec: + description: "Show user model tree" + action: user.getModelPaths "/export/history": spec: diff --git a/views/options/social/hall.jade b/views/options/social/hall.jade index 9d348ca7b9..ea7b99271b 100644 --- a/views/options/social/hall.jade +++ b/views/options/social/hall.jade @@ -37,24 +37,33 @@ script(type='text/ng-template', id='partials/options.social.hall.heroes.html') label input(type='checkbox', ng-model='hero.contributor.admin') =env.t('admin') + hr - hr + .form-group + label=env.t('balance') + input.form-control(type='number', step="any", ng-model='hero.balance') + small!= '`user.balance`' + env.t('notGems') + .form-group + .checkbox + label + input(type='checkbox', ng-model='hero.purchased.ads') + =env.t('hideAds') + accordion + accordion-group(heading='Items') + h4 Update Item + .form-group.well + input.form-control(type='text',placeholder='Path (eg, items.pets.BearCub-Base)',ng-model='hero.itemPath') + small.muted Enter the item path. E.g., items.pets.BearCub-Zombie or items.gear.owned.head_special_0 or items.gear.equipped.head. See all paths here. When in doubt, ask Tyler. + br + input.form-control(type='text',placeholder='Value (eg, 5)',ng-model='hero.itemVal') + small.muted Enter the item value. E.g., 5 or false or head_warrior_3 (respectively from above examples). + h4 Current Items + pre {{::toJson(hero.items,true)}} - .form-group - label=env.t('balance') - input.form-control(type='number', step="any", ng-model='hero.balance') - small!= '`user.balance`' + env.t('notGems') - - .form-group - .checkbox - label - input(type='checkbox', ng-model='hero.purchased.ads') - =env.t('hideAds') - - // h4 Backer Status - // Add backer stuff like tier, disable adds, etcs - .form-group - input.form-control.btn.btn-primary(type='submit')=env.t('save') + // h4 Backer Status + // Add backer stuff like tier, disable adds, etcs + .form-group + input.form-control.btn.btn-primary(type='submit')=env.t('save') table.table.table-striped From b946338184d1ae392621ab09824405ff3c386c1a Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Thu, 17 Jul 2014 18:37:09 -0600 Subject: [PATCH 03/16] feat(dilatory): add dilatory achievement --- views/shared/profiles/achievements.jade | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/views/shared/profiles/achievements.jade b/views/shared/profiles/achievements.jade index 7ea8eabee0..ff58cbcc57 100644 --- a/views/shared/profiles/achievements.jade +++ b/views/shared/profiles/achievements.jade @@ -154,3 +154,11 @@ div(ng-if='::profile.achievements.valentine') small =env.t('adoringFriendsText', {cards: "{{::profile.achievements.valentine}}"}) hr + +div(ng-if='::profile.achievements.quests.dilatory') + .achievement.achievement-dilatory + h5=env.t('achievementDilatory') + small + =env.t('achievementDilatoryText') + hr + From a4b4063b8d7f23d649703d6c88ac7b7ec97e097f Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Fri, 18 Jul 2014 11:58:25 +0200 Subject: [PATCH 04/16] remove beta from api call examples The API documentation examples used beta.habitrpg.com as url. Additionally, the base URL was specified including "/user/". removed both to avoid confusion and make copy-paste easier --- views/static/api.jade | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/views/static/api.jade b/views/static/api.jade index d9ee4e4334..d33d4e370c 100644 --- a/views/static/api.jade +++ b/views/static/api.jade @@ -80,7 +80,7 @@ html h2 Extensions / Scripts p HabitRPG has a simple API for up-scoring and down-scoring third party Habits: POST /api/v2/user/tasks/{id}/{direction} (headers x-api-user and x-api-key required). h4 Example - p curl -X POST -H "x-api-key: YOUR_API_TOKEN" -H "x-api-user: YOUR_USER_ID" https://beta.habitrpg.com/api/v2/user/tasks/productivity/up + p curl -X POST -H "x-api-key: YOUR_API_TOKEN" -H "x-api-user: YOUR_USER_ID" https://habitrpg.com/api/v2/user/tasks/productivity/up p Note: You may need to add --compressed -H "Content-Type:application/json" to your curl if you get errors. ul li POST to the URL /api/v2/user/tasks/{id}/{direction} @@ -91,7 +91,7 @@ html h2 Full API p All API requests should be prefaced by https://habitrpg.com. Every authenticated request should include two headers. Your api key (x-api-key) and your user id (x-api-user). Do not include {} braces in your header (-H 'x-api-user: a94b6d9d-6b64-43ae-856c-2c3f211bd426') h2 Requirements: - p The base-url for all routes is /api/v2. So /user actions will be at https://beta.habitrpg.com/api/v2/user/*. You need to send x-api-user and x-api-key headers for each request. + p The base-url for all routes is /api/v2. So /user actions will be at https://habitrpg.com/api/v2/*. You need to send x-api-user and x-api-key headers for each request. p For create & edit paths (PUT & POST), you'll need to know the schema of the object you're trying to create or edit. See Schema definitions here p If any of the documentation is lacking or you're having trouble with it, please post an issue to Github #message-bar.swagger-ui-wrap From 3ed977dad6fd0e5a8abaf0f54319c6bb878765f7 Mon Sep 17 00:00:00 2001 From: Skyrunner Date: Sun, 20 Jul 2014 16:54:18 +0900 Subject: [PATCH 05/16] Fixed #3756? It seems to be fixed, but I don't know the good development habits for jade/whatever technology it is. Please comment & close if the fix is not acceptable. --- views/options/social/group.jade | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/views/options/social/group.jade b/views/options/social/group.jade index 6727f89eb3..78092b630a 100644 --- a/views/options/social/group.jade +++ b/views/options/social/group.jade @@ -3,7 +3,7 @@ a.pull-right.gem-wallet(popover-trigger='mouseenter', popover-title=env.t('guild span.task-action-btn.tile.flush.neutral .Pet_Currency_Gem2x.Gems | {{group.balance * 4 | number:0 }} - =env.t('guildGems') + =' ' + env.t('guildGems') .container-fluid .row From 5cc4c6032927e862f8e1047952cc3dc8f43e4e05 Mon Sep 17 00:00:00 2001 From: David Marshall Date: Mon, 14 Jul 2014 01:56:42 -0700 Subject: [PATCH 06/16] Edit stripe cc subscriptions --- public/js/controllers/rootCtrl.js | 20 ++++++++++++++++++++ src/controllers/payments.js | 26 ++++++++++++++++++++++++++ src/routes/payments.js | 1 + views/options/settings.jade | 1 + 4 files changed, 48 insertions(+) diff --git a/public/js/controllers/rootCtrl.js b/public/js/controllers/rootCtrl.js index 7d5dd189b4..22a2b05437 100644 --- a/public/js/controllers/rootCtrl.js +++ b/public/js/controllers/rootCtrl.js @@ -121,6 +121,26 @@ habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$ }); } + $rootScope.showStripeEdit = function(){ + StripeCheckout.open({ + key: window.env.STRIPE_PUB_KEY, + address: false, + name: 'Update', + description: 'Update the card to be charged for your subscription', + panelLabel: 'Update Card', + token: function(data) { + var url = '/stripe/subscribe/edit?plan=basic_earned'; + $scope.$apply(function(){ + $http.post(url, data).success(function() { + window.location.reload(true); + }).error(function(err) { + alert(err); + }); + }) + } + }); + } + $rootScope.cancelSubscription = function(){ if (!confirm(window.env.t('sureCancelSub'))) return; window.location.href = '/' + user.purchased.plan.paymentMethod.toLowerCase() + '/subscribe/cancel?_id=' + user._id + '&apiToken=' + user.apiToken; diff --git a/src/controllers/payments.js b/src/controllers/payments.js index 86b45bcbdf..fb3f5cdf81 100644 --- a/src/controllers/payments.js +++ b/src/controllers/payments.js @@ -142,6 +142,32 @@ api.stripeSubscribeCancel = function(req, res, next) { }); } +api.stripeSubscribeEdit = function(req, res, next) { + var stripe = require("stripe")(nconf.get('STRIPE_API_KEY')); + var token = req.body.id; + var user = res.locals.user; + var user_id = user.purchased.plan.customerId; + var sub_id; + + async.waterfall([ + function(cb){ + stripe.customers.listSubscriptions(user_id, cb); + }, + function(response, cb) { + sub_id = response.data[0].id; + console.warn(sub_id); + console.warn([user_id, sub_id, { card: token }]); + stripe.customers.updateSubscription(user_id, sub_id, { card: token }, cb); + }, + function(response, cb) { + user.save(cb); + } + ], function(err, saved){ + if (err) return res.send(500, err.toString()); // don't json this, let toString() handle errors + res.send(200); + }); +} + api.paypalSubscribe = function(req,res,next) { var uuid = res.locals.user._id; // Authenticate a future subscription of ~5 USD diff --git a/src/routes/payments.js b/src/routes/payments.js index c290e79b73..c34d77b24b 100644 --- a/src/routes/payments.js +++ b/src/routes/payments.js @@ -13,6 +13,7 @@ router.get('/paypal/subscribe/cancel', auth.authWithUrl, i18n.getUserLanguage, p router.post('/paypal/ipn', i18n.getUserLanguage, payments.paypalIPN); // misc ipn handling router.post("/stripe/checkout", auth.auth, i18n.getUserLanguage, payments.stripeCheckout); +router.post("/stripe/subscribe/edit", auth.auth, i18n.getUserLanguage, payments.stripeSubscribeEdit) //router.get("/stripe/subscribe", auth.authWithUrl, i18n.getUserLanguage, payments.stripeSubscribe); // checkout route is used (above) with ?plan= instead router.get("/stripe/subscribe/cancel", auth.authWithUrl, i18n.getUserLanguage, payments.stripeSubscribeCancel); diff --git a/views/options/settings.jade b/views/options/settings.jade index e82bb5aa31..13d4b37f9a 100644 --- a/views/options/settings.jade +++ b/views/options/settings.jade @@ -207,4 +207,5 @@ script(id='partials/options.settings.subscription.html',type='text/ng-template') |   span.glyphicon.glyphicon-ok div(ng-include="'partials/options.settings.subscription.perks.html'") + .btn.btn-primary(ng-if='!user.purchased.plan.dateTerminated', ng-click='showStripeEdit()') Update Card .btn.btn-sm.btn-danger(ng-if='!user.purchased.plan.dateTerminated', ng-click='cancelSubscription()')=env.t('cancelSub') \ No newline at end of file From 1361b71b6c2a507b2f0193dc32ebd965629a6125 Mon Sep 17 00:00:00 2001 From: David Marshall Date: Mon, 21 Jul 2014 13:40:05 -0700 Subject: [PATCH 07/16] Pin karma-mocha to 0.1.3, fixes CI --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9445060c7f..595511e185 100644 --- a/package.json +++ b/package.json @@ -85,7 +85,7 @@ "karma-ng-html2js-preprocessor": "~0.1.0", "karma-chai-plugins": "~0.1.0", "mocha": "~1.12.1", - "karma-mocha": "~0.1.0", + "karma-mocha": "~0.1.3", "csv": "~0.3.6", "mongoskin": "~0.6.1", "expect.js": "~0.2.0", From 3bbb4e6966a0bb5edb75de9a63484c1a470c55ba Mon Sep 17 00:00:00 2001 From: David Marshall Date: Mon, 21 Jul 2014 13:52:03 -0700 Subject: [PATCH 08/16] Exactly request karma-mocha 0.1.3, cause tilda was loading 0.1.6 on CI --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 595511e185..a30fed03cf 100644 --- a/package.json +++ b/package.json @@ -85,7 +85,7 @@ "karma-ng-html2js-preprocessor": "~0.1.0", "karma-chai-plugins": "~0.1.0", "mocha": "~1.12.1", - "karma-mocha": "~0.1.3", + "karma-mocha": "0.1.3", "csv": "~0.3.6", "mongoskin": "~0.6.1", "expect.js": "~0.2.0", From a1f2375b9df6a6b6dcb61c0fd38e7b4dd2dcbd0b Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Mon, 21 Jul 2014 14:55:36 -0600 Subject: [PATCH 09/16] feat(subscription): disable "update card" for paypal subscribers --- views/options/settings.jade | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/views/options/settings.jade b/views/options/settings.jade index df1fce0ee8..12fa09d5dc 100644 --- a/views/options/settings.jade +++ b/views/options/settings.jade @@ -219,5 +219,5 @@ script(id='partials/options.settings.subscription.html',type='text/ng-template') |   span.glyphicon.glyphicon-ok div(ng-include="'partials/options.settings.subscription.perks.html'") - .btn.btn-primary(ng-if='!user.purchased.plan.dateTerminated', ng-click='showStripeEdit()') Update Card - .btn.btn-sm.btn-danger(ng-if='!user.purchased.plan.dateTerminated', ng-click='cancelSubscription()')=env.t('cancelSub') \ No newline at end of file + .btn.btn-primary(ng-if=':: !user.purchased.plan.dateTerminated && user.purchased.plan.paymentMethod=="Stripe"', ng-click='showStripeEdit()') Update Card + .btn.btn-sm.btn-danger(ng-if=':: !user.purchased.plan.dateTerminated', ng-click='cancelSubscription()')=env.t('cancelSub') \ No newline at end of file From bf29a19d33d7c311edf13fc45bb758a57d8ec257 Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Mon, 21 Jul 2014 19:08:16 -0600 Subject: [PATCH 10/16] chore(errors): allow disabling error emails (getting too many VersionError, which maxes our limit and people can't use "Forgot Password" feature) --- src/logging.js | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/logging.js b/src/logging.js index f8e56da7cf..5e1449db3d 100644 --- a/src/logging.js +++ b/src/logging.js @@ -9,17 +9,19 @@ if (logger == null) { logger = new (winston.Logger)({}); if (nconf.get('NODE_ENV') == 'production') { logger.add(winston.transports.newrelic, {}); - logger.add(winston.transports.Mail, { - to: nconf.get('ADMIN_EMAIL') || nconf.get('SMTP_USER'), - from: "HabitRPG <" + nconf.get('SMTP_USER') + ">", - subject: "HabitRPG Error", - host: nconf.get('SMTP_HOST'), - port: nconf.get('SMTP_PORT'), - tls: nconf.get('SMTP_TLS'), - username: nconf.get('SMTP_USER'), - password: nconf.get('SMTP_PASS'), - level: 'error' - }); + if (!nconf.get('DISABLE_ERROR_EMAILS')) { + logger.add(winston.transports.Mail, { + to: nconf.get('ADMIN_EMAIL') || nconf.get('SMTP_USER'), + from: "HabitRPG <" + nconf.get('SMTP_USER') + ">", + subject: "HabitRPG Error", + host: nconf.get('SMTP_HOST'), + port: nconf.get('SMTP_PORT'), + tls: nconf.get('SMTP_TLS'), + username: nconf.get('SMTP_USER'), + password: nconf.get('SMTP_PASS'), + level: 'error' + }); + } } else { logger.add(winston.transports.Console, {colorize:true}); logger.add(winston.transports.File, {filename: 'habitrpg.log'}); From 439828d2b1f005286dcd836691818d9bf714435d Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Mon, 21 Jul 2014 22:27:25 -0600 Subject: [PATCH 11/16] feat(coupons): update wording @veeeeeee --- src/controllers/coupon.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/coupon.js b/src/controllers/coupon.js index 6e332f4093..2bad8cb1c2 100644 --- a/src/controllers/coupon.js +++ b/src/controllers/coupon.js @@ -24,7 +24,7 @@ api.getCoupons = function(req,res,next) { Coupon.find({},{}, options, function(err,coupons){ //res.header('Content-disposition', 'attachment; filename=coupons.csv'); res.csv([['code']].concat(_.map(coupons, function(c){ - return ['HabitRPG - To redeem your code, go to goo.gl/azsGaH and enter '+c._id]; + return ["Hi, it's nice to meet you! Want some exclusive UnConventional Armor? You can redeem the coupon code at the end of this message at http://habitrpg.com/#/options/settings/coupon (If you haven't already created an account, you'll need to do that first.) See you in the game! COUPON CODE: "+ c._id]; }))); }); } From ca31d46e0e8270e9eaa06b2a455b779ece13723d Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Mon, 21 Jul 2014 22:39:36 -0600 Subject: [PATCH 12/16] tmp(coupons): just export c._id to csv --- src/controllers/coupon.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/coupon.js b/src/controllers/coupon.js index 2bad8cb1c2..4907729d7b 100644 --- a/src/controllers/coupon.js +++ b/src/controllers/coupon.js @@ -24,7 +24,7 @@ api.getCoupons = function(req,res,next) { Coupon.find({},{}, options, function(err,coupons){ //res.header('Content-disposition', 'attachment; filename=coupons.csv'); res.csv([['code']].concat(_.map(coupons, function(c){ - return ["Hi, it's nice to meet you! Want some exclusive UnConventional Armor? You can redeem the coupon code at the end of this message at http://habitrpg.com/#/options/settings/coupon (If you haven't already created an account, you'll need to do that first.) See you in the game! COUPON CODE: "+ c._id]; + return [c._id]; }))); }); } From 5c309444e050160b7d7852d3f03508acde862395 Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Tue, 22 Jul 2014 18:35:55 -0600 Subject: [PATCH 13/16] fix(coupons): fix to limit --- src/controllers/coupon.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/coupon.js b/src/controllers/coupon.js index 4907729d7b..233be59078 100644 --- a/src/controllers/coupon.js +++ b/src/controllers/coupon.js @@ -20,7 +20,7 @@ api.generateCoupons = function(req,res,next) { api.getCoupons = function(req,res,next) { var options = {sort:'seq'}; if (req.query.limit) options.limit = req.query.limit; - if (req.query.skip) options.limit = req.query.skip; + if (req.query.skip) options.skip = req.query.skip; Coupon.find({},{}, options, function(err,coupons){ //res.header('Content-disposition', 'attachment; filename=coupons.csv'); res.csv([['code']].concat(_.map(coupons, function(c){ From 1808dea9ecdf8b940f52549a209d842ea068675f Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Mon, 21 Jul 2014 14:20:22 -0600 Subject: [PATCH 14/16] bailey(mystery): july mystery item --- migrations/mysteryitems.js | 2 +- views/shared/new-stuff.jade | 44 ++++++++++++++++++++++--------------- 2 files changed, 27 insertions(+), 19 deletions(-) diff --git a/migrations/mysteryitems.js b/migrations/mysteryitems.js index 473b2ff43d..5b2cf2ce33 100644 --- a/migrations/mysteryitems.js +++ b/migrations/mysteryitems.js @@ -2,7 +2,7 @@ var _id = ''; var update = { $push: { 'purchased.plan.mysteryItems':{ - $each:['head_mystery_201406','armor_mystery_201406'] + $each:['head_mystery_201407','armor_mystery_201407'] } } }; diff --git a/views/shared/new-stuff.jade b/views/shared/new-stuff.jade index 8f4747cd35..4fc9347087 100644 --- a/views/shared/new-stuff.jade +++ b/views/shared/new-stuff.jade @@ -11,21 +11,29 @@ table table.table.table-striped tr td - h5 Mobile App Update - p We’ve released another update to the mobile app! Now you can feed and select pets from the app. Carry your cute pets with you everywhere you go! The app is available for iOS here, and Android here. We’re continuing to release updates on a regular basis, so if you like the direction that we’ve been taking the app, please do consider leaving us a review. Thank you! - tr - td - h5 Neglect Strike: Tavern Art Swap - p The Dread Drag'on's Rage Bar has filled, and it has unleashed its Neglect Strike, leading to a new look for the Tavern! As a reminder, the Drag'on's rage will NEVER hurt any users or interfere with their ability to be productive, so the chat and inn are still functional. Even so... poor Daniel! - p All users are automatically damaging the Drag'on with their tasks. There is nothing bad that can happen to you or your account by being in this fight! - tr - td - h5 Dread Drag'on Prize Change: Food Reward! - p We've received a lot of feedback due to the weekend's confusion, and it seems that awarding GP and XP for defeating the world boss significantly unbalanced the game for newer players. Based on your feedback, XP and GP will no longer be awarded. Instead, players will receive an assortment of food! The Mantis Shrimps will still be awarded. - p If you were looking forward to receiving the 900XP and 90 GP upon completion of the battle, feel free to award it to yourself using Settings > Site > Fix Character Values when the battle is done! - p Thank you for bearing with us through the confusion. We love you guys. - small.muted 7/16/2014 + h5 July Subscriber Item + .promo_mystery_201407.pull-right + p The July Subscriber Item has been revealed: the Undersea Explorer Item Set! All July subscribers will receive the Undersea Explorer Helm and the Undersea Explorer Suit. You still have six days to subscribe and receive the item set! Thank you so much for your support - we really do rely on you to keep HabitRPG free to use and running smoothly. + small.muted 7/25/2014 +h5 7/16/2014 +table.table.table-striped + tr + td + h5 Mobile App Update + p We’ve released another update to the mobile app! Now you can feed and select pets from the app. Carry your cute pets with you everywhere you go! The app is available for iOS here, and Android here. We’re continuing to release updates on a regular basis, so if you like the direction that we’ve been taking the app, please do consider leaving us a review. Thank you! + tr + td + h5 Neglect Strike: Tavern Art Swap + p The Dread Drag'on's Rage Bar has filled, and it has unleashed its Neglect Strike, leading to a new look for the Tavern! As a reminder, the Drag'on's rage will NEVER hurt any users or interfere with their ability to be productive, so the chat and inn are still functional. Even so... poor Daniel! + p All users are automatically damaging the Drag'on with their tasks. There is nothing bad that can happen to you or your account by being in this fight! + tr + td + h5 Dread Drag'on Prize Change: Food Reward! + p We've received a lot of feedback due to the weekend's confusion, and it seems that awarding GP and XP for defeating the world boss significantly unbalanced the game for newer players. Based on your feedback, XP and GP will no longer be awarded. Instead, players will receive an assortment of food! The Mantis Shrimps will still be awarded. + p If you were looking forward to receiving the 900XP and 90 GP upon completion of the battle, feel free to award it to yourself using Settings > Site > Fix Character Values when the battle is done! + p Thank you for bearing with us through the confusion. We love you guys. +hr h5 7/12/2014 table.table.table-striped tr @@ -116,7 +124,7 @@ table.table.table-striped tr td h5 June Subscriber Item - img.pull-right(src='/bower_components/habitrpg-shared/img/sprites/spritesmith/gear/events/mystery_201406/promo_mystery_201406.png') + .pull-right.promo_mystery_201406.png p The June Subscriber Item has been revealed: the Octomage Item Set! All June subscribers will receive the Octopus Robe and the Crown of Tentacles. You still have six days to subscribe and receive the item set! Thank you so much for your support - we really do rely on you to keep HabitRPG free to use and running smoothly. h5 Mobile App Update p There's a new mobile app update available! In addition to bug fixes, there are many improvements, including a new button-based menu, tap-and-hold to edit tasks, and the return of stats and in-app avatar customization! Working on the mobile app is our biggest To-Do this summer, so expect more in the coming months. If you feel that the app is improving, we'd love it if you would take the time to give us a review and let us know what you think! @@ -185,7 +193,7 @@ table.table.table-striped tr td h5 May Mystery Outfit Revealed! - img.pull-right(src='/bower_components/habitrpg-shared/img/sprites/spritesmith/gear/events/mystery_201405/promo_mystery_201405.png') + .pull-right.promo_mystery_201405.png p The May Mystery Item Set has been revealed for all subscribers... Flame Wielder Item Set! All people who are subscribed this May will receive two items: ul li Flame of Mind (helm) @@ -262,7 +270,7 @@ table.table.table-striped tr td h5 April Mystery Outfit Revealed! - img.pull-right(src='/marketing/promos/April14SAMPLE2.png') + //-img.pull-right(src='/marketing/promos/April14SAMPLE2.png') p The April Mystery Item Set has been revealed for all subscribers... Twilight Butterfly Armor Set! All people who are subscribed this April will receive two items: ul li Twilight Butterfly Antennae @@ -317,7 +325,7 @@ table.table.table-striped tr td h5 March Mystery Item Set - img.pull-right(src='/marketing/promos/201403_Forest_Walker.png') + //-img.pull-right(src='/marketing/promos/201403_Forest_Walker.png') p The March Mystery Item Set has been revealed for all subscribers... The Forest Walker Set! All people who are subscribed this March will receive two items: Forest Walker Armor and Forest Walker Antlers! br p The antlers are a head accessory, so they can be worn with any helmet. From 6e29facb981566827a322a854c78603f9686d04b Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Wed, 23 Jul 2014 14:54:32 -0600 Subject: [PATCH 15/16] feat(block-user): allow suspending user accounts (due to TOS violation or such) through Hall. @lemoness --- public/js/app.js | 5 +++- public/js/controllers/rootCtrl.js | 1 + src/controllers/auth.js | 39 ++++++++++++++----------------- src/controllers/hall.js | 10 +++----- src/models/user.js | 2 ++ views/options/social/hall.jade | 8 +++++++ views/shared/modals/settings.jade | 14 ++++++++++- 7 files changed, 48 insertions(+), 31 deletions(-) diff --git a/public/js/app.js b/public/js/app.js index d0fa54dc41..a114bc2fb0 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -228,7 +228,10 @@ window.habitrpg = angular.module('habitrpg', } else if (response.needRefresh) { $rootScope.$broadcast('responseError', "The site has been updated and the page needs to refresh. The last action has not been recorded, please refresh and try again."); - // 400 range? + } else if (response.data.code && response.data.code === 'ACCOUNT_SUSPENDED') { + $rootScope.openModal('suspended',{keyboard: false,backdrop: 'static',size: 'lg',controller:'AuthCtrl'}); + + // 400 range? } else if (response < 500) { $rootScope.$broadcast('responseText', response.data.err || response.data); diff --git a/public/js/controllers/rootCtrl.js b/public/js/controllers/rootCtrl.js index c6a826d162..15b3a994f2 100644 --- a/public/js/controllers/rootCtrl.js +++ b/public/js/controllers/rootCtrl.js @@ -83,6 +83,7 @@ habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$ scope: options.scope, // optional keyboard: (options.keyboard === undefined ? true : options.keyboard), // optional backdrop: (options.backdrop === undefined ? true : options.backdrop) // optional + }); } diff --git a/src/controllers/auth.js b/src/controllers/auth.js index f4eb382b7a..ac5bac6696 100644 --- a/src/controllers/auth.js +++ b/src/controllers/auth.js @@ -14,6 +14,12 @@ var api = module.exports; var NO_TOKEN_OR_UID = { err: "You must include a token and uid (user id) in your request"}; var NO_USER_FOUND = {err: "No user found."}; var NO_SESSION_FOUND = { err: "You must be logged in." }; +var accountSuspended = function(uuid){ + return { + err: 'Account has been suspended, please contact leslie@habitrpg.com with your UUID ('+uuid+') for assistance.', + code: 'ACCOUNT_SUSPENDED' + }; +} api.auth = function(req, res, next) { var uid = req.headers['x-api-user']; @@ -22,6 +28,7 @@ api.auth = function(req, res, next) { User.findOne({_id: uid,apiToken: token}, function(err, user) { if (err) return next(err); if (_.isEmpty(user)) return res.json(401, NO_USER_FOUND); + if (user.auth.blocked) return res.json(401, accountSuspended(user._id)); res.locals.wasModified = req.query._v ? +user._v !== +req.query._v : true; res.locals.user = user; @@ -121,9 +128,10 @@ api.loginLocal = function(req, res, next) { var username = req.body.username; var password = req.body.password; if (!(username && password)) return res.json(401, {err:'Missing :username or :password in request body, please provide both'}); - User.findOne({'auth.local.username': username}, function(err, user){ + User.findOne({'auth.local.username': username}, {auth:1}, function(err, user){ if (err) return next(err); if (!user) return res.json(401, {err:"Username or password incorrect. Click 'Forgot Password' for help with either. (Note: usernames are case-sensitive)"}); + if (user.auth.blocked) return res.json(401, accountSuspended(user._id)); // We needed the whole user object first so we can get his salt to encrypt password comparison User.findOne({ 'auth.local.username': username, @@ -144,30 +152,17 @@ api.loginLocal = function(req, res, next) { api.loginFacebook = function(req, res, next) { var email, facebook_id, name, _ref; _ref = req.body, facebook_id = _ref.facebook_id, email = _ref.email, name = _ref.name; - if (!facebook_id) { - return res.json(401, { - err: 'No facebook id provided' - }); - } - return User.findOne({ - 'auth.facebook.id': facebook_id - }, function(err, user) { + if (!facebook_id) + return res.json(401, {err: 'No facebook id provided'}); + User.findOne({'auth.facebook.id': facebook_id}, function(err, user) { if (err) { - return res.json(401, { - err: err - }); - } - if (user) { - return res.json(200, { - id: user.id, - token: user.apiToken - }); + return res.json(401, {err: err}); + } else if (user) { + if (user.auth.blocked) return res.json(401, accountSuspended(user._id)); + return res.json(200, {id: user.id,token: user.apiToken}); } else { /* FIXME: create a new user instead*/ - - return res.json(403, { - err: "Please register with Facebook on https://habitrpg.com, then come back here and log in." - }); + return res.json(403, {err: "Please register with Facebook on https://habitrpg.com, then come back here and log in."}); } }); }; diff --git a/src/controllers/hall.js b/src/controllers/hall.js index 754af8649e..cc87862707 100644 --- a/src/controllers/hall.js +++ b/src/controllers/hall.js @@ -39,6 +39,7 @@ api.getPatrons = function(req,res,next){ api.getHero = function(req,res,next) { User.findById(req.params.uid) .select('contributor balance profile.name purchased items') + .select('auth.local.username auth.local.email auth.facebook auth.blocked') .exec(function(err, user){ if (err) return next(err) if (!user) return res.json(400,{err:'User not found'}); @@ -64,14 +65,9 @@ api.updateHero = function(req,res,next) { if (req.body.itemPath && req.body.itemVal && req.body.itemPath.indexOf('items.')===0 && User.schema.paths[req.body.itemPath]) { - // TODO remove below after verified. Seems express handles type-casting just fine - //var itemVal = req.body.itemVal;itemVal = - // itemVal === 'false' ? false: itemVal === 'true' ? true: // boolean - // _.isNaN(parseInt(itemVal)) ? itemVal : // string - // _.isDate(itemVal) ? itemVal : // date - // parseInt(itemVal); // number - shared.dotSet(member, req.body.itemPath, req.body.itemVal); + shared.dotSet(member, req.body.itemPath, req.body.itemVal); // Sanitization at 5c30944 (deemed unnecessary) } + if (req.body.auth.blocked) member.auth.blocked = req.body.auth.blocked; member.save(cb); } ], function(err, saved){ diff --git a/src/models/user.js b/src/models/user.js index 4dd29c35ec..fbae36106b 100644 --- a/src/models/user.js +++ b/src/models/user.js @@ -46,6 +46,7 @@ var UserSchema = new Schema({ valentine: Number }, auth: { + blocked: Boolean, facebook: Schema.Types.Mixed, local: { email: String, @@ -405,6 +406,7 @@ UserSchema.pre('save', function(next) { //our own version incrementer this._v++; + next(); }); diff --git a/views/options/social/hall.jade b/views/options/social/hall.jade index ea7b99271b..59c6235ceb 100644 --- a/views/options/social/hall.jade +++ b/views/options/social/hall.jade @@ -59,6 +59,14 @@ script(type='text/ng-template', id='partials/options.social.hall.heroes.html') small.muted Enter the item value. E.g., 5 or false or head_warrior_3 (respectively from above examples). h4 Current Items pre {{::toJson(hero.items,true)}} + accordion-group(heading='Auth') + h4 Auth + pre {{::toJson(hero.auth)}} + .form-group + .checkbox + label + input(type='checkbox', ng-model='hero.auth.blocked') + | Blocked // h4 Backer Status // Add backer stuff like tier, disable adds, etcs diff --git a/views/shared/modals/settings.jade b/views/shared/modals/settings.jade index dc453d9662..8f64e3b639 100644 --- a/views/shared/modals/settings.jade +++ b/views/shared/modals/settings.jade @@ -8,7 +8,19 @@ script(type='text/ng-template', id='modals/reset.html') button.btn.btn-default(ng-click='$close();')=env.t('neverMind') button.btn.btn-danger(ng-click='$close(); reset()')=env.t('resetDo') -script(type='text/ng-template', id='modals/restore.html') +script(type='text/ng-template', id='modals/suspended.html') + .modal-header + h4 Account Suspended + .modal-body + p This account has been suspended. Please contact leslie@habitrpg.com for further assistance. + hr + p Account UUID: {{::user._id}} + a(ng-click='logout()') + span.glyphicon.glyphicon-log-out + |  + =env.t('logout') + +script(type='text/ng-template', id='modals/restore.html') .modal-header h4=env.t('fixValues') .modal-body From a4ae9332bb693832b6089b30f08058a29956aa4f Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Fri, 25 Jul 2014 12:13:26 -0600 Subject: [PATCH 16/16] feat(block-user): show "blocked" status in an alert, log the user out --- public/js/app.js | 4 +++- src/controllers/hall.js | 2 +- views/shared/modals/settings.jade | 12 ------------ 3 files changed, 4 insertions(+), 14 deletions(-) diff --git a/public/js/app.js b/public/js/app.js index a114bc2fb0..1fa78a24df 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -229,7 +229,9 @@ window.habitrpg = angular.module('habitrpg', $rootScope.$broadcast('responseError', "The site has been updated and the page needs to refresh. The last action has not been recorded, please refresh and try again."); } else if (response.data.code && response.data.code === 'ACCOUNT_SUSPENDED') { - $rootScope.openModal('suspended',{keyboard: false,backdrop: 'static',size: 'lg',controller:'AuthCtrl'}); + confirm(response.data.err); + localStorage.clear(); + window.location.href = '/logout'; // 400 range? } else if (response < 500) { diff --git a/src/controllers/hall.js b/src/controllers/hall.js index cc87862707..d3a2796476 100644 --- a/src/controllers/hall.js +++ b/src/controllers/hall.js @@ -67,7 +67,7 @@ api.updateHero = function(req,res,next) { && User.schema.paths[req.body.itemPath]) { shared.dotSet(member, req.body.itemPath, req.body.itemVal); // Sanitization at 5c30944 (deemed unnecessary) } - if (req.body.auth.blocked) member.auth.blocked = req.body.auth.blocked; + if (_.isBoolean(req.body.auth.blocked)) member.auth.blocked = req.body.auth.blocked; member.save(cb); } ], function(err, saved){ diff --git a/views/shared/modals/settings.jade b/views/shared/modals/settings.jade index 8f64e3b639..b5728fb551 100644 --- a/views/shared/modals/settings.jade +++ b/views/shared/modals/settings.jade @@ -8,18 +8,6 @@ script(type='text/ng-template', id='modals/reset.html') button.btn.btn-default(ng-click='$close();')=env.t('neverMind') button.btn.btn-danger(ng-click='$close(); reset()')=env.t('resetDo') -script(type='text/ng-template', id='modals/suspended.html') - .modal-header - h4 Account Suspended - .modal-body - p This account has been suspended. Please contact leslie@habitrpg.com for further assistance. - hr - p Account UUID: {{::user._id}} - a(ng-click='logout()') - span.glyphicon.glyphicon-log-out - |  - =env.t('logout') - script(type='text/ng-template', id='modals/restore.html') .modal-header h4=env.t('fixValues')