Browse files

changed the api slightly, and beefed up documentation.

  • Loading branch information...
1 parent 4c23504 commit 9fd9748ea076aa5434dc1b7800b723b9332b4458 @treygriffith committed Mar 17, 2013
Showing with 753 additions and 0 deletions.
  1. +26 −0 .gitignore
  2. +181 −0 README.md
  3. +198 −0 drivers/fs.js
  4. +51 −0 drivers/generic.js
  5. +7 −0 drivers/index.js
  6. +101 −0 drivers/mongo.js
  7. +9 −0 example/app.js
  8. +168 −0 index.js
  9. +5 −0 package.json
  10. +7 −0 utils.js
View
26 .gitignore
@@ -0,0 +1,26 @@
+### OSX ###
+.DS_Store
+.AppleDouble
+.LSOverride
+Icon
+
+# Files that might appear on external disk
+.Spotlight-V100
+.Trashes
+
+### Node ###
+lib-cov
+*.seed
+*.log
+*.csv
+*.dat
+*.out
+*.pid
+*.gz
+
+pids
+logs
+results
+
+npm-debug.log
+node_modules
View
181 README.md
@@ -0,0 +1,181 @@
+Authoritative Key Server
+========================
+`aks` is an implementation of an Authoritative PGP Key Server, using HKP Version 2, as outlined in [this blog post]() and further defined in the [Public API]().
+It is intended to be a demonstration of the concept of an Authoritative Key Server using updated REST principles.
+
+Installation
+------------
+
+Through [NPM](http://www.npmjs.org)
+ ``` bash
+ $ npm install aks
+ ```
+
+or using Git
+ ``` bash
+ $ git clone git://github.com/treygriffith/aks .git node_modules/aks/
+ ```
+
+Usage
+-----
+
+`aks` can be run as a stand-alone web server or as an additional web server inside of a Node application. To create it, simply create an instance of `aks` and instruct it to listen on the proper port.
+
+It takes as a parameter a database driver that implements the methods described in [Key Database Drivers](). It ships with a MongoDB driver (on top of Mongoose) as well as a Filesystem driver, meant only for local dev use, not as a production server.
+
+ ``` javascript
+ var aks = require('aks');
+
+ var mongoUrl = 'mongodb://' + db.user + ':' + db.pass + '@' + db.host + ':' + db.port + '/' + db.name;
+
+ var mongoKeyDatabase = new aks.drivers.Mongo(mongoUrl, 'keys'); // the storage mechanism for the keys is divorced from serving the keys themselves.
+
+ var server = new aks.Server(mongoKeyDatabase);
+
+ server.listen(); // defaults to the typical HKP Port of 11371
+ ```
+
+Once the Key Server is listening on a port, it will respond to requests formed in accordance with the [Public API]().
+
+
+#### Server Options
+
+The optional second parameter when starting the AKS server is an object of options. The possible properties for the options object are:
+* `trustProxy` - If set to a truthy value, this tells AKS to trust a proxy which handles SSL connections by respecting the `X-Forwarded-Proto` header. [See the Express documentation of `trust proxy`](http://expressjs.com/api.html#app-settings) for more information. Defaults to false.
+* `useIndex` - If set to a truthy value, this tells AKS to ignore requests for multiple users, as outlined in [Spam Concerns](). Defaults to false.
+* `baseUri` - If set, this should a string, beginning and ending with a forward slash, that defines the base uri at which the key server listens for requests. This is to facillitate the key server co-existing with other services on a single server. Defaults to `/`.
+
+HKP Version 2
+-------------
+
+The public API for this Authoritative Key Server is available over HTTP and HTTPS. It uses a protocol known as HKP (HTTP Keyserver Procotol). [The IETF draft of this protocol](http://tools.ietf.org/html/draft-shaw-openpgp-hkp-00) is the basis for much of the API, and it was itself based on earlier implementations of Keyservers, such as [Marc Horowitz's Public Key Server](http://www.mit.edu/afs/net.mit.edu/project/pks/thesis/paper/thesis.html).
+
+Although the above draft was never approved, HKP as referenced herein will be known as "version 2", as the API that AKS uses is substantially different from that outlined in the above draft and the implementations of Public Keyservers in the wild.
+
+HKP version 2 is an updated form of the protocol with an emphasis on REST principles, practical uses of Keyservers, and the usage of a Keyserver as an Authoritative Key Server. As such, it does not implement many of the same methods as HKP version 1, and the methods it does implement are done so in a substantially different way. HKP version 2 should be considered incompatible with version 1.
+
+Version 2's heavy emphasis on Authoritative Keyservers means that it is missing many features which might make it more amenable to uses such [SKS](https://bitbucket.org/skskeyserver/sks-keyserver/wiki/Home) which distribute keys to many servers at once.
+
+### Public API
+
+#### Retrieving all the users on a keyserver
+
+HKP version 1 defined the index of the keys as all of the individual keyid's. While technically true, for most use cases it is more helpful to have an index of users with keys, regardless of whether or not some of those users (again defined by unique email addresses) have the same key.
+
+HKP version 2 uses this more practical definition of an index, which is retrieved by sending a `GET` request to `/users`. The response is a JSON object, which has two properties: `version` and `keys`. `version` defines the version of the HKP protocol in use, in our case it is always `2`. `keys` are an array of key objects, corresponding to all the unique users who have keys on this server. Each key object has a single property defined, `path`, which defines the relative path (not the absolute path) to the user's Public Key Block.
+
+ ```
+ GET http://keys.example.com/users
+ ```
+could return
+ ```
+ {
+ "version": 2,
+ "keys": [
+ {
+ "path": "example.com/alice"
+ },
+ {
+ "path": "example.com/bob"
+ }
+ ]
+ }
+ ```
+
+HKP version 1 defined a variable, `mr` to designate whether a response should be machine-readable or human-readable. Since encryption is generally something better undertaken by machines than humans, HKP version 2 assumes all request to be machine-readable, but the responses are in formats (like JSON) that are also easily read by humans.
+
+Each `key` can optionally contain additional properties describing the key, including the `keyid`, algorithm, etc., but the protocol only requires the `path` property.
+
+#### Retrieving all the users for a domain on a keyserver
+
+As might be expected from the `path`s returned from index, it is possible to retrieve all the users for a particular domain by using the domain as a the endpoint. The JSON object returned is the same as for the index route, but the paths do not include the domain as they are relative to the current endpoint.
+
+ ```
+ GET http://keys.example.com/users/example.com
+ ```
+could return
+ ```
+ {
+ "version": 2,
+ "keys": [
+ {
+ "path": "alice"
+ },
+ {
+ "path": "bob"
+ }
+ ]
+ }
+ ```
+
+#### Retrieving a user's public key
+
+HKP version 1 relied on string searching to find the key id for a particular user, and then `GET`ing that key id to retrieve the Public Key Block. HKP version 2, by contrast does not support string searching, and instead returns the Public Key Block for a user (as defined by a unique email address) when sending a `GET` request to `/users/:domain/:user`. For example:
+ ```
+ GET http://keys.example.com/users/example.com/alice
+ ```
+could return
+ ```
+ -----BEGIN PGP PUBLIC KEY BLOCK-----
+ Version: GnuPG v1.0.1 (GNU/Linux)
+ Comment: For info see http://www.gnupg.org
+
+ mQGiBDkHP3URBACkWGsYh43pkXU9wj/X1G67K8/DSrl85r7dNtHNfLL/ewil10k2
+ q8saWJn26QZPsDVqdUJMOdHfJ6kQTAt9NzQbgcVrxLYNfgeBsvkHF/POtnYcZRgL
+ tZ6syBBWs8JB4xt5V09iJSGAMPUQE8Jpdn2aRXPApdoDw179LM8Rq6r+gwCg5ZZa
+ pGNlkgFu24WM5wC1zg4QTbMD/3MJCSxfL99Ek5HXcB3yhj+o0LmIrGAVBgoWdrRd
+ BIGjQQFhV1NSwC8YhN/4nGHWpaTxgEtnb4CI1wI/G3DK9olYMyRJinkGJ6XYfP3b
+ cCQmqATDF5ugIAmdditnw7deXqn/eavaMxRXJM/RQSgJJyVpbAO2OqKe6L6Inb5H
+ kjcZA/9obTm499dDMRQ/CNR92fA5pr0zriy/ziLUow+cqI59nt+bEb9nY1mfmUN6
+ SW0jCH+pIQH5lerV+EookyOyq3ocUdjeRYF/d2jl9xmeSyL2H3tDvnuE6vgqFU/N
+ sdvby4B2Iku7S/h06W6GPQAe+pzdyX9vS+Pnf8osu7W3j60WprQkUGF1bCBHYWxs
+ YWdoZXIgPHBhdWxnYWxsQHJlZGhhdC5jb20+iFYEExECABYFAjkHP3UECwoEAwMV
+ AwIDFgIBAheAAAoJEJECmvGCPSWpMjQAoNF2zvRgdR/8or9pBhu95zeSnkb7AKCm
+ /uXVS0a5KoN7J61/1vEwx11poLkBQdQ5Bz+MEAQA8ztcWRJjW8cHCgLaE402jyqQ
+ 37gDT/n4VS66nU+YItzDFScVmgMuFRzhibLblfO9TpZzxEbSF3T6p9hLLnHCQ1bD
+ HRsKfh0eJYMMqB3+HyUpNeqCMEEd9AnWD9P4rQtO7Pes38sV0lX0OSvsTyMG9wEB
+ vSNZk+Rl+phA55r1s8cAAwUEAJjqazvk0bgFrw1OPG9m7fEeDlvPSV6HSA0fvz4w
+ c7ckfpuxg/URQNf3TJA00Acprk8Gg8J2CtebAyR/sP5IsrK5l1luGdk+l0M85FpT
+ /cen2OdJtToAF/6fGnIkeCeP1O5aWTbDgdAUHBRykpdWU3GJ7NS6923fVg5khQWg
+ uwrAiEYEGBECAAYFAjkHP4wACgkQkQKa8YI9JamliwCfXox/HjlorMKnQRJkeBcZ
+ iLyPH1QAoI33Ft/0HBqLtqdtP4vWYQRbibjW
+ =BMEc
+ -----END PGP PUBLIC KEY BLOCK-----
+ ```
+
+### Spam Concerns
+
+There are (legitimate) concerns that exposing all the users on a keyserver with a simple `GET` request, or all the users of a particular domain can lead to users who are published in this way to be targeted by spammers. Some Public Key Servers, such as the PGP Global Directory have attempted to combat this behavior by requiring users to solve a CAPTCHA before they are granted access to the API. However, since the philosophy of HKP version 2 is that machines, not humans, should be handling encryption, a CAPTCHA is decidely the wrong mechanism.
+
+HKP version 2 makes requests for all the users of a keyserver easier, but fundamentally presents the same opportunity for spammers. To combat this, individual implementations can choose to not make the Index methods accessible. The only method that is REQUIRED for HKP Version 2 is retrieving the public key for a single user. In AKS's implementation, the option `useIndex` can be set to false to disable these methods. In addition, implemenations may choose to use rate limits, API keys, or other methods to attempt to stop spammers from accessing the keyserver.
+
+### Multiple Users for a Single Key
+
+Multiple unique email addresses can be associated with a single PGP key. HKP version 1 approached the key as the fundamental unit, and listed all of the users for which the key was applicable. Since there are very few applications for which it would be useful to know all the users of a particular key, instead of the key for a particular user, HKP version 2 takes a fundamentally different approach.
+
+However, one of the instances in which it would be useful to know all the users for a single key would be communicating with a large group that all shared a single PGP key. This scenario is one in which the members of the group corresponded about the shared group key ahead of time, and as a result, a Keyserver is not a necessity.
+
+If this scenario (or others like it) do turn out to be an important use for HKP version 2 Keyservers, the protocol can be expanded, perhaps through a `/groups` endpoint.
+
+Key Database Drivers
+--------------------
+
+AKS is implemented such that it is agnostic to how keys are stored/retrieved and interacts with any storage mechanism through a driver that implements the methods required by AKS.
+
+Two database drivers are included with this distribution:
+1. A [filesystem driver](drivers/fs.js) intended as a local demonstration of an AKS, and
+2. A [Mongo driver](drivers/mongo.js) intended as a basic MongodDB interface for an Authoritative Key Server.
+
+Compliant Key Database Drivers implement the following methods:
+* `findOne`
+ The `findOne` method calls back with a single `key` object when supplied with a valid email address as the first parameter. The key object should have at least the following properties defined:
+ * `keytext` - The Public Key Block
+ * `uid` - The email address which uniquely identifies this key
+ * `user` - Portion of the email address prior to the `@`
+ * `domain` - The domain of the user (portion of the email address after the `@`)
+* `find`
+ The find method should take the `domain` as an optional first parameter. If supplied, it should call back with an array of keys corresponding to users of the `domain`. If `domain` is admitted, it should call back with an array of keys for all users on the keyserver. The `key` objects in the array should have the same properties defined as for the `findOne` method with the exception of `keytext`.
+* `add`
+ The `add` method should store a key object when supplied with an email address as the first parameter and the Public Key Block as the second parameter. While this method is not currently used by the Public API, it will likely be implemented in the near future.
+
+A [generic driver](drivers/generic.js) is included with this distribution as a starting point for future database drivers.
View
198 drivers/fs.js
@@ -0,0 +1,198 @@
+/**
+ * Module Dependencies
+ */
+
+var Filequeue = require('filequeue');
+var fq = new Filequeue();
+var path = require('path');
+
+
+/**
+ * Intialiaze a new FsKDb (Filesystem Key Database)
+ * @param {String} baseDir Base Directory in which keys are stored
+ */
+function FsKDb(baseDir) {
+
+ this.baseDir = baseDir;
+
+ return this;
+}
+
+/**
+ * Find a single key from the email uid
+ * @param {String} email Email uid to retrieve a key for
+ * @param {Function} callback Function to evaluate with the results of the find
+ */
+FsKDb.prototype.findOne = function(email, callback) {
+ var parts = email.split('@'),
+ domain = parts[1],
+ user = parts[0],
+ keyPath = path.resolve(this.baseDir, domain, email);
+
+ fq.exists(keyPath, function(exists) {
+
+ if(!exists) {
+ callback(); // no key found
+ return;
+ }
+
+ fq.readFile(keyPath, 'utf8', function(err, keytext) {
+
+ if(err) {
+ callback(err);
+ return;
+ }
+
+ if(!keytext || (keytext && !keytext.length)) {
+ callback(); // file exists but it is empty
+ return;
+ }
+
+ callback(null, {
+ uid:email,
+ user: user,
+ domain: domain,
+ keytext: keytext
+ });
+
+ });
+ });
+};
+
+/**
+ * Find all the keys for this server
+ * @param {String} domain Optional domain in which to search for keys
+ * @param {Function} callback Function to evaluate with the results of the find
+ */
+FsKDb.prototype.find = function(domain, callback) {
+ if(!callback) {
+ callback = domain;
+ domain = null;
+ }
+
+ var baseDir = this.baseDir,
+ keys = [];
+
+ var getUsers = function(domain, callback) {
+ // get all the users for the domain
+ fq.readdir(path.resovle(baseDir, domain), function(err, users) {
+
+ if(err) {
+ callback(err);
+ return;
+ }
+
+ users.forEach(function(user) {
+
+ // add to the array with the email as the uid
+ keys.push({
+ uid: user + '@' + domain,
+ user: user,
+ domain: domain
+ });
+
+ });
+
+ callback(null, keys.sort()); // sort the keys prior to returning them
+
+ });
+ };
+
+ if(domain) {
+
+ // get only users for the defined domain
+ getUsers(domain, callback);
+ return;
+ }
+
+ // get all the domains stored in the directory
+ fq.readdir(baseDir, function(err, domains) {
+
+ if(err) {
+ callback(err);
+ return;
+ }
+
+ if(!domains || (domains && !domains.length)) {
+ callback(null, keys);
+ return;
+ }
+
+ var count = 0;
+
+ domains.forEach(function(domain) {
+
+ getUsers(domain, function(err, keys) {
+
+ if(err) {
+ callback(err);
+ return;
+ }
+
+ if(++count === domains.length) { // the last domain has been fetched
+ callback(null, keys);
+ }
+
+ });
+
+ });
+
+ });
+};
+
+/**
+ * Add a key to the database
+ * @param {String} email Email to associate with the key
+ * @param {String} keytext ASCII-armored keytext including headers
+ * @param {Function} callback Function to evaluate with an error or the added key on success
+ */
+FsKDb.prototype.add = function(email, keytext, callback) {
+ var parts = email.split('@'),
+ domain = parts[1],
+ user = parts[0],
+ domainPath = path.resolve(this.baseDir, domain),
+ keyPath = path.resolve(this.baseDir, domain, user);
+
+ var createKey = function(cb) {
+
+ fq.writeFile(keyPath, keytext, 'utf8', function(err) {
+
+ if(err) {
+ cb(err);
+ return;
+ }
+
+ cb(null, {uid:email, user:user, domain:domain, keytext:keytext});
+
+ });
+
+ };
+
+ fq.exists(domainPath, function(exists) {
+
+ if(!exists) {
+ fq.mkdir(domainPath, function(err) {
+
+ if(err) {
+ callback(err);
+ return;
+ }
+
+ createKey(callback);
+
+ });
+
+ return;
+ }
+
+ createKey(callback);
+
+ });
+
+};
+
+/**
+ * Export the FsKDb object
+ */
+
+module.exports = FsKDb;
View
51 drivers/generic.js
@@ -0,0 +1,51 @@
+///////////////////////////////////
+// Generic Driver Implementation //
+///////////////////////////////////
+
+/**
+ * Initalize a new Driver Object
+ */
+function Driver() {
+ var kdb = this;
+
+ return this;
+}
+
+
+/**
+ * Find a single key from the email uid
+ * @param {String} email Email uid to retrieve a key for
+ * @param {Function} callback Function to evaluate with the results of the find
+ */
+Driver.prototype.findOne = function(email, callback) {
+
+};
+
+
+
+ /**
+ * Find all the keys for this server
+ * @param {String} domain Optional domain in which to search for keys
+ * @param {Function} callback Function to evaluate with the results of the find
+ */
+Driver.prototype.find = function(domain, callback) {
+
+};
+
+
+/**
+ * Add a key to the database
+ * @param {String} email Email to associate with the key
+ * @param {String} keytext ASCII-armored keytext including headers
+ * @param {Function} callback Function to evaluate with an error or the added key on success
+ */
+Driver.prototype.add = function(email, keytext, callback) {
+
+};
+
+
+/**
+ * Export the Driver object
+ */
+
+module.exports = Driver;
View
7 drivers/index.js
@@ -0,0 +1,7 @@
+/**
+ * Export the pre-packaged database drivers
+ */
+
+exports.Mongo = require('./mongo');
+
+exports.Fs = require('./fs');
View
101 drivers/mongo.js
@@ -0,0 +1,101 @@
+/**
+ * Module Dependencies
+ */
+
+var mongoose = require('mongoose');
+var mongooseTypes = require('mongoose-types');
+mongooseTypes.loadTypes(mongoose, 'email');
+
+
+/**
+ * Initalize a new MongoKDb (MongoDB Key Database)
+ */
+function MongoKDb(url, collectionName) {
+
+ mongoose.connect(url);
+
+ var key = mongoose.Schema({
+ uid: mongoose.SchemaTypes.Email,
+ domain: String,
+ user: String,
+ keytext: String
+ });
+
+ this.model = this.collection = mongoose.model(collectionName, key);
+
+ return this;
+}
+
+
+/**
+ * Find a single key from the email uid
+ * @param {String} email Email uid to retrieve a key for
+ * @param {Function} callback Function to evaluate with the results of the find
+ */
+MongoKDb.prototype.findOne = function(email, callback) {
+
+ this.collection.findOne({uid:email}, callback);
+
+};
+
+
+
+ /**
+ * Find all the keys for this server
+ * @param {String} domain Optional domain in which to search for keys
+ * @param {Function} callback Function to evaluate with the results of the find
+ */
+MongoKDb.prototype.find = function(domain, callback) {
+
+ var query = {};
+
+ if(!callback) {
+ callback = domain;
+ domain = null;
+ }
+
+ if(domain) {
+ query.domain = domain;
+ }
+
+ this.collection.find(query, callback);
+
+};
+
+
+/**
+ * Add a key to the database
+ * @param {String} email Email to associate with the key
+ * @param {String} keytext ASCII-armored keytext including headers
+ * @param {Function} callback Function to evaluate with an error or the key on success
+ */
+MongoKDb.prototype.add = function(email, keytext, callback) {
+ var parts = email.split('@'),
+ user = parts[0],
+ domain = parts[1];
+
+ var key = new this.model({
+ uid:email,
+ domain:domain,
+ user:user
+ keytext:keytext
+ });
+
+ key.save(function(err, key) {
+ if(err) {
+ callback(err);
+ return;
+ }
+ callback(null, {
+ email: key.email,
+ keytext: key.keytext
+ });
+ });
+};
+
+
+/**
+ * Export the MongoKDb object
+ */
+
+module.exports = MongoKDb;
View
9 example/app.js
@@ -0,0 +1,9 @@
+var aks = require('../');
+
+var fsKeyDatabase = new aks.drivers.Fs('keys'); // the fs driver is for demonstration only
+
+var server = new aks.Server(fsKeyDatabase, {
+ trustProxy: true
+});
+
+server.listen(process.env.PORT || 11371);
View
168 index.js
@@ -0,0 +1,168 @@
+/**
+ * Module Dependencies
+ */
+
+var utils = require('./utils');
+var express = require('express');
+
+
+/**
+ * Initalize the Key Server
+ * @param {Object} db Database driver that implements methods as described in './drivers/generic'
+ * @param {Object} options Options for the AKS instance
+ */
+function AKS(db, options) {
+ var useIndex = options.useIndex || false,
+ trustProxy = options.trustProxy || false,
+ baseUri = options.baseUri || '/',
+ uri = baseUri + 'keys';
+
+ var version = this.version = 2;
+
+ this.db = db;
+ this.app = express();
+
+ if(trustProxy) {
+ this.app.enable('trust proxy');
+ }
+
+ this.app.get(uri + '/', function(req, res) {
+
+ if(!useIndex) {
+
+ // Index of all keys is unsupported
+ res.status(501);
+ res.send();
+ return;
+
+ }
+
+ // retrieve all the available usernames with associated references to keys
+ db.find(function(err, keys) {
+
+ if(err) {
+ res.status(500);
+ res.send(err);
+ return;
+ }
+
+ var displayKeys = {
+ version: version,
+ keys: []
+ };
+
+ keys.forEach(function(key) {
+ displayKeys.keys.push({
+ path: key.domain + '/' + key.user
+ });
+ });
+
+ res.status(200);
+ res.json(displayKeys);
+
+ });
+
+ });
+
+ this.app.get(uri + '/:domain', function(req, res) {
+
+ var domain = req.params.domain;
+
+ if(!useIndex) {
+
+ // Index of all keys is unsupported
+ res.status(501);
+ res.send();
+ return;
+
+ }
+
+ // retrieve all users for this domain with associated references to keys
+ db.find(domain, function(err, keys) {
+
+ if(err) {
+ res.status(500);
+ res.send(err);
+ return;
+ }
+
+ var displayKeys = {
+ version: version,
+ keys: []
+ };
+
+ keys.forEach(function(key) {
+ displayKeys.keys.push({
+ path: key.user
+ });
+ });
+
+ res.status(200);
+ res.json(displayKeys);
+
+ });
+
+ });
+
+ this.app.get(uri + '/:domain/:user', function(req, res) {
+
+ var email = req.params.user + '@' + req.params.domain;
+
+ if(!utils.isValidEmail(email)) {
+ res.status(400);
+ res.send();
+ return;
+ }
+
+ // retrieve the key associated with this email address
+ db.findOne(email, function(err, key) {
+
+ if(err) {
+ res.status(500);
+ res.send(err);
+ return;
+ }
+
+ if(!key || (key && !key.keytext)) {
+ res.status(404);
+ res.send();
+ return;
+ }
+
+ res.status(200);
+ res.send(key.keytext);
+
+ });
+
+ });
+
+ this.app.listen(process.env.PORT || 3000);
+
+ return this;
+}
+
+AKS.prototype.listen = function(port, args) {
+ if(typeof port === 'number') {
+ args = Array.prototype.slice.call(arguments, 1);
+ } else {
+ port = 11371;
+ args = Array.prototype.slice.call(arguments);
+ }
+
+ args.unshift(port);
+
+ this.app.listen.apply(this.app, args);
+};
+
+/**
+ * Export the Authoritative Key Server
+ */
+
+exports.Server = AKS;
+
+
+/**
+ * Export the standard database drivers
+ */
+
+exports.drivers = require('./drivers');
View
5 package.json
@@ -15,8 +15,13 @@
"aks"
],
"dependencies": {
+ "express" : "3.1.x",
+ "mongoose": "3.5.x",
+ "mongoose-types": "1.0.x",
+ "filequeue": "0.1.2"
},
"devDependencies": {
+ "mocha": "1.8.x"
},
"main": "./index.js",
"engines": {
View
7 utils.js
@@ -0,0 +1,7 @@
+exports.isValidEmail = function(email) {
+ var pattern = /^((([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;
+ if(email.match(pattern)) {
+ return true;
+ }
+ return false;
+};

0 comments on commit 9fd9748

Please sign in to comment.