Comparing version 0.1.2 to 0.1.3
@@ -28,6 +28,5 @@ /* Base64 conversion functions | ||
* OTHER DEALINGS IN THE SOFTWARE. | ||
*/ | ||
/* base64 encode/decode compatible with window.btoa/atob | ||
* | ||
* base64 encode/decode compatible with window.btoa/atob | ||
* | ||
* window.atob/btoa is a Firefox extension to convert binary data (the "b") | ||
@@ -51,4 +50,7 @@ * to base64 (ascii, the "a"). | ||
* then an exception is thrown. | ||
* | ||
* -*- Mode: JS; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- | ||
* vim: set sw=2 ts=2 et tw=80 : | ||
*/ | ||
base64 = {}; | ||
var base64 = {}; | ||
base64.PADCHAR = '='; | ||
@@ -55,0 +57,0 @@ base64.ALPHA = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; |
@@ -35,2 +35,5 @@ /* | ||
* and disclaimer. | ||
* | ||
* -*- Mode: JS; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- | ||
* vim: set sw=2 ts=2 et tw=80 : | ||
*/ | ||
@@ -37,0 +40,0 @@ |
@@ -24,2 +24,5 @@ /* Conversion functions used in OpenID for node.js | ||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
* | ||
* -*- Mode: JS; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- | ||
* vim: set sw=2 ts=2 et tw=80 : | ||
*/ | ||
@@ -26,0 +29,0 @@ |
@@ -24,2 +24,5 @@ /* A simple XRDS and Yadis parser written for OpenID for node.js | ||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
* | ||
* -*- Mode: JS; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- | ||
* vim: set sw=2 ts=2 et tw=80 : | ||
*/ | ||
@@ -61,3 +64,3 @@ | ||
var idMatch = /<(Local|Canonical)ID\s*?>(.*)<\/\1ID\s*?>/g.exec(service); | ||
var idMatch = /<(Local|Canonical)ID\s*?>(.*?)<\/\1ID\s*?>/g.exec(service); | ||
if(idMatch) | ||
@@ -90,10 +93,13 @@ { | ||
for(var i in svcs) | ||
{ | ||
var svc = svcs[i]; | ||
services.splice(svc.priority, 0, svc); | ||
} | ||
services.push.apply(services, svcs); | ||
} | ||
services.sort(function(a, b) | ||
{ | ||
return a.priority < b.priority | ||
? -1 | ||
: (a.priority == b.priority ? 0 : 1); | ||
}); | ||
return services; | ||
} |
537
openid.js
@@ -1,3 +0,1 @@ | ||
/* -*- Mode: JS; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ | ||
/* vim: set sw=2 ts=2 et tw=80 : */ | ||
/* OpenID for node.js | ||
@@ -26,2 +24,5 @@ * | ||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
* | ||
* -*- Mode: JS; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- | ||
* vim: set sw=2 ts=2 et tw=80 : | ||
*/ | ||
@@ -45,2 +46,22 @@ | ||
openid.RelyingParty = function(returnUrl, realm, stateless, strict, extensions) | ||
{ | ||
this.returnUrl = returnUrl; | ||
this.realm = realm || null; | ||
this.stateless = stateless; | ||
this.strict = strict; | ||
this.extensions = extensions; | ||
} | ||
openid.RelyingParty.prototype.authenticate = function(identifier, immediate, callback) | ||
{ | ||
openid.authenticate(identifier, this.returnUrl, this.realm, | ||
immediate, this.stateless, callback, this.extensions, this.strict); | ||
} | ||
openid.RelyingParty.prototype.verifyAssertion = function(requestOrUrl, callback) | ||
{ | ||
openid.verifyAssertion(requestOrUrl, callback, this.stateless, this.extensions); | ||
} | ||
function _isDef(e) | ||
@@ -110,2 +131,3 @@ { | ||
theUrl = url.parse(theUrl); | ||
theUrl.search = false; | ||
if(params) | ||
@@ -143,3 +165,4 @@ { | ||
} | ||
var options = { | ||
var options = | ||
{ | ||
host: getUrl.hostname, | ||
@@ -169,29 +192,34 @@ port: _isDef(getUrl.port) ? getUrl.port : | ||
}); | ||
}).on("error", function () { | ||
callback(); | ||
}).on('error', function(error) | ||
{ | ||
callback(error); | ||
}); | ||
} | ||
function _post(getUrl, data, callback, redirects) | ||
function _post(postUrl, data, callback, redirects) | ||
{ | ||
redirects = redirects || 5; | ||
getUrl = url.parse(getUrl); | ||
postUrl = url.parse(postUrl); | ||
var path = getUrl.pathname || '/'; | ||
if(getUrl.query) | ||
var path = postUrl.pathname || '/'; | ||
if(postUrl.query) | ||
{ | ||
path += '?' + getUrl.query; | ||
path += '?' + postUrl.query; | ||
} | ||
var encodedData = _encodePostData(data); | ||
var options = { | ||
host: getUrl.hostname, | ||
port: _isDef(getUrl.port) ? getUrl.port : | ||
(getUrl.protocol == 'https:' ? 443 : 80), | ||
var options = | ||
{ | ||
host: postUrl.hostname, | ||
path: path, | ||
headers: {'Content-Type': 'application/x-www-form-urlencoded', | ||
'Content-Length': encodedData.length}, | ||
port: _isDef(postUrl.port) ? postUrl.port : | ||
(postUrl.protocol == 'https:' ? 443 : 80), | ||
headers: | ||
{ | ||
'Content-Type': 'application/x-www-form-urlencoded', | ||
'Content-Length': encodedData.length | ||
}, | ||
method: 'POST' | ||
}; | ||
(getUrl.protocol == 'https:' ? https : http).request(options, function(res) | ||
(postUrl.protocol == 'https:' ? https : http).request(options, function(res) | ||
{ | ||
@@ -215,4 +243,5 @@ var data = ''; | ||
}); | ||
}).on("error", function () { | ||
callback(); | ||
}).on('error', function(error) | ||
{ | ||
callback(error); | ||
}).end(encodedData); | ||
@@ -286,3 +315,3 @@ } | ||
{ | ||
provider.claimedIdentifier = service.canonicalIdentifier; | ||
provider.claimedIdentifier = service.id; | ||
} | ||
@@ -292,3 +321,3 @@ if(service.type == 'http://specs.openid.net/auth/2.0/signon') | ||
provider.version = 'http://specs.openid.net/auth/2.0'; | ||
provider.localIdentifier = service.localIdentifier; | ||
provider.localIdentifier = service.id; | ||
} | ||
@@ -333,3 +362,3 @@ else if(service.type == 'http://specs.openid.net/auth/2.0/server') | ||
{ | ||
var providerLinkMatches = new RegExp('<link\\s+.*?rel="' + rel + '".*?>', 'ig').exec(html); | ||
var providerLinkMatches = new RegExp('<link\\s+.*?rel="[^"]*?' + rel + '[^"]*?".*?>', 'ig').exec(html); | ||
@@ -350,3 +379,3 @@ if(!providerLinkMatches || providerLinkMatches.length < 1) | ||
_parseHtml = function(htmlUrl, html, callback, hops) | ||
function _parseHtml (htmlUrl, html, callback, hops) | ||
{ | ||
@@ -486,11 +515,10 @@ var metaUrl = _matchMetaTag(html); | ||
// Try XRDS/Yadis discovery | ||
_resolveXri(identifier, function(data) | ||
_resolveXri(identifier, function(providers) | ||
{ | ||
if(data == null) | ||
if(providers == null) | ||
{ | ||
// Fallback to HTML discovery | ||
_resolveHtml(identifier, function(data) | ||
_resolveHtml(identifier, function(providers) | ||
{ | ||
callback(data); | ||
callback(providers); | ||
}); | ||
@@ -500,3 +528,13 @@ } | ||
{ | ||
callback(data); | ||
// Add claimed identifier to providers with local identifiers | ||
// to ensure correct resolution of identities | ||
for(var p in providers) | ||
{ | ||
var provider = providers[p]; | ||
if(!provider.claimedIdentifier && provider.localIdentifier) | ||
{ | ||
provider.claimedIdentifier = identifier; | ||
} | ||
} | ||
callback(providers); | ||
} | ||
@@ -531,3 +569,3 @@ }); | ||
openid.associate = function(provider, callback, algorithm) | ||
openid.associate = function(provider, callback, strict, algorithm) | ||
{ | ||
@@ -564,17 +602,27 @@ var params = _generateAssociationRequestParameters(provider.version, algorithm); | ||
{ | ||
// TODO: Should assert secure channel before | ||
// allowing unencrypted association? | ||
// Alternatively, one can drop association and | ||
// use dumb mode (verify), which is secure | ||
if(algorithm == 'DH-SHA1' /*&& url.protocol == 'https:'*/) | ||
if(algorithm == 'DH-SHA1') | ||
{ | ||
openid.associate(provider, callback, 'no-encryption-256'); | ||
if(strict && url.protocol != 'https:') | ||
{ | ||
callback({ error: 'Channel is insecure and no encryption method is supported by provider' }); | ||
} | ||
else | ||
{ | ||
openid.associate(provider, callback, strict, 'no-encryption-256'); | ||
} | ||
} | ||
else if(algorithm == 'no-encryption-256') | ||
{ | ||
openid.associate(provider, callback, 'no-encryption'); | ||
if(strict && url.protocol != 'https:') | ||
{ | ||
callback({ error: 'Channel is insecure and no encryption method is supported by provider' }); | ||
} | ||
else | ||
{ | ||
openid.associate(provider, callback, strict, 'no-encryption'); | ||
} | ||
} | ||
else if(algorithm == 'DH-SHA256') | ||
{ | ||
openid.associate(provider, callback, 'DH-SHA1'); | ||
openid.associate(provider, callback, strict, 'DH-SHA1'); | ||
} | ||
@@ -586,2 +634,6 @@ else | ||
} | ||
else if (data.error) | ||
{ | ||
callback(data); | ||
} | ||
else | ||
@@ -663,3 +715,3 @@ { | ||
openid.authenticate = function(identifier, returnUrl, realm, immediate, callback, extensions) | ||
openid.authenticate = function(identifier, returnUrl, realm, immediate, stateless, callback, extensions, strict) | ||
{ | ||
@@ -670,23 +722,49 @@ openid.discover(identifier, function(providers, version) | ||
{ | ||
callback(null, "No provider discovered for identity"); | ||
return callback(null); | ||
} | ||
for(var p in providers) | ||
var providerIndex = -1; | ||
var chooseProvider = function successOrNext(authUrl) | ||
{ | ||
var provider = providers[p]; | ||
openid.associate(provider, function(answer) | ||
if(authUrl) | ||
{ | ||
if(!answer || answer.error) | ||
return callback(authUrl); | ||
} | ||
if(++providerIndex >= providers.length) | ||
{ | ||
return callback(null); | ||
} | ||
var provider = providers[providerIndex]; | ||
if(stateless) | ||
{ | ||
_requestAuthentication(provider, null, returnUrl, | ||
realm, immediate, extensions || {}, successOrNext); | ||
} | ||
else | ||
{ | ||
openid.associate(provider, function(answer) | ||
{ | ||
// TODO: Do dumb/stateless mode | ||
callback(null, answer.error); | ||
} | ||
if(!answer || answer.error) | ||
{ | ||
successOrNext(); | ||
} | ||
else | ||
{ | ||
_requestAuthentication(provider, answer.assoc_handle, returnUrl, | ||
realm, immediate, extensions || {}, successOrNext); | ||
} | ||
}); | ||
_requestAuthentication(provider, answer.assoc_handle, returnUrl, realm, immediate, callback, extensions || {}); | ||
}); | ||
} | ||
} | ||
}; | ||
chooseProvider(); | ||
}); | ||
} | ||
function _requestAuthentication(provider, assoc_handle, returnUrl, realm, immediate, callback, extensions) | ||
function _requestAuthentication(provider, assoc_handle, returnUrl, realm, immediate, extensions, callback) | ||
{ | ||
@@ -702,5 +780,8 @@ var params = { | ||
for(var i in extensions) { | ||
for(var i in extensions) | ||
{ | ||
for(var key in extensions[i].requestParams) | ||
{ | ||
params[key] = extensions[i].requestParams[key]; | ||
} | ||
} | ||
@@ -731,6 +812,2 @@ | ||
} | ||
else | ||
{ | ||
// TODO: This is stateless mode | ||
} | ||
@@ -756,4 +833,5 @@ if(returnUrl) | ||
openid.verifyAssertion = function(requestOrUrl) | ||
openid.verifyAssertion = function(requestOrUrl, callback, stateless, extensions) | ||
{ | ||
extensions = extensions || {}; | ||
var assertionUrl = requestOrUrl; | ||
@@ -766,4 +844,3 @@ if(typeof(requestOrUrl) !== typeof('')) | ||
assertionUrl = url.parse(assertionUrl); | ||
assertionUrl.query = querystring.parse(assertionUrl.query); | ||
var params = _fixParams(assertionUrl.query); | ||
var params = querystring.parse(assertionUrl.query); | ||
@@ -773,18 +850,22 @@ var assertionError = _getAssertionError(params); | ||
{ | ||
return { authenticated: false, error: assertionError }; | ||
callback({ authenticated: false, error: assertionError }); | ||
} | ||
if(!_checkValidHandle(params)) | ||
{ | ||
return { authenticated: false, error: 'Association handle has been invalidated' }; | ||
callback({ authenticated: false, error: 'Association handle has been invalidated' }); | ||
} | ||
if(!_checkSignature(params)) | ||
_checkSignature(params, function(result) | ||
{ | ||
return { authenticated: false, error: 'Provider signature is invalid or expired' }; | ||
} | ||
if(extensions && result.authenticated) | ||
{ | ||
for(var ext in extensions) | ||
{ | ||
var instance = extensions[ext]; | ||
instance.fillResult(params, result); | ||
} | ||
} | ||
// make sure to remove already used associations to prevent replay | ||
openid.removeAssociation(params['openid.assoc_handle']); | ||
return { authenticated : true , identifier: params['openid.claimed_id'], | ||
params: params }; | ||
callback(result); | ||
}, stateless); | ||
} | ||
@@ -815,3 +896,3 @@ | ||
function _checkSignature(params) | ||
function _checkSignature(params, callback, stateless) | ||
{ | ||
@@ -821,9 +902,21 @@ if(!_isDef(params['openid.signed']) || | ||
{ | ||
return false; | ||
return callback({ authenticated: false, error: 'No signature in response' }); | ||
} | ||
if(stateless) | ||
{ | ||
_checkSignatureUsingProvider(params, callback); | ||
} | ||
else | ||
{ | ||
_checkSignatureUsingAssociation(params, callback); | ||
} | ||
} | ||
function _checkSignatureUsingAssociation(params, callback) | ||
{ | ||
var association = openid.loadAssociation(params['openid.assoc_handle']); | ||
if(!association) | ||
{ | ||
return false; | ||
return callback({ authenticated: false, error: 'Invalid association handle'}); | ||
} | ||
@@ -839,3 +932,3 @@ | ||
{ | ||
return false; | ||
return callback({ authenticated: false, error: 'At least one parameter referred in signature is not present in response'}); | ||
} | ||
@@ -849,21 +942,55 @@ message += param + ':' + value + '\n'; | ||
return ourSignature == params['openid.sig']; | ||
if(ourSignature == params['openid.sig']) | ||
{ | ||
callback({ authenticated: true }); | ||
} | ||
else | ||
{ | ||
callback({ authenticated: false, error: 'Invalid signature' }); | ||
} | ||
} | ||
// Recursive parameter lookup for node v0.2.x | ||
function _fixParams(params) { | ||
for (var key in params) { | ||
if (typeof(params[key]) == "object") { | ||
_fixParams(params[key]); | ||
for (var childkey in params[key]) { | ||
params[key + "." + childkey] = params[key][childkey]; | ||
function _checkSignatureUsingProvider(params, callback) | ||
{ | ||
var requestParams = | ||
{ | ||
'openid.mode' : 'check_authentication' | ||
}; | ||
for(var key in params) | ||
{ | ||
if(params.hasOwnProperty(key) && key != 'openid.mode') | ||
{ | ||
requestParams[key] = params[key]; | ||
} | ||
} | ||
_post(params['openid.op_endpoint'], requestParams, function(data, headers, statusCode) | ||
{ | ||
if(statusCode != 200 || data == null) | ||
{ | ||
callback({ authenticated: false, error: 'Invalid assertion check from provider'}); | ||
} | ||
else | ||
{ | ||
data = _decodePostData(data); | ||
if(data['is_valid'] == 'true') | ||
{ | ||
callback({ authenticated: true }); | ||
} | ||
delete params[key]; | ||
else | ||
{ | ||
callback({ authenticated: false, error: 'Invalid signature' }); | ||
} | ||
} | ||
} | ||
return params; | ||
}); | ||
} | ||
/* ----- Extensions ----- */ | ||
function _getExtensionAlias(params, ns) { | ||
/* ================================================================== | ||
* Extensions | ||
* ================================================================== | ||
*/ | ||
function _getExtensionAlias(params, ns) | ||
{ | ||
for (var k in params) | ||
@@ -874,103 +1001,167 @@ if (params[k] == ns) | ||
/* Simple Registration Extension: http://openid.net/specs/openid-simple-registration-extension-1_1-01.html */ | ||
var sreg_keys = ["nickname", "email", "fullname", "dob", "gender", "postcode", "country", "language", "timezone"]; | ||
openid.SimpleRegistration = function SimpleRegistration(options) { | ||
if (options.params) { // parsing the successful result | ||
var extension = _getExtensionAlias(options.params, "http://openid.net/extensions/sreg/1.1") || "sreg"; | ||
for (var i in sreg_keys) { | ||
var key = sreg_keys[i]; | ||
if (options.params["openid." + extension + "." + key]) | ||
this[key] = options.params["openid." + extension + "." + key]; | ||
} | ||
} else { // constructing the params for the auth request | ||
this.requestParams = {"openid.ns.sreg": "http://openid.net/extensions/sreg/1.1"}; | ||
if (options.policy_url) | ||
this.requestParams["openid.sreg.policy_url"] = options.policy_url; | ||
var required = []; | ||
var optional = []; | ||
for (var i in sreg_keys) { | ||
var key = sreg_keys[i]; | ||
if (options[key]) { | ||
if (options[key] == "required") | ||
required.push(key); | ||
else | ||
optional.push(key); | ||
/* | ||
* Simple Registration Extension | ||
* http://openid.net/specs/openid-simple-registration-extension-1_1-01.html | ||
*/ | ||
var sreg_keys = ['nickname', 'email', 'fullname', 'dob', 'gender', 'postcode', 'country', 'language', 'timezone']; | ||
openid.SimpleRegistration = function SimpleRegistration(options) | ||
{ | ||
this.requestParams = {'openid.ns.sreg': 'http://openid.net/extensions/sreg/1.1'}; | ||
if (options.policy_url) | ||
this.requestParams['openid.sreg.policy_url'] = options.policy_url; | ||
var required = []; | ||
var optional = []; | ||
for (var i in sreg_keys) | ||
{ | ||
var key = sreg_keys[i]; | ||
if (options[key]) | ||
{ | ||
if (options[key] == 'required') | ||
{ | ||
required.push(key); | ||
} | ||
if (required.length) | ||
this.requestParams["openid.sreg.required"] = required.join(","); | ||
if (optional.length) | ||
this.requestParams["openid.sreg.optional"] = optional.join(","); | ||
else | ||
{ | ||
optional.push(key); | ||
} | ||
} | ||
if (required.length) | ||
{ | ||
this.requestParams['openid.sreg.required'] = required.join(','); | ||
} | ||
if (optional.length) | ||
{ | ||
this.requestParams['openid.sreg.optional'] = optional.join(','); | ||
} | ||
} | ||
} | ||
}; | ||
/* User Interface Extension: http://svn.openid.net/repos/specifications/user_interface/1.0/trunk/openid-user-interface-extension-1_0.html */ | ||
openid.UserInterface = function UserInterface(options) { | ||
if (typeof(options) != "object") | ||
options = {mode: options || "popup"}; | ||
this.requestParams = {"openid.ns.ui": "http://specs.openid.net/extensions/ui/1.0"}; | ||
for (var k in options) { | ||
this.requestParams["openid.ui." + k] = options[k]; | ||
openid.SimpleRegistration.prototype.fillResult = function(params, result) | ||
{ | ||
var extension = _getExtensionAlias(params, 'http://openid.net/extensions/sreg/1.1') || 'sreg'; | ||
for (var i in sreg_keys) | ||
{ | ||
var key = sreg_keys[i]; | ||
if (params['openid.' + extension + '.' + key]) | ||
{ | ||
result[key] = params['openid.' + extension + '.' + key]; | ||
} | ||
} | ||
}; | ||
/* | ||
* User Interface Extension | ||
* http://svn.openid.net/repos/specifications/user_interface/1.0/trunk/openid-user-interface-extension-1_0.html | ||
*/ | ||
openid.UserInterface = function UserInterface(options) | ||
{ | ||
if (typeof(options) != 'object') | ||
{ | ||
options = { mode: options || 'popup' }; | ||
} | ||
this.requestParams = {'openid.ns.ui': 'http://specs.openid.net/extensions/ui/1.0'}; | ||
for (var k in options) | ||
{ | ||
this.requestParams['openid.ui.' + k] = options[k]; | ||
} | ||
}; | ||
openid.UserInterface.prototype.fillResult = function(params, result) | ||
{ | ||
// TODO: Fill results | ||
} | ||
/* Attribute Exchange Extension: http://openid.net/specs/openid-attribute-exchange-1_0.html */ | ||
/* also see http://www.axschema.org/types/ and http://code.google.com/intl/de-DE/apis/accounts/docs/OpenID.html#Parameters */ | ||
/* | ||
* Attribute Exchange Extension | ||
* http://openid.net/specs/openid-attribute-exchange-1_0.html | ||
* Also see: | ||
* - http://www.axschema.org/types/ | ||
* - http://code.google.com/intl/en-US/apis/accounts/docs/OpenID.html#Parameters | ||
*/ | ||
// TODO: count handling | ||
var attributeMapping = { | ||
"http://axschema.org/contact/country/home": "country" | ||
, "http://axschema.org/contact/email": "email" | ||
, "http://axschema.org/namePerson/first": "firstname" | ||
, "http://axschema.org/pref/language": "language" | ||
, "http://axschema.org/namePerson/last": "lastname" | ||
// the following are not in the google document: | ||
, "http://axschema.org/namePerson/friendly": "nickname" | ||
, "http://axschema.org/namePerson": "fullname" | ||
var attributeMapping = | ||
{ | ||
'http://axschema.org/contact/country/home': 'country' | ||
, 'http://axschema.org/contact/email': 'email' | ||
, 'http://axschema.org/namePerson/first': 'firstname' | ||
, 'http://axschema.org/pref/language': 'language' | ||
, 'http://axschema.org/namePerson/last': 'lastname' | ||
// The following are not in the Google document: | ||
, 'http://axschema.org/namePerson/friendly': 'nickname' | ||
, 'http://axschema.org/namePerson': 'fullname' | ||
}; | ||
openid.AttributeExchange = function AttributeExchange(options) { | ||
if (options.params) { // parsing the successful result | ||
var extension = _getExtensionAlias(options.params, "http://openid.net/srv/ax/1.0") || "ax"; | ||
var regex = new RegExp("^openid\\." + extension + "\\.(value|type)\\.(\\w+)$"); | ||
var aliases = {}; | ||
var values = {}; | ||
for (var k in options.params) { | ||
var matches = k.match(regex); | ||
if (!matches) | ||
continue; | ||
if (matches[1] == "type") | ||
aliases[options.params[k]] = matches[2]; | ||
else | ||
values[matches[2]] = options.params[k]; | ||
openid.AttributeExchange = function AttributeExchange(options) | ||
{ | ||
this.requestParams = {'openid.ns.ax': 'http://openid.net/srv/ax/1.0', | ||
'openid.ax.mode' : 'fetch_request'}; | ||
var required = []; | ||
var optional = []; | ||
for (var ns in options) | ||
{ | ||
if (options[ns] == 'required') | ||
{ | ||
required.push(ns); | ||
} | ||
for (var ns in aliases) { | ||
if (aliases[ns] in values) | ||
this[ns] = values[aliases[ns]]; | ||
else | ||
{ | ||
optional.push(ns); | ||
} | ||
} else { // constructing the params for the auth request | ||
this.requestParams = {"openid.ns.ax": "http://openid.net/srv/ax/1.0", | ||
"openid.ax.mode" : "fetch_request"}; | ||
var required = []; | ||
var optional = []; | ||
for (var ns in options) { | ||
if (options[ns] == "required") | ||
required.push(ns); | ||
else | ||
optional.push(ns); | ||
} | ||
var self = this; | ||
required = required.map(function(ns, i) | ||
{ | ||
var attr = attributeMapping[ns] || 'req' + i; | ||
self.requestParams['openid.ax.type.' + attr] = ns; | ||
return attr; | ||
}); | ||
optional = optional.map(function(ns, i) | ||
{ | ||
var attr = attributeMapping[ns] || 'opt' + i; | ||
self.requestParams['openid.ax.type.' + attr] = ns; | ||
return attr; | ||
}); | ||
if (required.length) | ||
{ | ||
this.requestParams['openid.ax.required'] = required.join(','); | ||
} | ||
if (optional.length) | ||
{ | ||
this.requestParams['openid.ax.if_available'] = optional.join(','); | ||
} | ||
} | ||
openid.AttributeExchange.prototype.fillResult = function(params, result) | ||
{ | ||
var extension = _getExtensionAlias(params, 'http://openid.net/srv/ax/1.0') || 'ax'; | ||
var regex = new RegExp('^openid\\.' + extension + '\\.(value|type)\\.(\\w+)$'); | ||
var aliases = {}; | ||
var values = {}; | ||
for (var k in params) | ||
{ | ||
var matches = k.match(regex); | ||
if (!matches) | ||
{ | ||
continue; | ||
} | ||
var self = this; | ||
required = required.map(function (ns, i) { | ||
var attr = attributeMapping[ns] || "req" + i; | ||
self.requestParams["openid.ax.type." + attr] = ns; | ||
return attr; | ||
}); | ||
optional = optional.map(function (ns, i) { | ||
var attr = attributeMapping[ns] || "opt" + i; | ||
self.requestParams["openid.ax.type." + attr] = ns; | ||
return attr; | ||
}); | ||
if (required.length) | ||
this.requestParams["openid.ax.required"] = required.join(","); | ||
if (optional.length) | ||
this.requestParams["openid.ax.if_available"] = optional.join(","); | ||
if (matches[1] == 'type') | ||
{ | ||
aliases[params[k]] = matches[2]; | ||
} | ||
else | ||
{ | ||
values[matches[2]] = params[k]; | ||
} | ||
} | ||
for (var ns in aliases) | ||
{ | ||
if (aliases[ns] in values) | ||
{ | ||
result[ns] = values[aliases[ns]]; | ||
} | ||
} | ||
} |
@@ -1,18 +0,19 @@ | ||
{ name: 'openid', | ||
description: 'OpenID 1.1/2.0 library for node.js', | ||
maintainers: [{ | ||
'name': 'Håvard Stranden', | ||
'email': 'havard.stranden@gmail.com', | ||
'web': 'http://ox.no' }], | ||
version: '0.1.2', | ||
repository: { | ||
'type': 'git', | ||
'url': 'http://github.com/havard/node-openid.git' }, | ||
bugs: 'http://github.com/havard/node-openid/issues', | ||
licenses: [{ | ||
'type' : 'MIT', | ||
'url' : 'http://github.com/havard/node-openid/raw/master/LICENSE' }], | ||
'directories' : { | ||
'lib' : './lib' }, | ||
'main' : './openid.js', | ||
{ "name": "openid", | ||
"description": "OpenID 1.1/2.0 library for node.js", | ||
"maintainers": [{ | ||
"name": "Håvard Stranden", | ||
"email": "havard.stranden@gmail.com", | ||
"web": "http://ox.no" }], | ||
"version": "0.1.3", | ||
"repository": { | ||
"type": "git", | ||
"url": "http://github.com/havard/node-openid.git" }, | ||
"bugs": "http://github.com/havard/node-openid/issues", | ||
"licenses": [{ | ||
"type" : "MIT", | ||
"url" : "http://github.com/havard/node-openid/raw/master/LICENSE" }], | ||
"directories" : { | ||
"lib" : "./lib" }, | ||
"main" : "./openid.js", | ||
"engines" : [ "node >= 0.4.1" ] | ||
} |
@@ -32,2 +32,10 @@ # OpenID for node.js | ||
var querystring = require('querystring'); | ||
var relyingParty = new openid.RelyingParty( | ||
'http://example.com/verify', // Verification URL (yours) | ||
null, // Realm (optional, specifies realm for OpenID authentication) | ||
false, // Use stateless verification | ||
false, // Strict mode | ||
[]); // List of extensions to enable and include | ||
var server = require('http').createServer( | ||
@@ -37,37 +45,35 @@ function(req, res) | ||
var parsedUrl = url.parse(req.url); | ||
if(parsedUrl.pathname == '/verify') | ||
if(parsedUrl.pathname == '/authenticate') | ||
{ | ||
// User supplied identifier | ||
var query = querystring.parse(parsedUrl.query); | ||
var identifier = query.openid_identifier; | ||
// Resolve identifier, associate, and build authentication URL | ||
relyingParty.authenticate(identifier, false, function(authUrl) | ||
{ | ||
if (!authUrl) | ||
{ | ||
res.writeHead(500); | ||
res.end(error); | ||
} | ||
else | ||
{ | ||
res.writeHead(302, { Location: authUrl }); | ||
res.end(); | ||
} | ||
}); | ||
} | ||
else if(parsedUrl.pathname == '/verify') | ||
{ | ||
// Verify identity assertion | ||
var result = openid.verifyAssertion(req); // or req.url | ||
var attributes = []; | ||
var sreg = new openid.SimpleRegistration(result); | ||
for (var k in sreg) | ||
attributes.push(k + ": " + sreg[k]); | ||
var ax = new openid.AttributeExchange(result); | ||
for (var k in ax) | ||
attributes.push(k + ": " + ax[k]); | ||
res.writeHead(200); | ||
res.end(result.authenticated ? 'Success :)\n' + attributes.join("\n") : 'Failure :(\n' + result.error); | ||
// NOTE: Passing just the URL is also possible | ||
relyingParty.verifyAssertion(req, function(result) | ||
{ | ||
res.writeHead(200); | ||
res.end(result.authenticated | ||
? 'Success :)' | ||
: 'Failure :('); | ||
}); | ||
} | ||
else if(parsedUrl.pathname == '/authenticate') | ||
{ | ||
// Resolve identifier, associate, build authentication URL | ||
openid.authenticate( | ||
querystring.parse(parsedUrl.query).openid_identifier, // user supplied identifier | ||
'http://example.com/verify', // our callback URL | ||
null, // realm (optional) | ||
false, // attempt immediate authentication first? | ||
function(authUrl) | ||
{ | ||
res.writeHead(302, { Location: authUrl }); | ||
res.end(); | ||
}, [new openid.UserInterface(), new openid.SimpleRegistration({ | ||
"nickname" : true, "email" : true, "fullname" : true, | ||
"dob" : true, "gender" : true, "postcode" : true, | ||
"country" : true, "language" : true, "timezone" : true}), | ||
new openid.AttributeExchange({ | ||
"http://axschema.org/contact/email": "required", | ||
"http://axschema.org/namePerson/friendly": "required", | ||
"http://axschema.org/namePerson": "required"})]); | ||
} | ||
else | ||
@@ -87,2 +93,4 @@ { | ||
A more elaborate example can be found in `sample.js` in the GitHub repository. | ||
## Storing association state | ||
@@ -89,0 +97,0 @@ |
102
sample.js
@@ -31,2 +31,31 @@ /* A simple sample demonstrating OpenID for node.js | ||
var querystring = require('querystring'); | ||
var extensions = [new openid.UserInterface(), | ||
new openid.SimpleRegistration( | ||
{ | ||
"nickname" : true, | ||
"email" : true, | ||
"fullname" : true, | ||
"dob" : true, | ||
"gender" : true, | ||
"postcode" : true, | ||
"country" : true, | ||
"language" : true, | ||
"timezone" : true | ||
}), | ||
new openid.AttributeExchange( | ||
{ | ||
"http://axschema.org/contact/email": "required", | ||
"http://axschema.org/namePerson/friendly": "required", | ||
"http://axschema.org/namePerson": "required" | ||
})]; | ||
var relyingParty = new openid.RelyingParty( | ||
'http://example.com/verify', // Verification URL (yours) | ||
null, // Realm (optional, specifies realm for OpenID authentication) | ||
false, // Use stateless verification | ||
false, // Strict mode | ||
extensions); // List of extensions to enable and include | ||
var server = require('http').createServer( | ||
@@ -36,43 +65,40 @@ function(req, res) | ||
var parsedUrl = url.parse(req.url); | ||
if(parsedUrl.pathname == '/verify') | ||
if(parsedUrl.pathname == '/authenticate') | ||
{ | ||
// User supplied identifier | ||
var query = querystring.parse(parsedUrl.query); | ||
var identifier = query.openid_identifier; | ||
// Resolve identifier, associate, and build authentication URL | ||
relyingParty.authenticate(identifier, false, function(authUrl) | ||
{ | ||
if (!authUrl) | ||
{ | ||
res.writeHead(500); | ||
res.end(error); | ||
} | ||
else | ||
{ | ||
res.writeHead(302, { Location: authUrl }); | ||
res.end(); | ||
} | ||
}); | ||
} | ||
else if(parsedUrl.pathname == '/verify') | ||
{ | ||
// Verify identity assertion | ||
var result = openid.verifyAssertion(req); // or req.url | ||
var attributes = []; | ||
var sreg = new openid.SimpleRegistration(result); | ||
for (var k in sreg) | ||
attributes.push(k + ": " + sreg[k]); | ||
var ax = new openid.AttributeExchange(result); | ||
for (var k in ax) | ||
attributes.push(k + ": " + ax[k]); | ||
res.writeHead(200); | ||
res.end(result.authenticated ? 'Success :)\n' + attributes.join("\n") : 'Failure :(\n' + result.error); | ||
// NOTE: Passing just the URL is also possible | ||
relyingParty.verifyAssertion(req, function(result) | ||
{ | ||
// Result contains properties: | ||
// - authenticated (true/false) | ||
// - error (message, only if not authenticated) | ||
// - answers from any extensions (e.g. | ||
// "http://axschema.org/contact/email" if requested | ||
// and present at provider) | ||
res.writeHead(200); | ||
res.end((result.authenticated ? 'Success :)' : 'Failure :(') + | ||
'\n\n' + JSON.stringify(result)); | ||
}); | ||
} | ||
else if(parsedUrl.pathname == '/authenticate') | ||
{ | ||
// Resolve identifier, associate, build authentication URL | ||
openid.authenticate( | ||
querystring.parse(parsedUrl.query).openid_identifier, // user supplied identifier | ||
'http://example.com/verify', // our callback URL | ||
null, // realm (optional) | ||
false, // attempt immediate authentication first? | ||
function(authUrl, error) | ||
{ | ||
if (error) | ||
{ | ||
res.writeHead(200); | ||
res.end(error); | ||
return; | ||
} | ||
res.writeHead(302, { Location: authUrl }); | ||
res.end(); | ||
}, [new openid.UserInterface(), new openid.SimpleRegistration({ | ||
"nickname" : true, "email" : true, "fullname" : true, | ||
"dob" : true, "gender" : true, "postcode" : true, | ||
"country" : true, "language" : true, "timezone" : true}), | ||
new openid.AttributeExchange({ | ||
"http://axschema.org/contact/email": "required", | ||
"http://axschema.org/namePerson/friendly": "required", | ||
"http://axschema.org/namePerson": "required"})]); | ||
} | ||
else | ||
@@ -79,0 +105,0 @@ { |
@@ -32,7 +32,7 @@ /* OpenID for node.js | ||
{ | ||
openid.authenticate('example.com', 'http://example.com/verify', null, false, | ||
function(data, error) | ||
openid.authenticate('example.com', 'http://example.com/verify', null, false, false, | ||
function(url, provider) | ||
{ | ||
assert.equal(null, data); | ||
assert.ok(error); | ||
assert.equal(null, url); | ||
assert.equal(null, provider); | ||
test.done(); | ||
@@ -135,3 +135,3 @@ }); | ||
openid.authenticate('http://www.google.com/accounts/o8/id', | ||
'http://licensing.ox.no:8080/verify', null, true, function(url) | ||
'http://example.com/verify', null, true, false, function(url) | ||
{ | ||
@@ -143,6 +143,18 @@ assert.ok(url.indexOf('checkid_immediate') !== -1); | ||
exports.testImmediateAuthenticationWithGoogleAppsForDomains = function(test) | ||
{ | ||
// domain must be a valid google apps domain with openid enabled. | ||
openid.authenticate('https://www.google.com/accounts/o8/site-xrds?hd=opower.com', | ||
'http://example.com/verify', null, true, false, function(url) | ||
{ | ||
assert.ok(url.indexOf('checkid_immediate') !== -1); | ||
test.done(); | ||
}); | ||
} | ||
exports.testSetupAuthenticationWithGoogle = function(test) | ||
{ | ||
openid.authenticate('http://www.google.com/accounts/o8/id', | ||
'http://licensing.ox.no:8080/verify', null, false, function(url) | ||
'http://example.com/verify', null, false, false, function(url) | ||
{ | ||
@@ -154,7 +166,41 @@ assert.ok(url.indexOf('checkid_setup') !== -1); | ||
exports.testAuthenticationWithGoogleUsingRelyingPartyObject = function(test) | ||
{ | ||
var rp = new openid.RelyingParty( | ||
'http://example.com/verify', | ||
null, | ||
false, | ||
false, | ||
null); | ||
rp.authenticate('http://www.google.com/accounts/o8/id', false, function(url) | ||
{ | ||
assert.ok(url.indexOf('checkid_setup') !== -1); | ||
test.done(); | ||
}); | ||
} | ||
exports.testVerificationUrl = function(test) | ||
{ | ||
var result = openid.verifyAssertion('http://fu'); | ||
assert.ok(!result.authenticated); | ||
test.done(); | ||
openid.verifyAssertion('http://fu', function(result) | ||
{ | ||
assert.ok(!result.authenticated); | ||
test.done(); | ||
}); | ||
} | ||
exports.testVerificationUrlUsingRelyingParty = function(test) | ||
{ | ||
var rp = new openid.RelyingParty( | ||
'http://example.com/verify', | ||
null, | ||
false, | ||
false, | ||
null); | ||
rp.verifyAssertion('http://fu', function(result) | ||
{ | ||
assert.ok(!result.authenticated); | ||
test.done(); | ||
}); | ||
} | ||
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No contributors or author data
MaintenancePackage does not specify a list of contributors or an author in package.json.
Found 1 instance in 1 package
No bug tracker
MaintenancePackage does not have a linked bug tracker in package.json.
Found 1 instance in 1 package
No website
QualityPackage does not have a website.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Invalid manifest file
QualityPackage has an invalid manifest file and can cause installation problems if you try to use it.
Found 1 instance in 1 package
Misc. License Issues
License(Experimental) A package's licensing information has fine-grained problems.
Found 1 instance in 1 package
97807
12
0
2893
0
111
2
2