Permalink
Browse files

Added out-of-the-box login types (login, email, and phone) to provide…

… flexibility for what login maps to in your data and app.
  • Loading branch information...
1 parent 17cffcb commit 263343de2ec7c84f6a0528a3b5155e46436f0645 @bnoguchi committed May 3, 2011
Showing with 143 additions and 26 deletions.
  1. +22 −0 README.md
  2. +4 −7 example/server.js
  3. +4 −4 example/views/login.jade
  4. +4 −4 example/views/register.jade
  5. +8 −0 lib/expressHelper.js
  6. +101 −11 lib/password.js
View
@@ -379,6 +379,28 @@ everyauth.password.extractExtraRegistrationParams( function (req) {
});
```
+### Password Recipe 2: Logging in with email or phone number
+
+By default, `everyauth` uses the field and user key name `login` during the
+registration and login process.
+
+Sometimes, you want to use `email` or `phone` instead of `login`. Moreover,
+you also want to validate `email` and `phone` fields upon registration.
+
+`everyauth` provides an easy way to do this:
+
+```javascript
+everyauth.password.loginWith('email');
+
+// OR
+
+everyauth.password.loginWith('phone');
+```
+
+With simple login configuration like this, you get email (or phone) validation
+in addition to renaming of the form field and user key corresponding to what
+otherwise would typically be referred to as 'login'.
+
## Setting up GitHub OAuth
```javascript
View
@@ -40,6 +40,7 @@ everyauth
everyauth
.password
+ .loginWith('email')
.getLoginPath('/login')
.postLoginPath('/login')
.loginView('login.jade')
@@ -57,17 +58,13 @@ everyauth
.getRegisterPath('/register')
.postRegisterPath('/register')
.registerView('register.jade')
- .validateRegistration( function (newUserAttrs) {
- var login = newUserAttrs.login
- , password = newUserAttrs.password
- , errors = [];
- if (!login) errors.push('Missing login');
+ .validateRegistration( function (newUserAttrs, errors) {
+ var login = newUserAttrs.login;
if (usersByLogin[login]) errors.push('Login already taken');
- if (!password) errors.push('Missing password');
return errors;
})
.registerUser( function (newUserAttrs) {
- var login = newUserAttrs.login;
+ var login = newUserAttrs[this.loginKey()];
return usersByLogin[login] = newUserAttrs;
})
View
@@ -4,10 +4,10 @@
li.error= error
form(action: '/login', method: 'POST')
#login
- label(for: 'login') Login
- input(type: 'text', name: 'login', value: login)
+ label(for: everyauth.password.loginFormFieldName) Login
+ input(type: 'text', name: everyauth.password.loginFormFieldName, value: email)
#password
- label(for: 'password') Password
- input(type: 'password', name: 'password')
+ label(for: everyauth.password.passwordFormFieldName) Password
+ input(type: 'password', name: everyauth.password.passwordFormFieldName)
#submit
input(type: 'submit') Login
@@ -5,10 +5,10 @@ h2 Register
li.error= error
form(action: '/register', method: 'POST')
#login
- label(for: 'login') Login
- input(type: 'text', name: 'login', value: userParams.login)
+ label(for: everyauth.password.loginFormFieldName) Login
+ input(type: 'text', name: everyauth.password.loginFormFieldName, value: userParams[everyauth.password.loginFormFieldName])
#password
- label(for: 'password') Password
- input(type: 'password', name: 'password')
+ label(for: everyauth.password.passwordFormFieldName) Password
+ input(type: 'password', name: everyauth.password.passwordFormFieldName)
#submit
input(type: 'submit') Register
View
@@ -1,3 +1,4 @@
+var everyauth = require('../index');
module.exports = function (app) {
app.dynamicHelpers({
everyauth: function (req, res) {
@@ -11,6 +12,13 @@ module.exports = function (app) {
ea[k] = auth[k];
}
+ // Add in access to loginFormFieldName() and passwordFormFieldName()
+ // TODO Don't compute this if we
+ // aren't using password module
+ ea.password || (ea.password = {});
+ ea.password.loginFormFieldName = everyauth.password.loginFormFieldName();
+ ea.password.passwordFormFieldName = everyauth.password.passwordFormFieldName();
+
ea.user = req.user;
return ea;
View
@@ -9,6 +9,10 @@ everyModule.submodule('password')
, passwordFormFieldName: 'the name of the login field. Same as what you put in your login form '
+ '- e.g., if <input type="password" name="pswd" />, then passwordFormFieldName '
+ 'should be set to "pswd".'
+ , loginHumanName: 'the human readable name of login -- e.g., "login" or "email"'
+ , loginKey: 'the name of the login field in your data store -- e.g., "email"; defaults to "login"'
+ , loginWith: 'specify what login type you want to use'
+ , validLoginTypes: 'specifies the different login types available and associated behavior'
, loginView: 'Either (A) the name of the view (e.g., "login.jade") or (B) the HTML string ' +
'that corresponds to the login page OR (C) a function (errors, login) {...} that returns the HTML string incorporating the array of `errors` messages and the `login` used in the prior attempt'
, loginSuccessRedirect: 'The path we redirect to after a successful login.'
@@ -18,14 +22,19 @@ everyModule.submodule('password')
})
.loginFormFieldName('login')
.passwordFormFieldName('password')
+ .loginHumanName('login')
+ .loginKey('login')
.get('getLoginPath', "the login page's uri path.")
.step('displayLogin')
.accepts('req res')
.promises(null)
.displayLogin( function (req, res) {
+ var locals;
if (res.render) {
- res.render(this.loginView(), {login: null});
+ locals = {};
+ locals[this.loginKey()] = null;
+ res.render(this.loginView(), locals);
} else {
res.writeHead(200, {'Content-Type': 'text/html'});
res.end(this.loginView());
@@ -85,12 +94,12 @@ everyModule.submodule('password')
}
})
.respondToLoginFail( function (res, errors, login) {
+ var locals;
if (!errors || !errors.length) return;
if (res.render) {
- res.render(this.loginView(), {
- errors: errors
- , login: login
- });
+ locals = { errors: errors };
+ locals[this.loginKey()] = login;
+ res.render(this.loginView(), locals);
} else {
res.writeHead(200, {'Content-Type': 'text/html'});
if ('function' === typeof this.loginView()) {
@@ -126,9 +135,13 @@ everyModule.submodule('password')
.description('Combines login, password, and extraParams into a newUserAttributes Object containing everything in extraParams plus login and password key/value pairs')
.accepts('login password extraParams')
.promises('newUserAttributes')
+ .step('validateRegistrationBase')
+ .description('Basic validation done by `everyauth`')
+ .accepts('newUserAttributes')
+ .promises('baseErrors')
.step('validateRegistration')
.description('Validates the registration parameters. Default includes check for existing user')
- .accepts('newUserAttributes')
+ .accepts('newUserAttributes baseErrors')
.promises('errors')
.step('maybeBreakToRegistrationFailSteps')
.accepts('req res errors newUserAttributes')
@@ -152,22 +165,54 @@ everyModule.submodule('password')
})
.aggregateParams( function (login, password, extraParams) {
var params = extraParams;
- params.login = login;
+ params[this.loginKey()] = login;
params.password = password;
return params;
})
- .validateRegistration( function (newUserAttributes) {
- var login = newUserAttributes.login
+ .validateRegistrationBase( function (newUserAttributes) {
+ var loginWith = this.loginWith()
+ , loginWithSpec, loginWithSanitize, loginWithValidate
+ , login = newUserAttributes[this.loginKey()]
, password = newUserAttributes.password
, errors = [];
- if (!login) errors.push('Missing login');
+ if (!login) {
+ errors.push('Missing ' + this.loginHumanName());
+ } else {
+ // loginWith specific validations
+ validLoginTypes = this.validLoginTypes();
+ loginWithSpec = this.validLoginTypes()[loginWith];
+
+ // Sanitize first
+ loginWithSanitize = loginWithSpec.sanitize;
+ if (loginWithSanitize) {
+ login = loginWithSanitize(login);
+ }
+
+ // Validate second
+ validateLoginWith = loginWithSpec.validate;
+ if (validateLoginWith) {
+ if (!validateLoginWith(login)) {
+ // Add error third
+ errors.push(loginWithSpec.error);
+ }
+ }
+ }
if (!password) errors.push('Missing password');
return errors;
})
+ .validateRegistration( function (newUserAttributes, baseErrors) {
+ return baseErrors;
+ })
.maybeBreakToRegistrationFailSteps( function (req, res, errors, newUserAttributes) {
- var user;
+ var user, loginField, loginKey;
if (errors && errors.length) {
user = newUserAttributes;
+ loginField = this.loginFormFieldName();
+ loginKey = this.loginKey();
+ if (loginField !== loginKey) {
+ user[loginField] = user[loginKey];
+ delete user[this.loginKey()];
+ }
delete user.password;
return this.breakTo('registrationFailSteps', req, res, errors, user);
}
@@ -198,3 +243,48 @@ everyModule.submodule('password')
, userParams: newUserAttributes
});
});
+
+function validateEmail (value) {
+ // From Scott Gonzalez: http://projects.scottsplayground.com/email_address_validation/
+ var isValid = /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?$/i.test(value);
+
+ return isValid;
+}
+
+password.validLoginTypes({
+ login: {}
+ , email: {
+ validate: validateEmail
+ , error: 'Please correct your email.'
+ }
+ , phone: {
+ sanitize: function (value) {
+ // Pull out only digits and 'x' for extension
+ var digitOrX = /[\dx]/i
+ , match, ret = '';
+ while (match = digitOrX.exec(value)) {
+ ret += match[0];
+ }
+ return ret;
+ }
+ , validate: function (value) {
+ return value.length >= 7;
+ }
+ , error: 'Please correct your phone.'
+ }
+});
+
+password.loginWith = function (loginType) {
+ if (!arguments.length) return this._loginType;
+
+ this._loginType = loginType;
+ var name
+ , validLoginTypes = Object.keys(this.validLoginTypes());
+ if (-1 === validLoginTypes.indexOf(loginType)) {
+ throw new Error("loginWith only supports " + validLoginTypes.join(', '));
+ }
+ this.loginFormFieldName(loginType);
+ this.loginKey(loginType);
+ this.loginHumanName(loginType);
+ return this;
+};

0 comments on commit 263343d

Please sign in to comment.