From 2f625bedf68c7da173a0f89e86c9f7e6f37c199d Mon Sep 17 00:00:00 2001 From: Marak Date: Sun, 5 Nov 2017 15:17:14 -0500 Subject: [PATCH] [api] [refactor] Email now required + New pricing * `User.email` is now a required field * Adds registration page for account name * Adds screen for upgrading legacy accounts * Legacy accounts must upgrade * API access will remain * Website access will be locked until register * Adds new pricing plans * Improves test coverage for signups / logins * Addresses: #265, #126 --- lib/helpers/numberWithCommas.js | 5 + lib/resources/servicePlan.js | 23 +- lib/resources/user.js | 10 +- lib/server/routeHandlers/loginCallback.js | 11 +- package.json | 1 + public/css/base.css | 3 +- public/css/responsive.css | 2 +- public/style.css | 15 +- scripts/tools/clear-all-billings.js | 49 ++ tests/client/0_account-signup-tests.js | 187 +++++- tests/config/dev/index.js | 2 + tests/lib/helpers/_request.js | 5 + view/account/index.html | 4 - view/account/index.js | 104 ++-- view/account/usage.html | 2 +- view/billing.html | 6 +- view/billing.js | 51 +- view/billingForm.js | 27 +- view/email-required.html | 72 +++ view/email-required.js | 38 ++ view/layout.html | 43 +- view/layout.js | 30 +- view/login.html | 66 ++- view/login.js | 31 +- view/pricing.html | 691 ++++++++++------------ view/pricing.js | 44 +- view/register.html | 164 +++++ view/register.js | 120 ++++ view/services.html | 6 +- view/services.js | 4 +- view/session.js | 4 + view/signup.js | 109 ++-- 32 files changed, 1328 insertions(+), 601 deletions(-) create mode 100644 lib/helpers/numberWithCommas.js create mode 100644 scripts/tools/clear-all-billings.js create mode 100644 view/email-required.html create mode 100644 view/email-required.js create mode 100644 view/register.html create mode 100644 view/register.js create mode 100644 view/session.js diff --git a/lib/helpers/numberWithCommas.js b/lib/helpers/numberWithCommas.js new file mode 100644 index 00000000..3af578ae --- /dev/null +++ b/lib/helpers/numberWithCommas.js @@ -0,0 +1,5 @@ +function numberWithCommas (x) { + return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); +} + +module.exports = numberWithCommas; \ No newline at end of file diff --git a/lib/resources/servicePlan.js b/lib/resources/servicePlan.js index 9d0af2f6..14bbf0e2 100644 --- a/lib/resources/servicePlan.js +++ b/lib/resources/servicePlan.js @@ -1,18 +1,31 @@ module.exports = { "free": { "hits": 1000, - "concurrency": 2 + "concurrency": 2, + "cost": "$0.00" }, "premium": { "hits": 10000, - "concurrency": 4 + "concurrency": 4, + "stripe_label": 'BASIC_HOSTING_PLAN_10', + "cost": "$10.00" }, - "pro": { + "advanced": { "hits": 50000, - "concurrency": 10 + "concurrency": 16, + "stripe_label": 'BASIC_HOSTING_PLAN_20', + "cost": "$25.00" + }, + "pro": { + "hits": 100000, + "concurrency": 8, + "stripe_label": 'BASIC_HOSTING_PLAN_50', + "cost": "$50.00" }, "business": { "hits": 1000000, - "concurrency": 50 + "concurrency": 32, + "stripe_label": 'BASIC_HOSTING_PLAN_100', + "cost": "$200.00" } }; \ No newline at end of file diff --git a/lib/resources/user.js b/lib/resources/user.js index 06773145..2485a363 100644 --- a/lib/resources/user.js +++ b/lib/resources/user.js @@ -11,7 +11,8 @@ user.property('env', 'object'); user.property('referredBy', 'string'); // user.property('dedicatedSignup', 'object'); -user.schema.properties.email.required = false; +user.schema.properties.name.required = false; +user.schema.properties.email.required = true; user.schema.properties.password.required = false; user.property('stripeID', 'string'); @@ -54,6 +55,7 @@ user.on('login', function (data) { name: "api-access-key", owner: data.name || data.username.toLowerCase() }; + // will only create key if it doesn't already exist keys.findOne(_query, function(err, key){ if (err) { _query.id = data.id; @@ -62,6 +64,12 @@ user.on('login', function (data) { }); }); +// when a user registers an account name +user.on('register', function (data) { + // TODO: create a new subdomain for that user ? + // Remark: Instead of automatically adding subdomains, allow user to register it easily +}); + /* not working? user.after('auth', function (data, next) { console.log('after auth', data) diff --git a/lib/server/routeHandlers/loginCallback.js b/lib/server/routeHandlers/loginCallback.js index 29f88dd0..d90148fa 100644 --- a/lib/server/routeHandlers/loginCallback.js +++ b/lib/server/routeHandlers/loginCallback.js @@ -1,7 +1,7 @@ var user = require('../../resources/user'); var metric = require('../../resources/metric'); -module['exports'] = function (req, res) { +module['exports'] = function loginCallback (req, res) { var referredBy = req.session.referredBy || ""; var redirectTo = req.session.redirectTo || "/services"; @@ -32,6 +32,10 @@ module['exports'] = function (req, res) { } catch(err) { // do nothing } + + // what happens if we have a conflicting namespace here between github and hook.io? + // i believe it will result in an error due to req.session.user already existing in hook.io data + // todo: figure out a way for github users to register accounts if their github name is already taken by another user on hook.io user.create({ name: req.session.user, email: mail, @@ -49,7 +53,12 @@ module['exports'] = function (req, res) { } else { // assign paid status based on existing user document req.session.paidStatus = result[0].paidStatus; + req.session.servicePlan = result[0].servicePlan || 'free'; req.session.email = result[0].email; + + // shouldn't this be assigned based on the github user name? + + // req.session.user = result[0].name; req.session.hookAccessKey = result[0].hookAccessKey; user.emit('login', result[0]); var u = result[0]; diff --git a/package.json b/package.json index 63b4ea74..cc519574 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "mschema": "https://github.com/mschema/mschema/tarball/master", "mschema-forms": "https://github.com/mschema/mschema-forms/tarball/master", "mschema-rpc": "https://github.com/mschema/mschema-rpc/tarball/master", + "ms": "2.0.0", "mustache": "^0.8.2", "nano": "^6.2.0", "node-slug": "0.0.2", diff --git a/public/css/base.css b/public/css/base.css index 32916692..04b2f270 100644 --- a/public/css/base.css +++ b/public/css/base.css @@ -119,9 +119,8 @@ label { margin-bottom: 30px; } -/* hide for now, too frequently popping up */ .emailReminder { - display: none; + display: block; } .emailReminder .content { diff --git a/public/css/responsive.css b/public/css/responsive.css index 4e3817d6..d65b6e01 100755 --- a/public/css/responsive.css +++ b/public/css/responsive.css @@ -189,7 +189,7 @@ margin-top:0px; /* small mobile :320px. */ @media (max-width: 767px) { -.container {width:300px} +.container {width:100%} .header_nav{ display:none; } diff --git a/public/style.css b/public/style.css index 09b8d2eb..3904e0d8 100755 --- a/public/style.css +++ b/public/style.css @@ -2700,8 +2700,13 @@ span.member-role { .button_set_plain { text-align: right; + /* margin: 20px 0px 0px; padding: 8px 0px; + */ + padding: 0px; + margin: 0px; + width: 75px; border-top: none; } .button_set_plain p { @@ -2711,16 +2716,18 @@ span.member-role { background: transparent; border: 1px solid #d7d7d7; color: #868686; - padding: 7px 18px 5px; + font-size: 16px; + /*padding: 7px 18px 5px;*/ } .button_set_plain .newsl_button button span { - background: transparent; + /*background: transparent;*/ + background-color: #0F99DE; border: 1px solid #d7d7d7; - color: #868686; + color: #FFF; } .button_set_plain .newsl_button button:hover span { border-color: #0F99DE; - background-color: #0F99DE; + background-color: #000; color: #fff; } .button_set_plain .newsl_button { diff --git a/scripts/tools/clear-all-billings.js b/scripts/tools/clear-all-billings.js new file mode 100644 index 00000000..f4b5e476 --- /dev/null +++ b/scripts/tools/clear-all-billings.js @@ -0,0 +1,49 @@ +var config = require('../../config'); + +var billing = require('../../lib/resources/billing'); +var user = require('../../lib/resources/user'); + +var colors = require('colors'); + +billing.persist(config.couch); +user.persist(config.couch); + +user.findOne({ + name: 'bobby' +}, function(err, _u){ + if(err) { + throw err; + } + _u.servicePlan = "free"; + _u.save(function(err){ + if(err) { + throw err; + } + clearBillings(); + }) +}); + +function clearBillings () { + billing.find({ owner: 'bobby' }, function(err, results){ + + // billing.all(function(err, results){ + if(err) { + throw err; + } + console.log(results) + function destroy () { + if (results.length === 0) { + process.exit(); + } + var result = results.pop(); + console.log(result.owner.toLowerCase()) + result.destroy(function(err, res){ + if (err) { + throw err; + } + destroy(); + }); + } + destroy(); + }); +} \ No newline at end of file diff --git a/tests/client/0_account-signup-tests.js b/tests/client/0_account-signup-tests.js index f273d7fa..51ebedab 100644 --- a/tests/client/0_account-signup-tests.js +++ b/tests/client/0_account-signup-tests.js @@ -37,26 +37,130 @@ tap.test('attempt to signup with no account name or email', function (t) { }); }); -tap.test('attempt to signup by account name with no password', function (t) { +tap.test('attempt to signup by just account name', function (t) { r({ uri: baseURL + "/signup", method: "POST", form: { - "email": testUser.name + "name": testUser.name }, }, function (err, res) { t.error(err, 'request did not error'); - console.log(err, res); t.equal(typeof res, 'object', "response contains object"); - t.equal(res.result, 'available', "name is available"); + t.equal(res.result, 'invalid', "unable to signup with just account name"); t.end(); }); }); +var request = require('request'); +var cookieRequest = request.defaults({jar: true}) -/* +tap.test('attempt to signup by email with no password', function (t) { + + var opts = { + uri: baseURL + '/signup', + form: { + "email": testUser.email + }, + method: 'POST', + json: true, + jar: true + }; + + r(opts, function (err, body) { + // now have cookies + // cookieRequest('http://images.google.com') + t.equal(body.result, 'valid', "registered valid account by email"); + var opts = { + uri: baseURL + '/session', + method: 'GET', + json: true, + jar: true + }; + r(opts, function (err, body) { + t.equal(body.email, testUser.email, 'has correct req.session.email') + t.end(); + }); + }) + +}); + + +tap.test('attempt to get session - with cookies', function (t) { + + var opts = { + uri: baseURL + '/session', + method: 'GET', + json: true, + jar: true + }; + r(opts, function (err, body) { + t.equal(body.email, testUser.email) + t.end(); + }); + +}); -Note: Removed from API +tap.test('attempt to register account name - with cookies - missing password', function (t) { + + var opts = { + uri: baseURL + '/register', + form: { + account_name: 'bobby' + }, + method: 'POST', + json: true, + jar: true + }; + r(opts, function (err, body, res) { + t.error(err, 'did not return error'); + t.equal(res.statusCode, 400); + t.equal(body.message, 'Password field is required.'); + t.end(); + }); + +}); + +tap.test('attempt to register account name - with cookies - has password', function (t) { + var opts = { + uri: baseURL + '/register', + form: { + account_name: 'bobby', + password: 'asd', + confirmPassword: 'asd' + }, + method: 'POST', + json: true, + jar: true + }; + r(opts, function (err, body, res) { + t.equal(res.statusCode, 200); + t.equal(body.result, 'success'); + t.end(); + }); +}); + +tap.test('attempt to register same account name - with cookies - has confirmed password', function (t) { + var opts = { + uri: baseURL + '/register', + form: { + account_name: 'bobby', + password: 'asd', + confirmPassword: 'asd' + }, + method: 'POST', + json: true, + jar: true + }; + r(opts, function (err, body, res) { + t.error(err, 'did not return error'); + t.equal(body.result, 'already-registered', 'returns already-registered message'); + t.end(); + }); +}); + + +/* tap.test('attempt to signup by account name mismtached passwords', function (t) { r({ @@ -76,12 +180,14 @@ tap.test('attempt to signup by account name mismtached passwords', function (t) }); */ -tap.test('attempt to signup by account name with valid password', function (t) { +/* + +tap.test('attempt to signup by email name with valid password', function (t) { r({ uri: baseURL + "/signup", method: "POST", form: { - "email": testUser.name, + "email": testUser.email, "password": "foo", "confirmPassword": "foo" }, @@ -93,12 +199,71 @@ tap.test('attempt to signup by account name with valid password', function (t) { t.end(); }); }); +*/ + +tap.test('attempt to logout out of session', function (t) { + r({ + uri: baseURL + "/logout", + method: "GET", + jar: true + }, function (err, body, res) { + t.error(err, 'request did not error'); + t.equal(typeof res, 'object', "response contains object"); + console.log('bbbb', body) + t.equal(res.statusCode, 200, "logged out and redirected"); + var opts = { + uri: baseURL + '/session', + method: 'GET', + json: true, + jar: true + }; + r(opts, function (err, body) { + // console.log("BBBB", body) + t.equal(typeof body.email, "undefined", "did not find req.session.email") + t.equal(typeof body.user, "undefined", "did not find req.session.user") + t.end(); + }); + }); +}); + +tap.test('attempt to signup with same email address', function (t) { + r({ + uri: baseURL + "/signup", + method: "POST", + form: { + "email": testUser.email + }, + }, function (err, res) { + t.error(err, 'request did not error'); + t.equal(typeof res, 'object', "response contains object"); + t.equal(res.result, 'exists', "email is already registered"); + t.end(); + }); +}); + +tap.test('attempt to login with with new account', function (t) { + r({ + uri: baseURL + "/login", + method: "POST", + json: true, + jar: true, + form: { + "email": testUser.email, + "password": "asd" + }, + }, function (err, res) { + t.error(err, 'request did not error'); + t.equal(typeof res, 'object', "response contains object"); + t.equal(res.result, 'valid', "valid login"); + t.end(); + }); +}); tap.test('attempt to clear test user - as superadmin', function (t) { r({ uri: baseURL + "/_admin", method: "POST", json: { method: "user.destroy", super_private_key: config.superadmin.super_private_key, - name: testUser.name + email: testUser.email }}, function (err, res, body) { t.error(err); t.error(err, 'request did not error'); @@ -110,5 +275,7 @@ tap.test('attempt to clear test user - as superadmin', function (t) { tap.test('perform hard shutdown of cluster', function (t) { t.end('shut down'); - process.exit(0); + setTimeout(function(){ + process.exit(); + }, 10); }); \ No newline at end of file diff --git a/tests/config/dev/index.js b/tests/config/dev/index.js index 2c9f556e..979a10e8 100644 --- a/tests/config/dev/index.js +++ b/tests/config/dev/index.js @@ -11,6 +11,7 @@ module['exports'] = { testUsers: { "bobby": { name: "bobby", + email: "bobby@marak.com", admin_key: "ad255b3e-833e-41e6-bc68-23439ff27f65", // admin-access-key run_key: "e27b1183-9375-4b64-ad2f-76a2c8ebd064", // only has hook::run read_only: "57a45b7c-7bcd-4c66-a7d4-c847e86764c7", // has only hook::logs::read, events::read, @@ -25,6 +26,7 @@ module['exports'] = { }, "david": { name: "david", + email: "david@marak.com", admin_key: "f34a3112-fb82-4092-8ea7-912fa11ba6dd", run_key: "f34a3112-fb82-4092-8ea7-912fa11ba6dd", hook_private_key: "f34a3112-fb82-4092-8ea7-912fa11ba6dd", diff --git a/tests/lib/helpers/_request.js b/tests/lib/helpers/_request.js index 7c960434..ce2208fb 100644 --- a/tests/lib/helpers/_request.js +++ b/tests/lib/helpers/_request.js @@ -10,6 +10,11 @@ module['exports'] = function _request (opts, cb) { //data.body = opts.json; //data.json = true; } + if (opts.jar) { + request = request.defaults({jar: true}) + } else { + request = request.defaults({jar: false}) + } request(data, function (err, res) { if (err) { return cb(err); diff --git a/view/account/index.html b/view/account/index.html index b1b39493..f0016abd 100644 --- a/view/account/index.html +++ b/view/account/index.html @@ -43,10 +43,6 @@ border: black; } - .status { - text-align: center; - } - .renameAccount { margin-left: 20px; font-size: 12px; diff --git a/view/account/index.js b/view/account/index.js index d26677f4..3589b5a2 100644 --- a/view/account/index.js +++ b/view/account/index.js @@ -93,7 +93,7 @@ module['exports'] = function view (opts, callback) { $('.status').html('Please set your new password immediately!'); } if (params.paid) { - $('.status').html('Thank you so much for supporting us!
Your Account has been Upgraded.
You now have access to additional features and higher usage limits.'); + $('.status').html(' Your Account has been Upgraded!
Thank you for your purchase.
You now have access to additional features and higher usage limits.'); } user.find({ name: req.session.user }, function(err, results) { @@ -108,7 +108,7 @@ module['exports'] = function view (opts, callback) { var _user = {}, r = results[0]; _user.name = r.name; _user.id = r.id; - r.paidStatus = r.paidStatus || req.session.paidStatus; + $('.myHooks').attr('href', '/' + _user.name); if (req.method === "POST") { @@ -126,37 +126,61 @@ module['exports'] = function view (opts, callback) { _user.password = params.password; } } - - // allow for account renames - if (typeof params.name === "undefined" || params.name.length < 3) { - params.name = req.session.user; - // return res.end('name is a required parameter!'); - } - - if (params.name && params.previousName && params.name !== params.previousName) { - renameAccount(_user, params, function (err, result){ - // display user info in account form - // TODO: if form post data, attempt to update user account information - req.session.destroy(); - req.logout(); - res.redirect("/"); - return; - }) - } else { - return user.update(_user, function(err, result){ - if (err) { - return res.end(err.message); + + // TODO: perform lookup of existing accounts by email + // do not allow users to override email address already in use + user.find({ email: params.email }, function (err, _users){ + if (err) { + return res.end(err.message); + } + if (_users.length > 0) { + var ok = false; + _users.forEach(function(_u){ + if (_u.name === req.session.user) { + ok = true; + } + }); + if (ok) { + // email matches current account session, allow update ( not really updating the email value though ) + } else { + // we found other accounts with the same email address, cannot save current email address + res.status(500); + return res.json({ error: true, message: 'An account is already registed to: ' + params.email }); } - req.session.email = result.email; - // display user info in account form - // TODO: if form post data, attempt to update user account information - showUserForm(_user, function(err, result){ - $('.userForm').html(result); - $('.status').html('Account Information Updated!'); - callback(null, $.html()); + } + // allow for account renames + if (typeof params.name === "undefined" || params.name.length < 3) { + params.name = req.session.user; + // return res.end('name is a required parameter!'); + } + + if (params.name && params.previousName && params.name !== params.previousName) { + renameAccount(_user, params, function (err, result){ + // display user info in account form + // TODO: if form post data, attempt to update user account information + req.session.destroy(); + req.logout(); + res.redirect("/"); + return; + }) + } else { + return user.update(_user, function(err, result){ + if (err) { + return res.end(err.message); + } + req.session.email = result.email; + // display user info in account form + // TODO: if form post data, attempt to update user account information + showUserForm(result, function(err, result){ + $('.userForm').html(result); + $('.status').html('Account Information Updated!'); + $('.status').addClass('success'); + callback(null, $.html()); + }); }); - }); - } + } + }); + } else { return res.end('email parameter cannot be empty'); } @@ -181,6 +205,7 @@ var mustache = require('mustache'); function showUserForm (user, cb) { + var formSchema = userSchema || {}; formSchema.name = { @@ -189,20 +214,12 @@ function showUserForm (user, cb) { }; formSchema.email.default = user.email || ""; - // formSchema.email.disabled = true; - /* - formSchema.run = { + formSchema.servicePlan = { "type": "string", - "default": "true", - "format": "hidden" - }; - */ - formSchema.paidStatus = { - "type": "string", - "label": "account paid status", + "label": "service plan", "disabled": true, - "default": user.paidStatus + "default": user.servicePlan }; formSchema.password = { @@ -211,7 +228,8 @@ function showUserForm (user, cb) { }; formSchema.confirmPassword = { "type": "string", - "format": "password" + "format": "password", + "label": "confirm password" }; formSchema.previousName = { diff --git a/view/account/usage.html b/view/account/usage.html index f46c6cf5..d5060b92 100644 --- a/view/account/usage.html +++ b/view/account/usage.html @@ -17,7 +17,7 @@

Account Limits

-->

Monthly limits will automatically reset on the 1st of the month at midnight.
- Concurrency limits should reset to 0 if no services are currently running. + Concurrency limits should reset to zero if no services are currently running.

Request reset on usage limitations

diff --git a/view/billing.html b/view/billing.html index cde644f9..4261d86e 100644 --- a/view/billing.html +++ b/view/billing.html @@ -1,5 +1,9 @@ + +
+

Update Account with Email Address

+
+
+
+
+ +

Attention: A valid email address is now required for all accounts. Please add an email address to this account to continue.
+ Without having an email address on record it's possible you may get locked out of your account.

+ + + +
+
+ + +
+ +
+
+
+
+
+
+ + diff --git a/view/email-required.js b/view/email-required.js new file mode 100644 index 00000000..9c37131f --- /dev/null +++ b/view/email-required.js @@ -0,0 +1,38 @@ +var psr = require('parse-service-request'); +var user = require('../lib/resources/user'); + +module.exports = function (opts, cb) { + var $ = this.$, res = opts.res, req = opts.req; + if (!req.isAuthenticated()) { + return res.redirect(302, '/login'); + } + psr(req, res, function () { + var params = req.resource.params; + if (req.method === "POST") { + // check to see if valid email was posted + if (typeof params.email === 'string' && params.email.length > 0 && params.email.search('@') !== -1) { + user.findOne({ name: req.session.user }, function(err, _user){ + if (err) { + res.status(500) + return res.json({ error: true, message: err.message }); + } + _user.email = params.email; + _user.save(function(err){ + if (err) { + res.status(500) + return res.json({ error: true, message: err.message }); + } + req.session.email = params.email; + return res.end('set-email') + }); + }) + } else { + res.status(500) + return res.json({ error: true, message: 'Invalid email address'}); + } + //return cb(null, $.html()); + } else { + return cb(null, $.html()); + } + }) +} \ No newline at end of file diff --git a/view/layout.html b/view/layout.html index 50e262b1..814f8847 100644 --- a/view/layout.html +++ b/view/layout.html @@ -459,24 +459,6 @@

WELCOME to {{appName}}

-
-
-
-
-

Close [X]

-

Attention: Please verify your account with an email address.
- Without an email address on record, it's possible you may get locked out of your account.

-
-
- - -
- -
-
-
-
-
@@ -60,6 +71,7 @@

Registered Developers

+ + + +
+

Register Account

+
+

To continue, we require you set an account name and password

+
+
+ + +
+
+

+ +
+
+

+ +
+
+

+ +
+
+
+ +
+ I'd rather Sign-in with Github +
+
+
+
+
+

Your Home

+
+

Your account name is the home for your services on hook.io

+

{{appUrl}}account-name

+ +

You will be able to manage your services from this locations.

+
+
+
+
+ +

+
\ No newline at end of file diff --git a/view/register.js b/view/register.js new file mode 100644 index 00000000..22b8f835 --- /dev/null +++ b/view/register.js @@ -0,0 +1,120 @@ +var user = require('../lib/resources/user'); +var psr = require('parse-service-request'); +var config = require('../config'); +module.exports = function (opts, cb) { + var $ = this.$, + res = opts.res, + req = opts.req; + + $ = req.white($); + + var params = req.resource.params; + + if (!req.isAuthenticated()) { + if (req.jsonResponse) { + res.status(401); + return res.json({ error: true, message: 'session-required' }); + } + } + if (req.session.user && req.session.user !== 'anonymous') { + if (req.jsonResponse) { + return res.json({ result: 'already-registered' }); + } + return res.redirect(config.app.url + '/account'); + } + + function passwordRequired () { + res.status(400); + var r = { + error: true, + message: 'Password field is required.' + } + return res.json(r); + } + + psr(req, res, function(){ + if (req.method === "POST") { + if (typeof params.password !== 'undefined' && typeof params.confirmPassword !== 'undefined') { + if (params.password.length > 0) { + if (params.password !== params.confirmPassword) { + res.status(500); + var r = { + error: true, + message: 'Passwords do not match.' + } + return res.json(r); + } + } else { + return passwordRequired(); + } + } else { + return passwordRequired(); + } + + if (typeof params.account_name !== 'string' || params.account_name.length === 0) { + res.status(500); + return r.json({ error: true, message: 'account_name parameter is required'}); + } + // TODO: slug check for account name? i hope its already there in resource-user + // TODO: if incoming account name is being registered, update it on the user document and session + user.find({ name: params.account_name }, function (err, _users) { + if (err) { + return res.json({ error: true, message: err.message }); + } + if (_users.length === 0) { + // account name is available, register it with current logged in account! + } else { + // account name is take + var r = { + result: 'exists' + } + return res.json(r); + } + user.findOne({ email: req.session.email}, function (err, _user) { + if (err) { + res.status(500) + return res.json({ error: true, message: err.message }); + } + + // TOOD: replace with user.signUp logic? + // user.register() logic? + // needs to perform req.login? + + // needs to perform password check and upate + + //_user.name = params.account_name; + // _user.password = params.password; + + var _update = { + id: _user.id, + name: params.account_name, + password: params.password + }; + // console.log('attemping to update user', _update) + // Remark: Must use User.update instead of User.save for password before hooks to work ( salt + one-way hash ) + // Would be nice if resource had .save() hooks working + user.update(_update, function(err, re){ + if (err) { + res.status(500) + return res.json({ error: true, message: err.message }); + } + // TODO: perform redirect + var r = { + result: 'success' + } + req.session.user = params.account_name; + return res.json(r); + return res.redirect(config.app.url + '/services'); + }); + }); + }); + } else { + // auto-suggest account name based on current email + var suggest = req.session.email.split('@')[0]; + $('#account_name').val(suggest); + $('.accountName').html('/' + suggest); + cb(null, $.html()) + } + }); + +} \ No newline at end of file diff --git a/view/services.html b/view/services.html index 45c9eb02..3f46fc30 100644 --- a/view/services.html +++ b/view/services.html @@ -72,6 +72,9 @@ .hookError { font-size: 14px; } + .message { + font-size: 24px; + } @@ -120,7 +123,6 @@
- + +
diff --git a/view/services.js b/view/services.js index 278713e2..ceb63b90 100644 --- a/view/services.js +++ b/view/services.js @@ -87,7 +87,9 @@ module['exports'] = function view (opts, callback) { //$('.navBar').remove(); $('.servicesHeader').html(req.params.owner); } - + if (params.registered) { + $('.message').html(req.session.email + ' is now registered.'); + } if (hooks.length > 0) { // sort hooks alphabetically by name hooks = hooks.sort(function(a,b){ diff --git a/view/session.js b/view/session.js new file mode 100644 index 00000000..ed567bf0 --- /dev/null +++ b/view/session.js @@ -0,0 +1,4 @@ +module.exports = function (opts, cb) { + var res = opts.res, req = opts.req; + res.json(req.session); +} \ No newline at end of file diff --git a/view/signup.js b/view/signup.js index abfb55f3..d5d7f36e 100644 --- a/view/signup.js +++ b/view/signup.js @@ -26,60 +26,53 @@ module['exports'] = function signup (opts, cb) { psr(req, res, function (req, res) { var params = req.resource.params; var email = params.email; + + // if email is invalid if(typeof email === "undefined" || email.length < 3) { var r = { result: 'invalid' }; return res.json(r); } - var type = "name"; - // determine if username or email - if (email.search('@') !== -1) { - type = "email"; - } - var query = {}; + + // TODO: validate email? + + // if an valid email has been provided email = email.toLowerCase(); - query[type] = email; - return user.find(query, function(err, results){ + + // attempt to find if email conflicts with existing user + return user.find({ email: email }, function (err, results) { if (err) { return res.end(err.stack); } + // if user exists, abort if (results.length > 0) { var r = { result: "exists" }; return res.json(r); } - + // TODO: remove legacy code for auto username var data = {}; - if (type === "email") { - data.email = email; - data.name = slug(email); - } else { - data.name = email; - } - if (data.type === "name" && typeof params.password === "undefined" /*|| typeof params.email === "undefined"*/) { - var r = { - result: "available" - }; - return res.json(r); - } else { - - data.password = params.password; - // todo: use user.signup - if (results.length === 0 && typeof params.password !== "undefined" && params.password.length !== 0 /*&& (params.password === params.confirmPassword) */) { - // ready to signup new user... - // do nothing, user.create will fire below - } else { - // can't signup user, something wrong with first password - // somewhat non-descriptive error here - // client mostly handles this first + data.type = "email" + data.email = email; + + // create the new hook.io user with email address + return user.create(data, function (err, result) { + if (err) { + err.message = JSON.parse(err.message) var r = { - result: "available", + result: "error", + error: true, + message: err.message.errors[0].message }; + res.status(500); return res.json(r); } - return user.create(data, function (err, result) { + + // todo: set token here??? which token? + // once the user is created, login the current request session + req.login(result, function(err){ if (err) { var r = { result: "error", @@ -87,33 +80,33 @@ module['exports'] = function signup (opts, cb) { }; return res.json(r); } - // todo: set token here - req.login(result, function(err){ - if (err) { - var r = { - result: "error", - error: err.stack - }; - return res.json(r); - } + + // once session login is completed, assign some user session variables for later use + if (typeof result.name !== 'undefined') { req.session.user = result.name.toLowerCase(); - req.session.email = result.email; - req.session.hookAccessKey = result.hookAccessKey; - var r = { - result: "valid", - }; - // r.res = "redirect"; - r.redirect = req.session.redirectTo || "/services"; - //console.log('doing the redirect', r) - user.emit('login', result); - if (req.jsonResponse) { - return res.json(r); - } else { - return res.redirect(r.redirect); - } - }); + } + // TODO: universal login + req.session.email = result.email; + req.session.hookAccessKey = result.hookAccessKey; + var r = { + result: "valid", + }; + // r.res = "redirect"; + r.redirect = req.session.redirectTo || "/services"; + + // TODO: emit the user login event + // user.emit('login', result); + + // if json response, send back json message + if (req.jsonResponse) { + return res.json(r); + } else { + // if not json response, assume browser and redirect to logged in `/services` page + return res.redirect(r.redirect); + } }); - } + }); + }); }); };