stormpath-js
Advanced tools
Comparing version 0.2.0 to 0.4.0
{ | ||
"name": "stormpath.js", | ||
"description": "A browser-ready javascript library for Stormpath", | ||
"version": "0.2.0", | ||
"version": "0.4.0", | ||
"license": "Apache 2.0", | ||
@@ -6,0 +6,0 @@ "main": "dist/stormpath.js", |
@@ -82,4 +82,5 @@ # Contributing | ||
You will be prompted for the type of release (path, minor, major). | ||
Please select the appropriate option and press enter. | ||
After answering, the task will do the following: | ||
The task will then do the following for you: | ||
@@ -94,11 +95,16 @@ * Build the project and place the ouput in the `dist` folder | ||
It will then ask if you would like to commit the files and push them to origin/master with a new tag. | ||
While at this prompt you may use your git tool of choice to reivew the changes that have been made. | ||
At this point you may answer yes to have the files committed and pushed, or answer no to end the task. | ||
If you answer no, it is epexcted that you will manually commit, push and tag the changes. | ||
Before answering "yes", you should use your git tool of choice to review the changes to make sure | ||
that everything looks sane. | ||
The task will not automatically publish this proejct to NPM, you must do that manually. | ||
When you answer "yes", these changes will be committed and pushed to origin. | ||
Bower is updated automatically by the presence of the new tag in the origin. | ||
NPM will not be updated automatically, you must do that manually: | ||
Bower is updated automatically by the tags that were pushed by the release task | ||
````bash | ||
npm publish | ||
```` | ||
Finally, you should push the new version to the Stormpath CDN | ||
If you answer no, it is epexcted that you will manually commit, push and tag the changes. | ||
/* | ||
Stormpath.js v0.2.0 | ||
(c) 2014 Stormpath, Inc. http://stormpath.com | ||
Stormpath.js v0.4.0 | ||
(c) 2014-2015 Stormpath, Inc. http://stormpath.com | ||
License: Apache 2.0 | ||
*/ | ||
!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.Stormpath=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(_dereq_,module,exports){ | ||
var RequestExecutor = _dereq_('./request-executor'); | ||
var utils = _dereq_('./utils'); | ||
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Stormpath = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ | ||
'use strict'; | ||
var deferCallback = require('./defer-callback'); | ||
var IdSiteRequestExecutor = require('./idsite-request-executor'); | ||
var strings = require('./strings'); | ||
var utils = require('./utils'); | ||
var base64 = utils.base64; | ||
function Client(options,readyCallback){ | ||
/** | ||
* Creates a Stormpath.js Client | ||
* | ||
* A client is meant to encapsulate the communication | ||
* with Stormpath's REST API. | ||
* | ||
* @constructor | ||
* @param {object} options configuration options | ||
* @param {function} readyCallback called when the client has | ||
* initialized with its needed data from the REST API. | ||
*/ | ||
function Client (options,readyCallback) { | ||
var opts = typeof options === 'object' ? options : {}; | ||
var cb = typeof options === 'function' ? options : ( readyCallback || utils.noop); | ||
var self = this; | ||
var jwtSegments = null; | ||
self.jwt = opts.token || self._getToken(); | ||
if(!self.jwt){ | ||
setTimeout(function(){cb(new Error('jwt not found as url query parameter'));},1); | ||
return; | ||
self.jwt = opts.token || self.getJwtFromUrl(); | ||
if (!self.jwt) { | ||
return deferCallback(cb,[new Error(strings.errors.JWT_NOT_FOUND)]); | ||
} | ||
try{ | ||
self.jwtPayload = JSON.parse(base64.atob(self.jwt.split('.')[1])); | ||
self.appHref = self.jwtPayload.app_href; | ||
self.sptoken = self.jwtPayload.sp_token || null; | ||
self.baseurl = self.appHref.match('^.+//([^\/]+)\/')[0]; | ||
}catch(e){ | ||
setTimeout(function(){cb(e);},1); | ||
return; | ||
jwtSegments = self.jwt.split('.'); | ||
if (jwtSegments.length < 2 || jwtSegments.length > 3) { | ||
return deferCallback(cb,[new Error(strings.errors.NOT_A_JWT)]); | ||
} | ||
self.requestExecutor = opts.requestExecutor || new RequestExecutor(self.jwt); | ||
try { | ||
self.jwtPayload = JSON.parse(base64.atob(jwtSegments[1])); | ||
} catch (e) { | ||
return deferCallback(cb,[new Error(strings.errors.MALFORMED_JWT_CLAIMS)]); | ||
} | ||
self.appHref = self.jwtPayload.app_href; | ||
self.sptoken = self.jwtPayload.sp_token || null; | ||
self.baseurl = self.appHref.match('^.+//([^\/]+)\/')[0]; | ||
if (self.jwtPayload.onk) { | ||
self.setCachedOrganizationNameKey(self.jwtPayload.onk); | ||
} | ||
var idSiteModelHref = self.appHref; | ||
self.requestExecutor = opts.requestExecutor || new IdSiteRequestExecutor(self.jwt); | ||
self.requestExecutor.execute( | ||
'GET',self.appHref + '?expand=idSiteModel', | ||
function(err,application){ | ||
cb(err, err? null:application.idSiteModel); | ||
{ | ||
method: 'GET', | ||
url: idSiteModelHref + '?expand=idSiteModel', | ||
json: true | ||
}, | ||
function (err,application) { | ||
if (err) { | ||
if (err.status === 401) { | ||
return cb(new Error(strings.errors.INITIAL_JWT_REJECTED)); | ||
} | ||
return cb(err); | ||
} | ||
/* | ||
Assert that the response got a new auth token header. If it did not, | ||
there is likely a proxy or firewall that is stripping it from the | ||
response. | ||
*/ | ||
if (!self.requestExecutor.authToken) { | ||
return cb(new Error(strings.errors.NO_AUTH_TOKEN_HEADER)); | ||
} | ||
cb(null,application.idSiteModel); | ||
} | ||
@@ -39,15 +89,64 @@ ); | ||
Client.prototype._getToken = function() { | ||
return decodeURIComponent( (window.location.href.match(/jwt=(.+)/) || [])[1] || '' ); | ||
/** | ||
* When storing an organization name key for future us, store it in this named | ||
* cookie. | ||
* @type {String} | ||
*/ | ||
Client.prototype.organizationNameKeyCookieKey = 'sp.onk'; | ||
/** | ||
* How long we should store the organization name key cookie for. Default: | ||
* forever. | ||
* @type {String} | ||
*/ | ||
Client.prototype.organizationNameKeyCookieExpiration = 'expires=Fri, 31 Dec 9999 23:59:59 GMT'; | ||
/** | ||
* Pull the cached organization name key from the organization name key cookie | ||
* @return {string} The cached organization name. | ||
*/ | ||
Client.prototype.getCachedOrganizationNameKey = function () { | ||
return decodeURIComponent( | ||
document.cookie.replace( | ||
new RegExp('(?:(?:^|.*;)\\s*' + encodeURIComponent(this.organizationNameKeyCookieKey) | ||
.replace(/[\-\.\+\*]/g, '\\$&') + '\\s*\\=\\s*([^;]*).*$)|^.*$'), | ||
'$1' | ||
) | ||
) || null; | ||
}; | ||
Client.prototype.login = function login(credentials,callback) { | ||
/** | ||
* Store the organization name key in the organization name key cookie | ||
* @param {string} organization name key | ||
*/ | ||
Client.prototype.setCachedOrganizationNameKey = function (nameKey) { | ||
document.cookie = encodeURIComponent(this.organizationNameKeyCookieKey) + | ||
'=' + encodeURIComponent(nameKey) + '; ' + this.organizationNameKeyCookieExpiration; | ||
}; | ||
/** | ||
* Attempts to fetch the JWT from the ?jwt=X location in the window URL. | ||
* Returns an empty string if not found | ||
* @return {string} JWT | ||
*/ | ||
Client.prototype.getJwtFromUrl = function () { | ||
return decodeURIComponent( (window.location.href.match(/jwt=([^&]+)/) || [])[1] || '' ); | ||
}; | ||
/** | ||
* Make a login attempt against the REST API, given the credentials and | ||
* application context. | ||
* | ||
* @param {object} credentials - Example: <pre>{ username: '', password: ''}</pre> | ||
* @param {Function} callback | ||
*/ | ||
Client.prototype.login = function login (credentials,callback) { | ||
var self = this; | ||
var data; | ||
var creds = typeof credentials === 'object' ? credentials : null; | ||
if(!creds){ | ||
if (!creds) { | ||
throw new Error('must provide an object'); | ||
}else if(creds.providerData){ | ||
} else if (creds.providerData) { | ||
data = creds; | ||
}else if(creds.login){ | ||
} else if (creds.login) { | ||
data = { | ||
@@ -57,11 +156,14 @@ type: 'basic', | ||
}; | ||
}else{ | ||
} else { | ||
throw new Error('unsupported credentials object'); | ||
} | ||
if (creds.accountStore) { | ||
data.accountStore = creds.accountStore; | ||
} | ||
self.requestExecutor.execute( | ||
'POST',self.appHref+'/loginAttempts', | ||
{ | ||
body: data, | ||
withCredentials: true | ||
method: 'POST', | ||
url: self.appHref+'/loginAttempts', | ||
json: data | ||
}, | ||
@@ -73,12 +175,37 @@ callback || utils.noop | ||
Client.prototype.register = function register(data,callback) { | ||
if(typeof data!=='object'){ | ||
/** | ||
* Make an account creation attempt against the REST API, given the input data | ||
* and application context. Social login should use this method. | ||
* | ||
* @param {object} data - Example: | ||
* <pre> | ||
* { | ||
* username: '', | ||
* password: '', | ||
* givenName: '', | ||
* surname: '' | ||
* } | ||
* </pre> | ||
* Social Example: | ||
* <pre> | ||
* { | ||
* providerData: { | ||
* providerId: 'google', | ||
* accessToken: '' | ||
* } | ||
* } | ||
* </pre> | ||
* @param {Function} callback - Called back with an error or ID Site Success | ||
* Result | ||
*/ | ||
Client.prototype.register = function register (data,callback) { | ||
if (typeof data!=='object') { | ||
throw new Error('client.register() must be called with a data object'); | ||
} | ||
var self = this; | ||
self.requestExecutor.execute( | ||
'POST',self.appHref+'/accounts', | ||
var client = this; | ||
client.requestExecutor.execute( | ||
{ | ||
body: data, | ||
withCredentials: true | ||
method: 'POST', | ||
url: client.appHref+'/accounts', | ||
json: data | ||
}, | ||
@@ -89,10 +216,20 @@ callback || utils.noop | ||
Client.prototype.verifyEmailToken = function verifyEmailToken(callback) { | ||
if(typeof callback!=='function'){ | ||
/** | ||
* Verify the email verification token that was embedded in the JWT that is in | ||
* the URL. | ||
* | ||
* @param {Function} callback - If no error is given, the token is valid. | ||
*/ | ||
Client.prototype.verifyEmailToken = function verifyEmailToken (callback) { | ||
var client = this; | ||
if (typeof callback!=='function') { | ||
throw new Error('client.verifyEmailToken() takes a function as it\'s only argument'); | ||
} | ||
var self = this; | ||
self.requestExecutor.execute( | ||
'POST', | ||
self.baseurl + '/v1/accounts/emailVerificationTokens/' + self.sptoken, | ||
client.requestExecutor.execute( | ||
{ | ||
method: 'POST', | ||
url: client.baseurl + '/v1/accounts/emailVerificationTokens/' + client.sptoken | ||
}, | ||
callback | ||
@@ -102,10 +239,21 @@ ); | ||
Client.prototype.verifyPasswordResetToken = function verifyPasswordResetToken(callback) { | ||
if(typeof callback!=='function'){ | ||
/** | ||
* Verify the password reset token that was embedded in the JWT that is in the | ||
* URL. | ||
* | ||
* @param {Function} callback - If no error is given, the token is valid. | ||
*/ | ||
Client.prototype.verifyPasswordResetToken = function verifyPasswordResetToken (callback) { | ||
var client = this; | ||
if (typeof callback!=='function') { | ||
throw new Error('client.verifyPasswordResetToken() takes a function as it\'s only argument'); | ||
} | ||
var self = this; | ||
self.requestExecutor.execute( | ||
'GET', | ||
self.appHref + '/passwordResetTokens/' + self.sptoken, | ||
client.requestExecutor.execute( | ||
{ | ||
method: 'GET', | ||
url: client.appHref + '/passwordResetTokens/' + client.sptoken, | ||
json: true | ||
}, | ||
callback | ||
@@ -115,13 +263,33 @@ ); | ||
Client.prototype.setAccountPassword = function setAccountPassword(pwTokenVerification,newPassword,callback) { | ||
if(!pwTokenVerification || !pwTokenVerification.href){ | ||
throw new Error('invalid pwTokenVerification'); | ||
/** | ||
* Given the token resource, set the new password for the user. | ||
* | ||
* @param {object} passwordVerificationTokenResource - | ||
* Example: | ||
* <pre> | ||
* { | ||
* href: 'https://api.stormpath.com/v1/applications/1h72PFWoGxHKhysKjYIkir/passwordResetTokens/3Wog2qMsHyyjD76AWUnnlO' | ||
* } | ||
* </pre> | ||
* @param {Function} callback - If no error is given, the password was reset | ||
* successfully. If an error, the password strength validation error will be | ||
* provided. | ||
*/ | ||
Client.prototype.setAccountPassword = function setAccountPassword (passwordVerificationTokenResource,newPassword,callback) { | ||
var client = this; | ||
if (!passwordVerificationTokenResource || !passwordVerificationTokenResource.href) { | ||
throw new Error('invalid passwordVerificationTokenResource'); | ||
} | ||
if(!newPassword){ | ||
if (!newPassword) { | ||
throw new Error('must supply new password as second argument to client.setAccountPassword()'); | ||
} | ||
var self = this; | ||
self.requestExecutor.execute('POST',pwTokenVerification.href, | ||
client.requestExecutor.execute( | ||
{ | ||
body: { | ||
method: 'POST', | ||
url: passwordVerificationTokenResource.href, | ||
json: { | ||
password: newPassword | ||
@@ -134,12 +302,31 @@ } | ||
Client.prototype.sendPasswordResetEmail = function sendPasswordResetEmail(emailOrUsername,callback) { | ||
if(typeof emailOrUsername!=='string'){ | ||
throw new Error('sendPasswordResetEmail must be called with an email or username as the first argument'); | ||
/** | ||
* Given the email, send that account an email with password reset link. | ||
* | ||
* @param {string} email | ||
* @param {Function} callback - The error will indicate if the account could | ||
* not be found. Otherwise, the email was sent. | ||
*/ | ||
Client.prototype.sendPasswordResetEmail = function sendPasswordResetEmail (emailOrObject,callback) { | ||
var client = this; | ||
var body; | ||
/* | ||
emailOrObject is a backcompat option, in the future this should be a string-only option | ||
*/ | ||
if (typeof emailOrObject==='string') { | ||
body = { email: emailOrObject }; | ||
} else if (typeof emailOrObject === 'object') { | ||
body = emailOrObject; | ||
} else { | ||
throw new Error('sendPasswordResetEmail must be called with an email/username as the first argument, or an options object'); | ||
} | ||
var self = this; | ||
self.requestExecutor.execute( | ||
'POST', | ||
self.appHref + '/passwordResetTokens', | ||
client.requestExecutor.execute( | ||
{ | ||
body: { email: emailOrUsername } | ||
method: 'POST', | ||
url: client.appHref + '/passwordResetTokens', | ||
json: body | ||
}, | ||
@@ -151,167 +338,515 @@ callback || utils.noop | ||
module.exports = Client; | ||
},{"./request-executor":3,"./utils":4}],2:[function(_dereq_,module,exports){ | ||
module.exports = { | ||
Client: _dereq_('./client') | ||
}; | ||
},{"./client":1}],3:[function(_dereq_,module,exports){ | ||
var utils = _dereq_('./utils'); | ||
},{"./defer-callback":2,"./idsite-request-executor":3,"./strings":5,"./utils":6}],2:[function(require,module,exports){ | ||
'use strict'; | ||
function Request(method,url,options,callback){ | ||
var self = this; | ||
self.cb = callback; | ||
self.options = options; | ||
self.xhr = new XMLHttpRequest(); | ||
self.xhr.onreadystatechange = self.onLoad.bind(self); | ||
self.xhr.onerror = self.onerror.bind(self); | ||
self.xhr.open(method,url); | ||
if(options.withCredentials){ | ||
self.xhr.withCredentials = options.withCredentials; | ||
} | ||
self.xhr.setRequestHeader('Authorization', 'Bearer '+self.options.authToken); | ||
self.xhr.setRequestHeader('Content-Type','application/json'); | ||
self.xhr.send(JSON.stringify(options.body)); | ||
/** | ||
* Defers the calling of the callback until the next run of | ||
* the event loop. Do then when the callee is expecting | ||
* the callback to be called after the current event loop | ||
* is done processing (aka Angular's digest scheme) | ||
* | ||
* @function | ||
* @param {function} cb - The callback to call at a later time | ||
* @return {array} - args - The array of arguments to apply to the callback | ||
*/ | ||
function deferCallback (cb,args) { | ||
setTimeout(function () { | ||
cb.apply(null,args); | ||
},0); | ||
} | ||
self.done = false; | ||
return self; | ||
module.exports = deferCallback; | ||
},{}],3:[function(require,module,exports){ | ||
'use strict'; | ||
var xhr = require('xhr'); | ||
var strings = require('./strings.json'); | ||
/** | ||
* Request executor for ID Site, it must be initialized with the JWT that was | ||
* recieved via the jwt parameter that is in the URL when you arrive on ID | ||
* Site from the 302 redirect from the /sso endpoint | ||
* | ||
* @constructor | ||
* @param {string} authToken - a JWT from the sso request | ||
*/ | ||
function IdSiteRequestExecutor (authToken) { | ||
this.authToken = authToken; | ||
} | ||
Request.prototype.onLoad = function onLoad() { | ||
var self = this; | ||
var XHR = XMLHttpRequest; | ||
var s = self.xhr.readyState; | ||
if(s === XHR.DONE && !self.done) { | ||
try{ | ||
var headers = self.responseHeaders = self.getHeadersObject(); | ||
var body = (typeof self.xhr.responseText === 'string' && self.xhr.responseText !== '') ? JSON.parse(self.xhr.responseText) : {}; | ||
var status = self.xhr.status; | ||
var newToken = ( (headers['Authorization'] || '').match(/Bearer (.*)$/i) || [])[1] || ''; | ||
self.cb(status < 400 ? null : body, | ||
newToken, | ||
self, | ||
status < 400 ? body : null | ||
); | ||
self.done = true; | ||
}catch(e){ | ||
self.cb(e); | ||
/** | ||
* Handles a response from the REST API, as executed by the`xhr` library. | ||
* | ||
* It will cache the new auth token for future use, or supply the service | ||
* provider redirect URL (if that header exists) to the callback. | ||
* | ||
* @method | ||
* @param {object} err - Provided by the xhr library callback | ||
* @param {object} response - Provided by the xhr library callback | ||
* @param {object} body - Provided by the xhr library callback | ||
* @param {object} callback - the callback from the calling method in the client | ||
* layer | ||
*/ | ||
IdSiteRequestExecutor.prototype.handleResponse = function handleResponse (err,response,body,requestorCallback) { | ||
var executor = this; | ||
var newToken = response.headers.authorization; | ||
var parsedToken = null; | ||
var serviceProviderCallbackUrl = response.headers['stormpath-sso-redirect-location']; | ||
if (newToken) { | ||
parsedToken = newToken.split('Bearer'); | ||
if (parsedToken.length!==2) { | ||
return requestorCallback(new Error(strings.errors.INVALID_AUTH_TOKEN_HEADER)); | ||
} | ||
executor.authToken = parsedToken[1].trim(); | ||
} else { | ||
executor.authToken = null; | ||
} | ||
/* | ||
Note: The XHR library does not coerce HTTP error codes to err. | ||
Only low-level errors (e.g CORS errors) are passed as err | ||
*/ | ||
if (err) { | ||
return requestorCallback(err); | ||
} | ||
if (response.statusCode>399) { | ||
if (serviceProviderCallbackUrl) { | ||
body.serviceProviderCallbackUrl = serviceProviderCallbackUrl; | ||
} | ||
return requestorCallback(body); | ||
} | ||
if (serviceProviderCallbackUrl) { | ||
return requestorCallback(null, { serviceProviderCallbackUrl: serviceProviderCallbackUrl }); | ||
} | ||
requestorCallback(null,body); | ||
}; | ||
Request.prototype.onerror = function onerror() { | ||
var self = this; | ||
self.done = true; | ||
self.cb(new Error('Unknown XHR Error')); | ||
}; | ||
Request.prototype.getHeadersObject = function getHeadersObject() { | ||
var self = this; | ||
var all = self.xhr.getAllResponseHeaders().trim().split('\n'); | ||
return all.reduce(function(acc,str){ | ||
var x = str.split(': '); | ||
acc[x[0]] = x[1].trim(); | ||
return acc; | ||
},{}); | ||
}; | ||
function RequestExecutor(authToken){ | ||
this.authToken = authToken; | ||
this.terminated = false; | ||
} | ||
/** | ||
* Makes a request of the REST API, given the options for the xhr library. | ||
* The cached auth token is used for authentication with the REST API. | ||
* | ||
* @param {object} xhrRequestOptions - Options for the call to `xhr(options)` | ||
* @param {Function} callback - The callback to call with the result, as decorated | ||
* by the `handleResponse` method in this class | ||
* | ||
*/ | ||
IdSiteRequestExecutor.prototype.execute = function (xhrRequestOptions,callback) { | ||
RequestExecutor.prototype.execute = function(method,url,options,callback) { | ||
var self = this; | ||
if(self.terminated){ | ||
return callback(new Error('Request executor terminated, you must initiate a new flow from the service provider')); | ||
var executor = this; | ||
if (typeof xhrRequestOptions !== 'object') { | ||
throw new Error('Must provide xhrRequestOptions as first parameter'); | ||
} | ||
var opts = typeof options === 'object' ? options : { | ||
body: null | ||
}; | ||
opts.authToken = self.authToken; | ||
var cb = typeof options === 'function' ? options : ( callback || utils.noop); | ||
var req = new Request(method,url,opts,function onDone(err,newToken,request,body){ | ||
self.authToken = newToken; | ||
if(!err && !self.authToken){ | ||
self.terminated = true; | ||
} | ||
if(err){ | ||
return cb(err); | ||
} | ||
var redirectUrl = request.responseHeaders['Stormpath-SSO-Redirect-Location']; | ||
if(redirectUrl){ | ||
body.redirectUrl = redirectUrl; | ||
} | ||
cb(err,body); | ||
if (typeof callback !== 'function') { | ||
throw new Error('Must provide callback as second parameter'); | ||
} | ||
if (!xhrRequestOptions.headers) { | ||
xhrRequestOptions.headers = {}; | ||
} | ||
if (!executor.authToken) { | ||
return callback(new Error(strings.errors.NO_AUTH_TOKEN)); | ||
} | ||
xhrRequestOptions.headers.Authorization = 'Bearer ' + executor.authToken; | ||
return xhr(xhrRequestOptions, function xhrCallback (err,response,body) { | ||
executor.handleResponse(err,response,body,callback); | ||
}); | ||
return req; | ||
}; | ||
module.exports = RequestExecutor; | ||
},{"./utils":4}],4:[function(_dereq_,module,exports){ | ||
module.exports = IdSiteRequestExecutor; | ||
},{"./strings.json":5,"xhr":7}],4:[function(require,module,exports){ | ||
module.exports = { | ||
base64: _dereq_('base64'), | ||
Client: require('./client') | ||
}; | ||
},{"./client":1}],5:[function(require,module,exports){ | ||
module.exports={ | ||
"errors": { | ||
"JWT_NOT_FOUND": "JWT not found as url query parameter.", | ||
"NOT_A_JWT": "JWT does not appear to be a property formatted JWT.", | ||
"MALFORMED_JWT_CLAIMS": "The JWT claims section is malfomed and could not be decoded as JSON.", | ||
"NO_AUTH_TOKEN_HEADER": "HTTP response does not contain Authorization header.", | ||
"INVALID_AUTH_TOKEN_HEADER": "HTTP response has an invalid Authorization header.", | ||
"INITIAL_JWT_REJECTED": "The JWT used to initialized the client was rejected." | ||
} | ||
} | ||
},{}],6:[function(require,module,exports){ | ||
'use strict'; | ||
/** | ||
* @function | ||
* From: https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding#Solution_.232_.E2.80.93_rewriting_atob()_and_btoa()_using_TypedArrays_and_UTF-8 | ||
*/ | ||
function b64EncodeUnicode(str) { | ||
return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function(match, p1) { | ||
return String.fromCharCode('0x' + p1); | ||
})); | ||
} | ||
module.exports = { | ||
base64: { | ||
atob: function atob(str){ | ||
return decodeURIComponent(window.atob(str)); | ||
}, | ||
btoa: function btoa(str){ | ||
var v = b64EncodeUnicode(str); | ||
return v; | ||
} | ||
}, | ||
noop: function(){} | ||
}; | ||
},{"base64":5}],5:[function(_dereq_,module,exports){ | ||
;(function () { | ||
},{}],7:[function(require,module,exports){ | ||
"use strict"; | ||
var window = require("global/window") | ||
var once = require("once") | ||
var parseHeaders = require("parse-headers") | ||
var object = typeof exports != 'undefined' ? exports : this; // #8: web workers | ||
var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; | ||
function InvalidCharacterError(message) { | ||
this.message = message; | ||
} | ||
InvalidCharacterError.prototype = new Error; | ||
InvalidCharacterError.prototype.name = 'InvalidCharacterError'; | ||
// encoder | ||
// [https://gist.github.com/999166] by [https://github.com/nignag] | ||
object.btoa || ( | ||
object.btoa = function (input) { | ||
var str = String(input); | ||
for ( | ||
// initialize result and counter | ||
var block, charCode, idx = 0, map = chars, output = ''; | ||
// if the next str index does not exist: | ||
// change the mapping table to "=" | ||
// check if d has no fractional digits | ||
str.charAt(idx | 0) || (map = '=', idx % 1); | ||
// "8 - idx % 1 * 8" generates the sequence 2, 4, 6, 8 | ||
output += map.charAt(63 & block >> 8 - idx % 1 * 8) | ||
) { | ||
charCode = str.charCodeAt(idx += 3/4); | ||
if (charCode > 0xFF) { | ||
throw new InvalidCharacterError("'btoa' failed: The string to be encoded contains characters outside of the Latin1 range."); | ||
} | ||
block = block << 8 | charCode; | ||
module.exports = createXHR | ||
createXHR.XMLHttpRequest = window.XMLHttpRequest || noop | ||
createXHR.XDomainRequest = "withCredentials" in (new createXHR.XMLHttpRequest()) ? createXHR.XMLHttpRequest : window.XDomainRequest | ||
function isEmpty(obj){ | ||
for(var i in obj){ | ||
if(obj.hasOwnProperty(i)) return false | ||
} | ||
return output; | ||
}); | ||
return true | ||
} | ||
// decoder | ||
// [https://gist.github.com/1020396] by [https://github.com/atk] | ||
object.atob || ( | ||
object.atob = function (input) { | ||
var str = String(input).replace(/=+$/, ''); | ||
if (str.length % 4 == 1) { | ||
throw new InvalidCharacterError("'atob' failed: The string to be decoded is not correctly encoded."); | ||
function createXHR(options, callback) { | ||
function readystatechange() { | ||
if (xhr.readyState === 4) { | ||
loadFunc() | ||
} | ||
} | ||
for ( | ||
// initialize result and counters | ||
var bc = 0, bs, buffer, idx = 0, output = ''; | ||
// get next character | ||
buffer = str.charAt(idx++); | ||
// character found in table? initialize bit storage and add its ascii value; | ||
~buffer && (bs = bc % 4 ? bs * 64 + buffer : buffer, | ||
// and if not first of each 4 characters, | ||
// convert the first 8 bits to one ascii character | ||
bc++ % 4) ? output += String.fromCharCode(255 & bs >> (-2 * bc & 6)) : 0 | ||
function getBody() { | ||
// Chrome with requestType=blob throws errors arround when even testing access to responseText | ||
var body = undefined | ||
if (xhr.response) { | ||
body = xhr.response | ||
} else if (xhr.responseType === "text" || !xhr.responseType) { | ||
body = xhr.responseText || xhr.responseXML | ||
} | ||
if (isJson) { | ||
try { | ||
body = JSON.parse(body) | ||
} catch (e) {} | ||
} | ||
return body | ||
} | ||
var failureResponse = { | ||
body: undefined, | ||
headers: {}, | ||
statusCode: 0, | ||
method: method, | ||
url: uri, | ||
rawRequest: xhr | ||
} | ||
function errorFunc(evt) { | ||
clearTimeout(timeoutTimer) | ||
if(!(evt instanceof Error)){ | ||
evt = new Error("" + (evt || "Unknown XMLHttpRequest Error") ) | ||
} | ||
evt.statusCode = 0 | ||
callback(evt, failureResponse) | ||
} | ||
// will load the data & process the response in a special response object | ||
function loadFunc() { | ||
if (aborted) return | ||
var status | ||
clearTimeout(timeoutTimer) | ||
if(options.useXDR && xhr.status===undefined) { | ||
//IE8 CORS GET successful response doesn't have a status field, but body is fine | ||
status = 200 | ||
} else { | ||
status = (xhr.status === 1223 ? 204 : xhr.status) | ||
} | ||
var response = failureResponse | ||
var err = null | ||
if (status !== 0){ | ||
response = { | ||
body: getBody(), | ||
statusCode: status, | ||
method: method, | ||
headers: {}, | ||
url: uri, | ||
rawRequest: xhr | ||
} | ||
if(xhr.getAllResponseHeaders){ //remember xhr can in fact be XDR for CORS in IE | ||
response.headers = parseHeaders(xhr.getAllResponseHeaders()) | ||
} | ||
} else { | ||
err = new Error("Internal XMLHttpRequest Error") | ||
} | ||
callback(err, response, response.body) | ||
} | ||
if (typeof options === "string") { | ||
options = { uri: options } | ||
} | ||
options = options || {} | ||
if(typeof callback === "undefined"){ | ||
throw new Error("callback argument missing") | ||
} | ||
callback = once(callback) | ||
var xhr = options.xhr || null | ||
if (!xhr) { | ||
if (options.cors || options.useXDR) { | ||
xhr = new createXHR.XDomainRequest() | ||
}else{ | ||
xhr = new createXHR.XMLHttpRequest() | ||
} | ||
} | ||
var key | ||
var aborted | ||
var uri = xhr.url = options.uri || options.url | ||
var method = xhr.method = options.method || "GET" | ||
var body = options.body || options.data | ||
var headers = xhr.headers = options.headers || {} | ||
var sync = !!options.sync | ||
var isJson = false | ||
var timeoutTimer | ||
if ("json" in options) { | ||
isJson = true | ||
headers["accept"] || headers["Accept"] || (headers["Accept"] = "application/json") //Don't override existing accept header declared by user | ||
if (method !== "GET" && method !== "HEAD") { | ||
headers["content-type"] || headers["Content-Type"] || (headers["Content-Type"] = "application/json") //Don't override existing accept header declared by user | ||
body = JSON.stringify(options.json) | ||
} | ||
} | ||
xhr.onreadystatechange = readystatechange | ||
xhr.onload = loadFunc | ||
xhr.onerror = errorFunc | ||
// IE9 must have onprogress be set to a unique function. | ||
xhr.onprogress = function () { | ||
// IE must die | ||
} | ||
xhr.ontimeout = errorFunc | ||
xhr.open(method, uri, !sync, options.username, options.password) | ||
//has to be after open | ||
if(!sync) { | ||
xhr.withCredentials = !!options.withCredentials | ||
} | ||
// Cannot set timeout with sync request | ||
// not setting timeout on the xhr object, because of old webkits etc. not handling that correctly | ||
// both npm's request and jquery 1.x use this kind of timeout, so this is being consistent | ||
if (!sync && options.timeout > 0 ) { | ||
timeoutTimer = setTimeout(function(){ | ||
aborted=true//IE9 may still call readystatechange | ||
xhr.abort("timeout") | ||
var e = new Error("XMLHttpRequest timeout") | ||
e.code = "ETIMEDOUT" | ||
errorFunc(e) | ||
}, options.timeout ) | ||
} | ||
if (xhr.setRequestHeader) { | ||
for(key in headers){ | ||
if(headers.hasOwnProperty(key)){ | ||
xhr.setRequestHeader(key, headers[key]) | ||
} | ||
} | ||
} else if (options.headers && !isEmpty(options.headers)) { | ||
throw new Error("Headers cannot be set on an XDomainRequest object") | ||
} | ||
if ("responseType" in options) { | ||
xhr.responseType = options.responseType | ||
} | ||
if ("beforeSend" in options && | ||
typeof options.beforeSend === "function" | ||
) { | ||
// try to find character in table (0-63, not found => -1) | ||
buffer = chars.indexOf(buffer); | ||
options.beforeSend(xhr) | ||
} | ||
return output; | ||
}); | ||
}()); | ||
xhr.send(body) | ||
},{}]},{},[2]) | ||
(2) | ||
return xhr | ||
} | ||
function noop() {} | ||
},{"global/window":8,"once":9,"parse-headers":13}],8:[function(require,module,exports){ | ||
(function (global){ | ||
if (typeof window !== "undefined") { | ||
module.exports = window; | ||
} else if (typeof global !== "undefined") { | ||
module.exports = global; | ||
} else if (typeof self !== "undefined"){ | ||
module.exports = self; | ||
} else { | ||
module.exports = {}; | ||
} | ||
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) | ||
},{}],9:[function(require,module,exports){ | ||
module.exports = once | ||
once.proto = once(function () { | ||
Object.defineProperty(Function.prototype, 'once', { | ||
value: function () { | ||
return once(this) | ||
}, | ||
configurable: true | ||
}) | ||
}) | ||
function once (fn) { | ||
var called = false | ||
return function () { | ||
if (called) return | ||
called = true | ||
return fn.apply(this, arguments) | ||
} | ||
} | ||
},{}],10:[function(require,module,exports){ | ||
var isFunction = require('is-function') | ||
module.exports = forEach | ||
var toString = Object.prototype.toString | ||
var hasOwnProperty = Object.prototype.hasOwnProperty | ||
function forEach(list, iterator, context) { | ||
if (!isFunction(iterator)) { | ||
throw new TypeError('iterator must be a function') | ||
} | ||
if (arguments.length < 3) { | ||
context = this | ||
} | ||
if (toString.call(list) === '[object Array]') | ||
forEachArray(list, iterator, context) | ||
else if (typeof list === 'string') | ||
forEachString(list, iterator, context) | ||
else | ||
forEachObject(list, iterator, context) | ||
} | ||
function forEachArray(array, iterator, context) { | ||
for (var i = 0, len = array.length; i < len; i++) { | ||
if (hasOwnProperty.call(array, i)) { | ||
iterator.call(context, array[i], i, array) | ||
} | ||
} | ||
} | ||
function forEachString(string, iterator, context) { | ||
for (var i = 0, len = string.length; i < len; i++) { | ||
// no such thing as a sparse string. | ||
iterator.call(context, string.charAt(i), i, string) | ||
} | ||
} | ||
function forEachObject(object, iterator, context) { | ||
for (var k in object) { | ||
if (hasOwnProperty.call(object, k)) { | ||
iterator.call(context, object[k], k, object) | ||
} | ||
} | ||
} | ||
},{"is-function":11}],11:[function(require,module,exports){ | ||
module.exports = isFunction | ||
var toString = Object.prototype.toString | ||
function isFunction (fn) { | ||
var string = toString.call(fn) | ||
return string === '[object Function]' || | ||
(typeof fn === 'function' && string !== '[object RegExp]') || | ||
(typeof window !== 'undefined' && | ||
// IE8 and below | ||
(fn === window.setTimeout || | ||
fn === window.alert || | ||
fn === window.confirm || | ||
fn === window.prompt)) | ||
}; | ||
},{}],12:[function(require,module,exports){ | ||
exports = module.exports = trim; | ||
function trim(str){ | ||
return str.replace(/^\s*|\s*$/g, ''); | ||
} | ||
exports.left = function(str){ | ||
return str.replace(/^\s*/, ''); | ||
}; | ||
exports.right = function(str){ | ||
return str.replace(/\s*$/, ''); | ||
}; | ||
},{}],13:[function(require,module,exports){ | ||
var trim = require('trim') | ||
, forEach = require('for-each') | ||
, isArray = function(arg) { | ||
return Object.prototype.toString.call(arg) === '[object Array]'; | ||
} | ||
module.exports = function (headers) { | ||
if (!headers) | ||
return {} | ||
var result = {} | ||
forEach( | ||
trim(headers).split('\n') | ||
, function (row) { | ||
var index = row.indexOf(':') | ||
, key = trim(row.slice(0, index)).toLowerCase() | ||
, value = trim(row.slice(index + 1)) | ||
if (typeof(result[key]) === 'undefined') { | ||
result[key] = value | ||
} else if (isArray(result[key])) { | ||
result[key].push(value) | ||
} else { | ||
result[key] = [ result[key], value ] | ||
} | ||
} | ||
) | ||
return result | ||
} | ||
},{"for-each":10,"trim":12}]},{},[4])(4) | ||
}); |
/* | ||
Stormpath.js v0.2.0 | ||
(c) 2014 Stormpath, Inc. http://stormpath.com | ||
Stormpath.js v0.4.0 | ||
(c) 2014-2015 Stormpath, Inc. http://stormpath.com | ||
License: Apache 2.0 | ||
*/ | ||
!function(a){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=a();else if("function"==typeof define&&define.amd)define([],a);else{var b;"undefined"!=typeof window?b=window:"undefined"!=typeof global?b=global:"undefined"!=typeof self&&(b=self),b.Stormpath=a()}}(function(){return function a(b,c,d){function e(g,h){if(!c[g]){if(!b[g]){var i="function"==typeof require&&require;if(!h&&i)return i(g,!0);if(f)return f(g,!0);throw new Error("Cannot find module '"+g+"'")}var j=c[g]={exports:{}};b[g][0].call(j.exports,function(a){var c=b[g][1][a];return e(c?c:a)},j,j.exports,a,b,c,d)}return c[g].exports}for(var f="function"==typeof require&&require,g=0;g<d.length;g++)e(d[g]);return e}({1:[function(a,b){function c(a,b){var c="object"==typeof a?a:{},g="function"==typeof a?a:b||e.noop,h=this;if(h.jwt=c.token||h._getToken(),!h.jwt)return void setTimeout(function(){g(new Error("jwt not found as url query parameter"))},1);try{h.jwtPayload=JSON.parse(f.atob(h.jwt.split(".")[1])),h.appHref=h.jwtPayload.app_href,h.sptoken=h.jwtPayload.sp_token||null,h.baseurl=h.appHref.match("^.+//([^/]+)/")[0]}catch(i){return void setTimeout(function(){g(i)},1)}h.requestExecutor=c.requestExecutor||new d(h.jwt),h.requestExecutor.execute("GET",h.appHref+"?expand=idSiteModel",function(a,b){g(a,a?null:b.idSiteModel)})}var d=a("./request-executor"),e=a("./utils"),f=e.base64;c.prototype._getToken=function(){return decodeURIComponent((window.location.href.match(/jwt=(.+)/)||[])[1]||"")},c.prototype.login=function(a,b){var c,d=this,f="object"==typeof a?a:null;if(!f)throw new Error("must provide an object");if(f.providerData)c=f;else{if(!f.login)throw new Error("unsupported credentials object");c={type:"basic",value:e.base64.btoa(f.login+":"+f.password)}}d.requestExecutor.execute("POST",d.appHref+"/loginAttempts",{body:c,withCredentials:!0},b||e.noop)},c.prototype.register=function(a,b){if("object"!=typeof a)throw new Error("client.register() must be called with a data object");var c=this;c.requestExecutor.execute("POST",c.appHref+"/accounts",{body:a,withCredentials:!0},b||e.noop)},c.prototype.verifyEmailToken=function(a){if("function"!=typeof a)throw new Error("client.verifyEmailToken() takes a function as it's only argument");var b=this;b.requestExecutor.execute("POST",b.baseurl+"/v1/accounts/emailVerificationTokens/"+b.sptoken,a)},c.prototype.verifyPasswordResetToken=function(a){if("function"!=typeof a)throw new Error("client.verifyPasswordResetToken() takes a function as it's only argument");var b=this;b.requestExecutor.execute("GET",b.appHref+"/passwordResetTokens/"+b.sptoken,a)},c.prototype.setAccountPassword=function(a,b,c){if(!a||!a.href)throw new Error("invalid pwTokenVerification");if(!b)throw new Error("must supply new password as second argument to client.setAccountPassword()");var d=this;d.requestExecutor.execute("POST",a.href,{body:{password:b}},c||e.noop)},c.prototype.sendPasswordResetEmail=function(a,b){if("string"!=typeof a)throw new Error("sendPasswordResetEmail must be called with an email or username as the first argument");var c=this;c.requestExecutor.execute("POST",c.appHref+"/passwordResetTokens",{body:{email:a}},b||e.noop)},b.exports=c},{"./request-executor":3,"./utils":4}],2:[function(a,b){b.exports={Client:a("./client")}},{"./client":1}],3:[function(a,b){function c(a,b,c,d){var e=this;return e.cb=d,e.options=c,e.xhr=new XMLHttpRequest,e.xhr.onreadystatechange=e.onLoad.bind(e),e.xhr.onerror=e.onerror.bind(e),e.xhr.open(a,b),c.withCredentials&&(e.xhr.withCredentials=c.withCredentials),e.xhr.setRequestHeader("Authorization","Bearer "+e.options.authToken),e.xhr.setRequestHeader("Content-Type","application/json"),e.xhr.send(JSON.stringify(c.body)),e.done=!1,e}function d(a){this.authToken=a,this.terminated=!1}var e=a("./utils");c.prototype.onLoad=function(){var a=this,b=XMLHttpRequest,c=a.xhr.readyState;if(c===b.DONE&&!a.done)try{var d=a.responseHeaders=a.getHeadersObject(),e="string"==typeof a.xhr.responseText&&""!==a.xhr.responseText?JSON.parse(a.xhr.responseText):{},f=a.xhr.status,g=((d.Authorization||"").match(/Bearer (.*)$/i)||[])[1]||"";a.cb(400>f?null:e,g,a,400>f?e:null),a.done=!0}catch(h){a.cb(h)}},c.prototype.onerror=function(){var a=this;a.done=!0,a.cb(new Error("Unknown XHR Error"))},c.prototype.getHeadersObject=function(){var a=this,b=a.xhr.getAllResponseHeaders().trim().split("\n");return b.reduce(function(a,b){var c=b.split(": ");return a[c[0]]=c[1].trim(),a},{})},d.prototype.execute=function(a,b,d,f){var g=this;if(g.terminated)return f(new Error("Request executor terminated, you must initiate a new flow from the service provider"));var h="object"==typeof d?d:{body:null};h.authToken=g.authToken;var i="function"==typeof d?d:f||e.noop,j=new c(a,b,h,function(a,b,c,d){if(g.authToken=b,a||g.authToken||(g.terminated=!0),a)return i(a);var e=c.responseHeaders["Stormpath-SSO-Redirect-Location"];e&&(d.redirectUrl=e),i(a,d)});return j},b.exports=d},{"./utils":4}],4:[function(a,b){b.exports={base64:a("base64"),noop:function(){}}},{base64:5}],5:[function(a,b,c){!function(){function a(a){this.message=a}var b="undefined"!=typeof c?c:this,d="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";a.prototype=new Error,a.prototype.name="InvalidCharacterError",b.btoa||(b.btoa=function(b){for(var c,e,f=String(b),g=0,h=d,i="";f.charAt(0|g)||(h="=",g%1);i+=h.charAt(63&c>>8-g%1*8)){if(e=f.charCodeAt(g+=.75),e>255)throw new a("'btoa' failed: The string to be encoded contains characters outside of the Latin1 range.");c=c<<8|e}return i}),b.atob||(b.atob=function(b){var c=String(b).replace(/=+$/,"");if(c.length%4==1)throw new a("'atob' failed: The string to be decoded is not correctly encoded.");for(var e,f,g=0,h=0,i="";f=c.charAt(h++);~f&&(e=g%4?64*e+f:f,g++%4)?i+=String.fromCharCode(255&e>>(-2*g&6)):0)f=d.indexOf(f);return i})}()},{}]},{},[2])(2)}); | ||
!function(a){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=a();else if("function"==typeof define&&define.amd)define([],a);else{var b;b="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,b.Stormpath=a()}}(function(){return function a(b,c,d){function e(g,h){if(!c[g]){if(!b[g]){var i="function"==typeof require&&require;if(!h&&i)return i(g,!0);if(f)return f(g,!0);var j=new Error("Cannot find module '"+g+"'");throw j.code="MODULE_NOT_FOUND",j}var k=c[g]={exports:{}};b[g][0].call(k.exports,function(a){var c=b[g][1][a];return e(c?c:a)},k,k.exports,a,b,c,d)}return c[g].exports}for(var f="function"==typeof require&&require,g=0;g<d.length;g++)e(d[g]);return e}({1:[function(a,b,c){"use strict";function d(a,b){var c="object"==typeof a?a:{},d="function"==typeof a?a:b||h.noop,j=this,k=null;if(j.jwt=c.token||j.getJwtFromUrl(),!j.jwt)return e(d,[new Error(g.errors.JWT_NOT_FOUND)]);if(k=j.jwt.split("."),k.length<2||k.length>3)return e(d,[new Error(g.errors.NOT_A_JWT)]);try{j.jwtPayload=JSON.parse(i.atob(k[1]))}catch(l){return e(d,[new Error(g.errors.MALFORMED_JWT_CLAIMS)])}j.appHref=j.jwtPayload.app_href,j.sptoken=j.jwtPayload.sp_token||null,j.baseurl=j.appHref.match("^.+//([^/]+)/")[0],j.jwtPayload.onk&&j.setCachedOrganizationNameKey(j.jwtPayload.onk);var m=j.appHref;j.requestExecutor=c.requestExecutor||new f(j.jwt),j.requestExecutor.execute({method:"GET",url:m+"?expand=idSiteModel",json:!0},function(a,b){return a?d(401===a.status?new Error(g.errors.INITIAL_JWT_REJECTED):a):j.requestExecutor.authToken?void d(null,b.idSiteModel):d(new Error(g.errors.NO_AUTH_TOKEN_HEADER))})}var e=a("./defer-callback"),f=a("./idsite-request-executor"),g=a("./strings"),h=a("./utils"),i=h.base64;d.prototype.organizationNameKeyCookieKey="sp.onk",d.prototype.organizationNameKeyCookieExpiration="expires=Fri, 31 Dec 9999 23:59:59 GMT",d.prototype.getCachedOrganizationNameKey=function(){return decodeURIComponent(document.cookie.replace(new RegExp("(?:(?:^|.*;)\\s*"+encodeURIComponent(this.organizationNameKeyCookieKey).replace(/[\-\.\+\*]/g,"\\$&")+"\\s*\\=\\s*([^;]*).*$)|^.*$"),"$1"))||null},d.prototype.setCachedOrganizationNameKey=function(a){document.cookie=encodeURIComponent(this.organizationNameKeyCookieKey)+"="+encodeURIComponent(a)+"; "+this.organizationNameKeyCookieExpiration},d.prototype.getJwtFromUrl=function(){return decodeURIComponent((window.location.href.match(/jwt=([^&]+)/)||[])[1]||"")},d.prototype.login=function(a,b){var c,d=this,e="object"==typeof a?a:null;if(!e)throw new Error("must provide an object");if(e.providerData)c=e;else{if(!e.login)throw new Error("unsupported credentials object");c={type:"basic",value:h.base64.btoa(e.login+":"+e.password)}}e.accountStore&&(c.accountStore=e.accountStore),d.requestExecutor.execute({method:"POST",url:d.appHref+"/loginAttempts",json:c},b||h.noop)},d.prototype.register=function(a,b){if("object"!=typeof a)throw new Error("client.register() must be called with a data object");var c=this;c.requestExecutor.execute({method:"POST",url:c.appHref+"/accounts",json:a},b||h.noop)},d.prototype.verifyEmailToken=function(a){var b=this;if("function"!=typeof a)throw new Error("client.verifyEmailToken() takes a function as it's only argument");b.requestExecutor.execute({method:"POST",url:b.baseurl+"/v1/accounts/emailVerificationTokens/"+b.sptoken},a)},d.prototype.verifyPasswordResetToken=function(a){var b=this;if("function"!=typeof a)throw new Error("client.verifyPasswordResetToken() takes a function as it's only argument");b.requestExecutor.execute({method:"GET",url:b.appHref+"/passwordResetTokens/"+b.sptoken,json:!0},a)},d.prototype.setAccountPassword=function(a,b,c){var d=this;if(!a||!a.href)throw new Error("invalid passwordVerificationTokenResource");if(!b)throw new Error("must supply new password as second argument to client.setAccountPassword()");d.requestExecutor.execute({method:"POST",url:a.href,json:{password:b}},c||h.noop)},d.prototype.sendPasswordResetEmail=function(a,b){var c,d=this;if("string"==typeof a)c={email:a};else{if("object"!=typeof a)throw new Error("sendPasswordResetEmail must be called with an email/username as the first argument, or an options object");c=a}d.requestExecutor.execute({method:"POST",url:d.appHref+"/passwordResetTokens",json:c},b||h.noop)},b.exports=d},{"./defer-callback":2,"./idsite-request-executor":3,"./strings":5,"./utils":6}],2:[function(a,b,c){"use strict";function d(a,b){setTimeout(function(){a.apply(null,b)},0)}b.exports=d},{}],3:[function(a,b,c){"use strict";function d(a){this.authToken=a}var e=a("xhr"),f=a("./strings.json");d.prototype.handleResponse=function(a,b,c,d){var e=this,g=b.headers.authorization,h=null,i=b.headers["stormpath-sso-redirect-location"];if(g){if(h=g.split("Bearer"),2!==h.length)return d(new Error(f.errors.INVALID_AUTH_TOKEN_HEADER));e.authToken=h[1].trim()}else e.authToken=null;return a?d(a):b.statusCode>399?(i&&(c.serviceProviderCallbackUrl=i),d(c)):i?d(null,{serviceProviderCallbackUrl:i}):void d(null,c)},d.prototype.execute=function(a,b){var c=this;if("object"!=typeof a)throw new Error("Must provide xhrRequestOptions as first parameter");if("function"!=typeof b)throw new Error("Must provide callback as second parameter");return a.headers||(a.headers={}),c.authToken?(a.headers.Authorization="Bearer "+c.authToken,e(a,function(a,d,e){c.handleResponse(a,d,e,b)})):b(new Error(f.errors.NO_AUTH_TOKEN))},b.exports=d},{"./strings.json":5,xhr:7}],4:[function(a,b,c){b.exports={Client:a("./client")}},{"./client":1}],5:[function(a,b,c){b.exports={errors:{JWT_NOT_FOUND:"JWT not found as url query parameter.",NOT_A_JWT:"JWT does not appear to be a property formatted JWT.",MALFORMED_JWT_CLAIMS:"The JWT claims section is malfomed and could not be decoded as JSON.",NO_AUTH_TOKEN_HEADER:"HTTP response does not contain Authorization header.",INVALID_AUTH_TOKEN_HEADER:"HTTP response has an invalid Authorization header.",INITIAL_JWT_REJECTED:"The JWT used to initialized the client was rejected."}}},{}],6:[function(a,b,c){"use strict";function d(a){return btoa(encodeURIComponent(a).replace(/%([0-9A-F]{2})/g,function(a,b){return String.fromCharCode("0x"+b)}))}b.exports={base64:{atob:function(a){return decodeURIComponent(window.atob(a))},btoa:function(a){var b=d(a);return b}},noop:function(){}}},{}],7:[function(a,b,c){"use strict";function d(a){for(var b in a)if(a.hasOwnProperty(b))return!1;return!0}function e(a,b){function c(){4===l.readyState&&j()}function f(){var a=void 0;if(l.response?a=l.response:"text"!==l.responseType&&l.responseType||(a=l.responseText||l.responseXML),u)try{a=JSON.parse(a)}catch(b){}return a}function g(a){clearTimeout(o),a instanceof Error||(a=new Error(""+(a||"Unknown XMLHttpRequest Error"))),a.statusCode=0,b(a,k)}function j(){if(!n){var c;clearTimeout(o),c=a.useXDR&&void 0===l.status?200:1223===l.status?204:l.status;var d=k,e=null;0!==c?(d={body:f(),statusCode:c,method:q,headers:{},url:p,rawRequest:l},l.getAllResponseHeaders&&(d.headers=i(l.getAllResponseHeaders()))):e=new Error("Internal XMLHttpRequest Error"),b(e,d,d.body)}}var k={body:void 0,headers:{},statusCode:0,method:q,url:p,rawRequest:l};if("string"==typeof a&&(a={uri:a}),a=a||{},"undefined"==typeof b)throw new Error("callback argument missing");b=h(b);var l=a.xhr||null;l||(l=a.cors||a.useXDR?new e.XDomainRequest:new e.XMLHttpRequest);var m,n,o,p=l.url=a.uri||a.url,q=l.method=a.method||"GET",r=a.body||a.data,s=l.headers=a.headers||{},t=!!a.sync,u=!1;if("json"in a&&(u=!0,s.accept||s.Accept||(s.Accept="application/json"),"GET"!==q&&"HEAD"!==q&&(s["content-type"]||s["Content-Type"]||(s["Content-Type"]="application/json"),r=JSON.stringify(a.json))),l.onreadystatechange=c,l.onload=j,l.onerror=g,l.onprogress=function(){},l.ontimeout=g,l.open(q,p,!t,a.username,a.password),t||(l.withCredentials=!!a.withCredentials),!t&&a.timeout>0&&(o=setTimeout(function(){n=!0,l.abort("timeout");var a=new Error("XMLHttpRequest timeout");a.code="ETIMEDOUT",g(a)},a.timeout)),l.setRequestHeader)for(m in s)s.hasOwnProperty(m)&&l.setRequestHeader(m,s[m]);else if(a.headers&&!d(a.headers))throw new Error("Headers cannot be set on an XDomainRequest object");return"responseType"in a&&(l.responseType=a.responseType),"beforeSend"in a&&"function"==typeof a.beforeSend&&a.beforeSend(l),l.send(r),l}function f(){}var g=a("global/window"),h=a("once"),i=a("parse-headers");b.exports=e,e.XMLHttpRequest=g.XMLHttpRequest||f,e.XDomainRequest="withCredentials"in new e.XMLHttpRequest?e.XMLHttpRequest:g.XDomainRequest},{"global/window":8,once:9,"parse-headers":13}],8:[function(a,b,c){(function(a){"undefined"!=typeof window?b.exports=window:"undefined"!=typeof a?b.exports=a:"undefined"!=typeof self?b.exports=self:b.exports={}}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}],9:[function(a,b,c){function d(a){var b=!1;return function(){return b?void 0:(b=!0,a.apply(this,arguments))}}b.exports=d,d.proto=d(function(){Object.defineProperty(Function.prototype,"once",{value:function(){return d(this)},configurable:!0})})},{}],10:[function(a,b,c){function d(a,b,c){if(!h(b))throw new TypeError("iterator must be a function");arguments.length<3&&(c=this),"[object Array]"===i.call(a)?e(a,b,c):"string"==typeof a?f(a,b,c):g(a,b,c)}function e(a,b,c){for(var d=0,e=a.length;e>d;d++)j.call(a,d)&&b.call(c,a[d],d,a)}function f(a,b,c){for(var d=0,e=a.length;e>d;d++)b.call(c,a.charAt(d),d,a)}function g(a,b,c){for(var d in a)j.call(a,d)&&b.call(c,a[d],d,a)}var h=a("is-function");b.exports=d;var i=Object.prototype.toString,j=Object.prototype.hasOwnProperty},{"is-function":11}],11:[function(a,b,c){function d(a){var b=e.call(a);return"[object Function]"===b||"function"==typeof a&&"[object RegExp]"!==b||"undefined"!=typeof window&&(a===window.setTimeout||a===window.alert||a===window.confirm||a===window.prompt)}b.exports=d;var e=Object.prototype.toString},{}],12:[function(a,b,c){function d(a){return a.replace(/^\s*|\s*$/g,"")}c=b.exports=d,c.left=function(a){return a.replace(/^\s*/,"")},c.right=function(a){return a.replace(/\s*$/,"")}},{}],13:[function(a,b,c){var d=a("trim"),e=a("for-each"),f=function(a){return"[object Array]"===Object.prototype.toString.call(a)};b.exports=function(a){if(!a)return{};var b={};return e(d(a).split("\n"),function(a){var c=a.indexOf(":"),e=d(a.slice(0,c)).toLowerCase(),g=d(a.slice(c+1));"undefined"==typeof b[e]?b[e]=g:f(b[e])?b[e].push(g):b[e]=[b[e],g]}),b}},{"for-each":10,trim:12}]},{},[4])(4)}); |
@@ -9,2 +9,3 @@ 'use strict'; | ||
pkg: grunt.file.readJSON('package.json'), | ||
year: new Date().getFullYear(), | ||
bump: { | ||
@@ -29,3 +30,3 @@ options:{ | ||
' Stormpath.js v<%= pkg.version %>\n' + | ||
' (c) 2014 Stormpath, Inc. http://stormpath.com\n'+ | ||
' (c) 2014-<%= year %> Stormpath, Inc. http://stormpath.com\n'+ | ||
' License: Apache 2.0\n' + | ||
@@ -55,4 +56,4 @@ '*/\n' | ||
port: 8085 | ||
}, | ||
}, | ||
} | ||
} | ||
}, | ||
@@ -64,3 +65,3 @@ browserify: { | ||
options: { | ||
bundleOptions: { | ||
browserifyOptions: { | ||
standalone: 'Stormpath' | ||
@@ -67,0 +68,0 @@ } |
@@ -46,6 +46,6 @@ // Karma configuration | ||
browserify: { | ||
watch: true | ||
watch: true, | ||
debug: true | ||
}, | ||
preprocessors: { | ||
@@ -52,0 +52,0 @@ 'test/it/*.js': ['browserify'], |
@@ -1,29 +0,79 @@ | ||
var RequestExecutor = require('./request-executor'); | ||
'use strict'; | ||
var deferCallback = require('./defer-callback'); | ||
var IdSiteRequestExecutor = require('./idsite-request-executor'); | ||
var strings = require('./strings'); | ||
var utils = require('./utils'); | ||
var base64 = utils.base64; | ||
function Client(options,readyCallback){ | ||
/** | ||
* Creates a Stormpath.js Client | ||
* | ||
* A client is meant to encapsulate the communication | ||
* with Stormpath's REST API. | ||
* | ||
* @constructor | ||
* @param {object} options configuration options | ||
* @param {function} readyCallback called when the client has | ||
* initialized with its needed data from the REST API. | ||
*/ | ||
function Client (options,readyCallback) { | ||
var opts = typeof options === 'object' ? options : {}; | ||
var cb = typeof options === 'function' ? options : ( readyCallback || utils.noop); | ||
var self = this; | ||
var jwtSegments = null; | ||
self.jwt = opts.token || self._getToken(); | ||
if(!self.jwt){ | ||
setTimeout(function(){cb(new Error('jwt not found as url query parameter'));},1); | ||
return; | ||
self.jwt = opts.token || self.getJwtFromUrl(); | ||
if (!self.jwt) { | ||
return deferCallback(cb,[new Error(strings.errors.JWT_NOT_FOUND)]); | ||
} | ||
try{ | ||
self.jwtPayload = JSON.parse(base64.atob(self.jwt.split('.')[1])); | ||
self.appHref = self.jwtPayload.app_href; | ||
self.sptoken = self.jwtPayload.sp_token || null; | ||
self.baseurl = self.appHref.match('^.+//([^\/]+)\/')[0]; | ||
}catch(e){ | ||
setTimeout(function(){cb(e);},1); | ||
return; | ||
jwtSegments = self.jwt.split('.'); | ||
if (jwtSegments.length < 2 || jwtSegments.length > 3) { | ||
return deferCallback(cb,[new Error(strings.errors.NOT_A_JWT)]); | ||
} | ||
self.requestExecutor = opts.requestExecutor || new RequestExecutor(self.jwt); | ||
try { | ||
self.jwtPayload = JSON.parse(base64.atob(jwtSegments[1])); | ||
} catch (e) { | ||
return deferCallback(cb,[new Error(strings.errors.MALFORMED_JWT_CLAIMS)]); | ||
} | ||
self.appHref = self.jwtPayload.app_href; | ||
self.sptoken = self.jwtPayload.sp_token || null; | ||
self.baseurl = self.appHref.match('^.+//([^\/]+)\/')[0]; | ||
if (self.jwtPayload.onk) { | ||
self.setCachedOrganizationNameKey(self.jwtPayload.onk); | ||
} | ||
var idSiteModelHref = self.appHref; | ||
self.requestExecutor = opts.requestExecutor || new IdSiteRequestExecutor(self.jwt); | ||
self.requestExecutor.execute( | ||
'GET',self.appHref + '?expand=idSiteModel', | ||
function(err,application){ | ||
cb(err, err? null:application.idSiteModel); | ||
{ | ||
method: 'GET', | ||
url: idSiteModelHref + '?expand=idSiteModel', | ||
json: true | ||
}, | ||
function (err,application) { | ||
if (err) { | ||
if (err.status === 401) { | ||
return cb(new Error(strings.errors.INITIAL_JWT_REJECTED)); | ||
} | ||
return cb(err); | ||
} | ||
/* | ||
Assert that the response got a new auth token header. If it did not, | ||
there is likely a proxy or firewall that is stripping it from the | ||
response. | ||
*/ | ||
if (!self.requestExecutor.authToken) { | ||
return cb(new Error(strings.errors.NO_AUTH_TOKEN_HEADER)); | ||
} | ||
cb(null,application.idSiteModel); | ||
} | ||
@@ -33,15 +83,64 @@ ); | ||
Client.prototype._getToken = function() { | ||
return decodeURIComponent( (window.location.href.match(/jwt=(.+)/) || [])[1] || '' ); | ||
/** | ||
* When storing an organization name key for future us, store it in this named | ||
* cookie. | ||
* @type {String} | ||
*/ | ||
Client.prototype.organizationNameKeyCookieKey = 'sp.onk'; | ||
/** | ||
* How long we should store the organization name key cookie for. Default: | ||
* forever. | ||
* @type {String} | ||
*/ | ||
Client.prototype.organizationNameKeyCookieExpiration = 'expires=Fri, 31 Dec 9999 23:59:59 GMT'; | ||
/** | ||
* Pull the cached organization name key from the organization name key cookie | ||
* @return {string} The cached organization name. | ||
*/ | ||
Client.prototype.getCachedOrganizationNameKey = function () { | ||
return decodeURIComponent( | ||
document.cookie.replace( | ||
new RegExp('(?:(?:^|.*;)\\s*' + encodeURIComponent(this.organizationNameKeyCookieKey) | ||
.replace(/[\-\.\+\*]/g, '\\$&') + '\\s*\\=\\s*([^;]*).*$)|^.*$'), | ||
'$1' | ||
) | ||
) || null; | ||
}; | ||
Client.prototype.login = function login(credentials,callback) { | ||
/** | ||
* Store the organization name key in the organization name key cookie | ||
* @param {string} organization name key | ||
*/ | ||
Client.prototype.setCachedOrganizationNameKey = function (nameKey) { | ||
document.cookie = encodeURIComponent(this.organizationNameKeyCookieKey) + | ||
'=' + encodeURIComponent(nameKey) + '; ' + this.organizationNameKeyCookieExpiration; | ||
}; | ||
/** | ||
* Attempts to fetch the JWT from the ?jwt=X location in the window URL. | ||
* Returns an empty string if not found | ||
* @return {string} JWT | ||
*/ | ||
Client.prototype.getJwtFromUrl = function () { | ||
return decodeURIComponent( (window.location.href.match(/jwt=([^&]+)/) || [])[1] || '' ); | ||
}; | ||
/** | ||
* Make a login attempt against the REST API, given the credentials and | ||
* application context. | ||
* | ||
* @param {object} credentials - Example: <pre>{ username: '', password: ''}</pre> | ||
* @param {Function} callback | ||
*/ | ||
Client.prototype.login = function login (credentials,callback) { | ||
var self = this; | ||
var data; | ||
var creds = typeof credentials === 'object' ? credentials : null; | ||
if(!creds){ | ||
if (!creds) { | ||
throw new Error('must provide an object'); | ||
}else if(creds.providerData){ | ||
} else if (creds.providerData) { | ||
data = creds; | ||
}else if(creds.login){ | ||
} else if (creds.login) { | ||
data = { | ||
@@ -51,11 +150,14 @@ type: 'basic', | ||
}; | ||
}else{ | ||
} else { | ||
throw new Error('unsupported credentials object'); | ||
} | ||
if (creds.accountStore) { | ||
data.accountStore = creds.accountStore; | ||
} | ||
self.requestExecutor.execute( | ||
'POST',self.appHref+'/loginAttempts', | ||
{ | ||
body: data, | ||
withCredentials: true | ||
method: 'POST', | ||
url: self.appHref+'/loginAttempts', | ||
json: data | ||
}, | ||
@@ -67,12 +169,37 @@ callback || utils.noop | ||
Client.prototype.register = function register(data,callback) { | ||
if(typeof data!=='object'){ | ||
/** | ||
* Make an account creation attempt against the REST API, given the input data | ||
* and application context. Social login should use this method. | ||
* | ||
* @param {object} data - Example: | ||
* <pre> | ||
* { | ||
* username: '', | ||
* password: '', | ||
* givenName: '', | ||
* surname: '' | ||
* } | ||
* </pre> | ||
* Social Example: | ||
* <pre> | ||
* { | ||
* providerData: { | ||
* providerId: 'google', | ||
* accessToken: '' | ||
* } | ||
* } | ||
* </pre> | ||
* @param {Function} callback - Called back with an error or ID Site Success | ||
* Result | ||
*/ | ||
Client.prototype.register = function register (data,callback) { | ||
if (typeof data!=='object') { | ||
throw new Error('client.register() must be called with a data object'); | ||
} | ||
var self = this; | ||
self.requestExecutor.execute( | ||
'POST',self.appHref+'/accounts', | ||
var client = this; | ||
client.requestExecutor.execute( | ||
{ | ||
body: data, | ||
withCredentials: true | ||
method: 'POST', | ||
url: client.appHref+'/accounts', | ||
json: data | ||
}, | ||
@@ -83,10 +210,20 @@ callback || utils.noop | ||
Client.prototype.verifyEmailToken = function verifyEmailToken(callback) { | ||
if(typeof callback!=='function'){ | ||
/** | ||
* Verify the email verification token that was embedded in the JWT that is in | ||
* the URL. | ||
* | ||
* @param {Function} callback - If no error is given, the token is valid. | ||
*/ | ||
Client.prototype.verifyEmailToken = function verifyEmailToken (callback) { | ||
var client = this; | ||
if (typeof callback!=='function') { | ||
throw new Error('client.verifyEmailToken() takes a function as it\'s only argument'); | ||
} | ||
var self = this; | ||
self.requestExecutor.execute( | ||
'POST', | ||
self.baseurl + '/v1/accounts/emailVerificationTokens/' + self.sptoken, | ||
client.requestExecutor.execute( | ||
{ | ||
method: 'POST', | ||
url: client.baseurl + '/v1/accounts/emailVerificationTokens/' + client.sptoken | ||
}, | ||
callback | ||
@@ -96,10 +233,21 @@ ); | ||
Client.prototype.verifyPasswordResetToken = function verifyPasswordResetToken(callback) { | ||
if(typeof callback!=='function'){ | ||
/** | ||
* Verify the password reset token that was embedded in the JWT that is in the | ||
* URL. | ||
* | ||
* @param {Function} callback - If no error is given, the token is valid. | ||
*/ | ||
Client.prototype.verifyPasswordResetToken = function verifyPasswordResetToken (callback) { | ||
var client = this; | ||
if (typeof callback!=='function') { | ||
throw new Error('client.verifyPasswordResetToken() takes a function as it\'s only argument'); | ||
} | ||
var self = this; | ||
self.requestExecutor.execute( | ||
'GET', | ||
self.appHref + '/passwordResetTokens/' + self.sptoken, | ||
client.requestExecutor.execute( | ||
{ | ||
method: 'GET', | ||
url: client.appHref + '/passwordResetTokens/' + client.sptoken, | ||
json: true | ||
}, | ||
callback | ||
@@ -109,13 +257,33 @@ ); | ||
Client.prototype.setAccountPassword = function setAccountPassword(pwTokenVerification,newPassword,callback) { | ||
if(!pwTokenVerification || !pwTokenVerification.href){ | ||
throw new Error('invalid pwTokenVerification'); | ||
/** | ||
* Given the token resource, set the new password for the user. | ||
* | ||
* @param {object} passwordVerificationTokenResource - | ||
* Example: | ||
* <pre> | ||
* { | ||
* href: 'https://api.stormpath.com/v1/applications/1h72PFWoGxHKhysKjYIkir/passwordResetTokens/3Wog2qMsHyyjD76AWUnnlO' | ||
* } | ||
* </pre> | ||
* @param {Function} callback - If no error is given, the password was reset | ||
* successfully. If an error, the password strength validation error will be | ||
* provided. | ||
*/ | ||
Client.prototype.setAccountPassword = function setAccountPassword (passwordVerificationTokenResource,newPassword,callback) { | ||
var client = this; | ||
if (!passwordVerificationTokenResource || !passwordVerificationTokenResource.href) { | ||
throw new Error('invalid passwordVerificationTokenResource'); | ||
} | ||
if(!newPassword){ | ||
if (!newPassword) { | ||
throw new Error('must supply new password as second argument to client.setAccountPassword()'); | ||
} | ||
var self = this; | ||
self.requestExecutor.execute('POST',pwTokenVerification.href, | ||
client.requestExecutor.execute( | ||
{ | ||
body: { | ||
method: 'POST', | ||
url: passwordVerificationTokenResource.href, | ||
json: { | ||
password: newPassword | ||
@@ -128,12 +296,31 @@ } | ||
Client.prototype.sendPasswordResetEmail = function sendPasswordResetEmail(emailOrUsername,callback) { | ||
if(typeof emailOrUsername!=='string'){ | ||
throw new Error('sendPasswordResetEmail must be called with an email or username as the first argument'); | ||
/** | ||
* Given the email, send that account an email with password reset link. | ||
* | ||
* @param {string} email | ||
* @param {Function} callback - The error will indicate if the account could | ||
* not be found. Otherwise, the email was sent. | ||
*/ | ||
Client.prototype.sendPasswordResetEmail = function sendPasswordResetEmail (emailOrObject,callback) { | ||
var client = this; | ||
var body; | ||
/* | ||
emailOrObject is a backcompat option, in the future this should be a string-only option | ||
*/ | ||
if (typeof emailOrObject==='string') { | ||
body = { email: emailOrObject }; | ||
} else if (typeof emailOrObject === 'object') { | ||
body = emailOrObject; | ||
} else { | ||
throw new Error('sendPasswordResetEmail must be called with an email/username as the first argument, or an options object'); | ||
} | ||
var self = this; | ||
self.requestExecutor.execute( | ||
'POST', | ||
self.appHref + '/passwordResetTokens', | ||
client.requestExecutor.execute( | ||
{ | ||
body: { email: emailOrUsername } | ||
method: 'POST', | ||
url: client.appHref + '/passwordResetTokens', | ||
json: body | ||
}, | ||
@@ -140,0 +327,0 @@ callback || utils.noop |
@@ -0,4 +1,25 @@ | ||
'use strict'; | ||
/** | ||
* @function | ||
* From: https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding#Solution_.232_.E2.80.93_rewriting_atob()_and_btoa()_using_TypedArrays_and_UTF-8 | ||
*/ | ||
function b64EncodeUnicode(str) { | ||
return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function(match, p1) { | ||
return String.fromCharCode('0x' + p1); | ||
})); | ||
} | ||
module.exports = { | ||
base64: require('base64'), | ||
base64: { | ||
atob: function atob(str){ | ||
return decodeURIComponent(window.atob(str)); | ||
}, | ||
btoa: function btoa(str){ | ||
var v = b64EncodeUnicode(str); | ||
return v; | ||
} | ||
}, | ||
noop: function(){} | ||
}; |
{ | ||
"name": "stormpath-js", | ||
"version": "0.2.0", | ||
"version": "0.4.0", | ||
"description": "A browser-ready javascript library for Stormpath. Use this library if you are building your own ID Site from scratch. Additional features may be added in the future.", | ||
@@ -20,29 +20,31 @@ "main": "lib/index.js", | ||
"license": "Apache-2.0", | ||
"keywords": ["stormpath"], | ||
"keywords": [ | ||
"stormpath" | ||
], | ||
"devDependencies": { | ||
"browserify": "^4.2.0", | ||
"browserify": "^11.2.0", | ||
"chai": "^1.9.1", | ||
"grunt": "^0.4.5", | ||
"grunt-browserify": "^2.1.2", | ||
"grunt-browserify": "^4.0.1", | ||
"grunt-bump": "0.0.14", | ||
"grunt-contrib-clean": "^0.5.0", | ||
"grunt-contrib-concat": "^0.4.0", | ||
"grunt-contrib-connect": "^0.8.0", | ||
"grunt-contrib-uglify": "^0.5.0", | ||
"karma": "^0.12.16", | ||
"grunt-karma": "^0.8.3", | ||
"grunt-contrib-watch": "^0.6.1", | ||
"grunt-istanbul": "^0.6.1", | ||
"grunt-karma": "^0.12.1", | ||
"grunt-prompt": "^1.1.0", | ||
"karma": "^0.13.11", | ||
"karma-browserify": "^4.4.0", | ||
"karma-chai": "^0.1.0", | ||
"karma-chrome-launcher": "^0.2.1", | ||
"karma-coverage": "^0.5.3", | ||
"karma-mocha": "^0.2.0", | ||
"load-grunt-tasks": "^0.6.0", | ||
"mocha": "^1.20.1", | ||
"karma-mocha": "^0.1.4", | ||
"karma-chrome-launcher": "^0.1.4", | ||
"karma-browserify": "^0.2.1", | ||
"chai": "^1.9.1", | ||
"karma-chai": "^0.1.0", | ||
"karma-coverage": "^0.2.4", | ||
"grunt-istanbul": "^0.3.0", | ||
"grunt-contrib-clean": "^0.5.0", | ||
"grunt-contrib-connect": "^0.8.0", | ||
"grunt-contrib-concat": "^0.4.0", | ||
"grunt-bump": "0.0.14", | ||
"grunt-prompt": "^1.1.0" | ||
"mocha": "^2.3.3" | ||
}, | ||
"dependencies": { | ||
"Base64": "^0.3.0" | ||
"xhr": "^2.1.0" | ||
} | ||
} |
@@ -1,5 +0,6 @@ | ||
# Stormpath.js - BETA | ||
# Stormpath.js | ||
A browser-ready javascript library for use with Stormpath features. Use this library if you are building your own ID Site from scratch. | ||
Additional features may be added in the future. | ||
A browser-ready javascript library for use with Stormpath features. Use this | ||
library if you are building your own ID Site from scratch. Additional features | ||
may be added in the future. | ||
@@ -9,5 +10,5 @@ | ||
In order to use Stormpath.js, your application must be part of a Service-Provider initiated flow. | ||
The client assumes this is true and searches the browser's URL for the secure token | ||
which is needed to initialize the client. | ||
In order to use Stormpath.js, your application must be part of a Service- | ||
Provider initiated flow. The client assumes this is true and searches the | ||
browser's URL for the secure token which is needed to initialize the client. | ||
@@ -18,4 +19,5 @@ For more information please read [Using Stormpath's ID Site to Host your User Management UI](http://docs.stormpath.com/guides/using-id-site) | ||
You may clone this repo and use the `stormpath.min.js` or `stormpath.js` files from the `dist/` folder | ||
by including them in your application with a script tag: | ||
You may clone this repo and use the `stormpath.min.js` or `stormpath.js` files | ||
from the `dist/` folder by including them in your application with a script | ||
tag: | ||
@@ -32,3 +34,3 @@ ````html | ||
You may also install this module via NPM and require it in your [Browerified](http://browserify.org) application: | ||
You may also install this module via NPM and require it in your [Browserify](http://browserify.org) application: | ||
@@ -39,8 +41,6 @@ ````bash | ||
In the near future we will provide this library through our CDN. | ||
### Initialization | ||
To initialize a Stormpath Client which will be used for all API communication, simply create a new instance and pass a callback function: | ||
To initialize a Stormpath Client which will be used for all API communication, | ||
simply create a new instance and pass a callback function: | ||
@@ -64,5 +64,10 @@ ````javascript | ||
* `passwordPolicy`, an object, which provides the password strength policies for the account store which new accounts will be created in. | ||
If null, new accounts are not permitted for the target account store or the store is a social provider store. | ||
* `providers`, which provides your social provider configuration as an array of objects, ordered by account store prioritization. | ||
* `passwordPolicy`, an object, which provides the password strength policies for | ||
the account store which new accounts will be created in. If null, new accounts | ||
are not permitted for the target account store or the store is a social | ||
provider store. | ||
* `providers`, which provides your social provider configuration as an array of | ||
objects, ordered by account store prioritization. | ||
* `logoUrl`, the URL to the logo image | ||
@@ -109,3 +114,6 @@ | ||
login: usernameOrEmail, | ||
password: submittedPassword | ||
password: submittedPassword, | ||
accountStore: { // optional | ||
href: 'optional account store href for login attempt' | ||
} | ||
}, | ||
@@ -116,4 +124,4 @@ function loginCallback(err,result){ | ||
}else{ | ||
// login was successful, send the user to the redirectUrl | ||
window.location.replace(result.redirectUrl); | ||
// login was successful, send the user to the serviceProviderCallbackUrl | ||
window.location.replace(result.serviceProviderCallbackUrl); | ||
} | ||
@@ -124,3 +132,3 @@ } | ||
### Login/register a user (Google or Facebook) | ||
### Social Login (Google or Facebook) | ||
@@ -142,4 +150,4 @@ Use the Facebook or Google Javascript Library to prompt the user for login, then pass | ||
}else{ | ||
// login was successful, send the user to the redirectUrl | ||
window.location.replace(result.redirectUrl); | ||
// login was successful, send the user to the serviceProviderCallbackUrl | ||
window.location.replace(result.serviceProviderCallbackUrl); | ||
} | ||
@@ -164,7 +172,7 @@ } | ||
function registerCallback(err,result){ | ||
if(result.redirectUrl){ | ||
// You will be given the redirectUrl if the email verification workflow is | ||
if(result.serviceProviderCallbackUrl){ | ||
// You will be given the serviceProviderCallbackUrl if the email verification workflow is | ||
// NOT enabled for this account store, in which case the user can now | ||
// continue to the redirectUrl | ||
window.location.replace(result.redirectUrl); | ||
// continue to the serviceProviderCallbackUrl | ||
window.location.replace(result.serviceProviderCallbackUrl); | ||
}else{ | ||
@@ -201,3 +209,9 @@ // tell the user to check their email for a verification link | ||
````javascript | ||
client.sendPasswordResetEmail(email,function(err){ | ||
var options = { | ||
email: 'email or username', | ||
accountStore: { // optional | ||
href: 'optional account store href for login attempt' | ||
} | ||
} | ||
client.sendPasswordResetEmail(options,function(err){ | ||
if(err){ | ||
@@ -253,2 +267,22 @@ // email is invalid, show err.message to user | ||
### 0.4.1 | ||
**Not Yet Released** | ||
### 0.4.0 | ||
**Released on October 23rd, 2015** | ||
* Refactoring the internal request executor to supply contextual error messages | ||
* Fixing https://github.com/stormpath/idsite-src/issues/2 by not sending cookies | ||
on requests to the API | ||
### 0.3.1 | ||
Fixing the base64 encoding strategy, unicode characters are now supported. | ||
### 0.3.0 | ||
Adding support for Organizations | ||
### 0.2.0 | ||
@@ -255,0 +289,0 @@ |
@@ -19,3 +19,3 @@ 'use strict'; | ||
}); | ||
it('should call the callback an idSiteModel',function(){ | ||
it('should call the callback with an idSiteModel',function(){ | ||
assert.equal(result[1].href,validToken.decoded.app_href+'/idSiteModel'); | ||
@@ -28,3 +28,3 @@ }); | ||
before(function(done){ | ||
client.login({login:'bad',password:'bad'},function(err,value){ | ||
new stormpathJs.Client({token:validToken.encoded}).login({login:'bad',password:'bad'},function(err,value){ | ||
result = [err,value]; | ||
@@ -42,3 +42,3 @@ done(); | ||
before(function(done){ | ||
client.login({login:'good',password:'good'},function(err,value){ | ||
new stormpathJs.Client({token:validToken.encoded}).login({login:'good',password:'good'},function(err,value){ | ||
result = [err,value]; | ||
@@ -49,3 +49,3 @@ done(); | ||
it('should give a redirect url',function(){ | ||
assert.equal(result[1].redirectUrl,'the-place-to-go'); | ||
assert.equal(result[1].serviceProviderCallbackUrl,'the-place-to-go'); | ||
}); | ||
@@ -81,3 +81,3 @@ }); | ||
it('should give a redirect url',function(){ | ||
assert.equal(result[1].redirectUrl,'the-place-to-go'); | ||
assert.equal(result[1].serviceProviderCallbackUrl,'the-place-to-go'); | ||
}); | ||
@@ -84,0 +84,0 @@ }); |
'use strict'; | ||
var stormpathJs = require('../common').stormpath; | ||
var stormpathJs = require('../../lib'); | ||
@@ -58,3 +58,3 @@ describe('Request Executor', function () { | ||
it('should not have a new token',function(){ | ||
assert.equal(client.requestExecutor.authToken,''); | ||
assert.equal(client.requestExecutor.authToken,null); | ||
}); | ||
@@ -61,0 +61,0 @@ }); |
'use strict'; | ||
var stormpathJs = require('../common').stormpath; | ||
var strings = require('../../lib/strings'); | ||
@@ -18,7 +19,7 @@ describe('Client', function () { | ||
it('should err',function(){ | ||
assert.instanceOf(result[0],Error); | ||
assert.equal(result[0].message,strings.errors.JWT_NOT_FOUND); | ||
}); | ||
}); | ||
describe('with an invalid JWT', function () { | ||
describe('with a invalid JWT', function () { | ||
var result; | ||
@@ -32,7 +33,7 @@ before(function(done){ | ||
it('should err',function(){ | ||
assert.instanceOf(result[0],Error); | ||
assert.equal(result[0].message,strings.errors.NOT_A_JWT); | ||
}); | ||
}); | ||
describe('with an valid JWT', function () { | ||
describe('with a valid JWT', function () { | ||
var requestedAppHref, result; | ||
@@ -47,4 +48,4 @@ var token = require('../data/valid-jwt.json'); | ||
requestExecutor: { | ||
execute: function(m,u,cb){ | ||
requestedAppHref = u; | ||
execute: function(xhrRequestOptions,cb){ | ||
requestedAppHref = xhrRequestOptions.url; | ||
cb(null,{idSiteModel:'abcd1234'}); | ||
@@ -63,5 +64,2 @@ done(); | ||
}); | ||
it('should call the callback with the idSiteModel value',function(){ | ||
assert.equal(result[1],'abcd1234'); | ||
}); | ||
@@ -81,4 +79,4 @@ }); | ||
requestExecutor: { | ||
execute: function(m,u,o,cb){ | ||
if(u.match(/idSiteModel/)){ | ||
execute: function(xhrRequestOptions,cb){ | ||
if(xhrRequestOptions.url.match(/idSiteModel/)){ | ||
// the first call for the site model | ||
@@ -88,3 +86,3 @@ done(); | ||
// the calls to login attempts | ||
calledWith.push(o); | ||
calledWith.push(xhrRequestOptions); | ||
cb(); | ||
@@ -122,3 +120,3 @@ } | ||
it('should post that data to the api',function(){ | ||
assert.deepEqual(calledWith[0].body,input); | ||
assert.deepEqual(calledWith[0].json,input); | ||
}); | ||
@@ -140,5 +138,44 @@ }); | ||
it('should post base64 encode the data and post it to the api',function(){ | ||
assert.deepEqual(calledWith[1].body,{type:'basic',value:data.encoded}); | ||
assert.deepEqual(calledWith[1].json,{type:'basic',value:data.encoded}); | ||
}); | ||
}); | ||
describe('if called with unicode characters', function(){ | ||
var result; | ||
var data = require('../data/unicode-password.json'); | ||
var input = { | ||
login: data.login, | ||
password: data.password | ||
}; | ||
before(function(done){ | ||
client.login(input,function(err){ | ||
result = [err]; | ||
done(); | ||
}); | ||
}); | ||
it('should post the correct base64 encoded string to the API',function(){ | ||
assert.deepEqual(calledWith[2].json,{type:'basic',value:data.encoded}); | ||
}); | ||
}); | ||
describe('if called with an account store',function(){ | ||
var result; | ||
var data = require('../data/basic-login.json'); | ||
var input = { | ||
login: data.login, | ||
password: data.password, | ||
accountStore:{ | ||
href: 'abc' | ||
} | ||
}; | ||
before(function(done){ | ||
client.login(input,function(err){ | ||
result = [err]; | ||
done(); | ||
}); | ||
}); | ||
it('should pass the account store to the api',function(){ | ||
assert.equal(calledWith[3].json.accountStore.href,input.accountStore.href); | ||
}); | ||
}); | ||
}); | ||
@@ -155,4 +192,4 @@ | ||
requestExecutor: { | ||
execute: function(m,u,o,cb){ | ||
if(u.match(/idSiteModel/)){ | ||
execute: function(xhrRequestOptions,cb){ | ||
if(xhrRequestOptions.url.match(/idSiteModel/)){ | ||
// the first call for the site model | ||
@@ -162,3 +199,3 @@ done(); | ||
// the calls to register | ||
calledWith.push([m,u,o]); | ||
calledWith.push([xhrRequestOptions]); | ||
cb(); | ||
@@ -186,5 +223,6 @@ } | ||
it('should post that data to the api',function(){ | ||
assert.deepEqual(calledWith[0][0],'POST'); | ||
expect(calledWith[0][1]).to.have.string('/accounts'); | ||
assert.deepEqual(calledWith[0][2].body,data); | ||
var xhrInvocation = calledWith[0][0]; | ||
assert.deepEqual(xhrInvocation.method,'POST'); | ||
expect(xhrInvocation.url).to.have.string('/accounts'); | ||
assert.deepEqual(xhrInvocation.json,data); | ||
}); | ||
@@ -205,4 +243,4 @@ }); | ||
requestExecutor: { | ||
execute: function(m,u,cb){ | ||
if(u.match(/idSiteModel/)){ | ||
execute: function(xhrRequestOptions,cb){ | ||
if(xhrRequestOptions.url.match(/idSiteModel/)){ | ||
// the first call for the site model | ||
@@ -212,3 +250,3 @@ done(); | ||
// the calls to verifyEmailToken | ||
calledWith.push([m,u]); | ||
calledWith.push([xhrRequestOptions]); | ||
cb(); | ||
@@ -235,5 +273,6 @@ } | ||
it('should post that data to the api',function(){ | ||
assert.deepEqual(calledWith[0][0],'POST'); | ||
expect(calledWith[0][1]).to.have.string('/v1/accounts/emailVerificationTokens/' + token.decoded.sp_token); | ||
assert.equal(calledWith[0][2],null); | ||
var xhrInvocation = calledWith[0][0]; | ||
assert.deepEqual(xhrInvocation.method,'POST'); | ||
expect(xhrInvocation.url).to.have.string('/v1/accounts/emailVerificationTokens/' + token.decoded.sp_token); | ||
assert.equal(xhrInvocation.json,null); | ||
}); | ||
@@ -254,4 +293,4 @@ }); | ||
requestExecutor: { | ||
execute: function(m,u,cb){ | ||
if(u.match(/idSiteModel/)){ | ||
execute: function(xhrRequestOptions,cb){ | ||
if(xhrRequestOptions.url.match(/idSiteModel/)){ | ||
// the first call for the site model | ||
@@ -261,3 +300,3 @@ done(); | ||
// the calls to verifyPasswordResetToken | ||
calledWith.push([m,u]); | ||
calledWith.push([xhrRequestOptions]); | ||
cb(); | ||
@@ -284,5 +323,6 @@ } | ||
it('should post that data to the api',function(){ | ||
assert.deepEqual(calledWith[0][0],'GET'); | ||
expect(calledWith[0][1]).to.have.string('passwordResetTokens/' + token.decoded.sp_token); | ||
assert.equal(calledWith[0][2],null); | ||
var xhrInvocation = calledWith[0][0]; | ||
assert.deepEqual(xhrInvocation.method,'GET'); | ||
expect(xhrInvocation.url).to.have.string('passwordResetTokens/' + token.decoded.sp_token); | ||
assert.equal(xhrInvocation.json,true); | ||
}); | ||
@@ -304,4 +344,4 @@ }); | ||
requestExecutor: { | ||
execute: function(m,u,o,cb){ | ||
if(u.match(/idSiteModel/)){ | ||
execute: function(xhrRequestOptions,cb){ | ||
if(xhrRequestOptions.url.match(/idSiteModel/)){ | ||
// the first call for the site model | ||
@@ -311,3 +351,3 @@ done(); | ||
// the calls to setAccountPassword | ||
calledWith.push([m,u,o]); | ||
calledWith.push([xhrRequestOptions]); | ||
cb(); | ||
@@ -352,5 +392,6 @@ } | ||
it('should post that data to the api',function(){ | ||
assert.deepEqual(calledWith[0][0],'POST'); | ||
assert.equal(calledWith[0][1],pwTokenVerification.href); | ||
assert.deepEqual(calledWith[0][2],{body:{password:newPassword}}); | ||
var xhrInvocation = calledWith[0][0]; | ||
assert.deepEqual(xhrInvocation.method,'POST'); | ||
assert.equal(xhrInvocation.url,pwTokenVerification.href); | ||
assert.deepEqual(xhrInvocation.json,{password:newPassword}); | ||
}); | ||
@@ -372,4 +413,4 @@ }); | ||
requestExecutor: { | ||
execute: function(m,u,o,cb){ | ||
if(u.match(/idSiteModel/)){ | ||
execute: function(xhrRequestOptions,cb){ | ||
if(xhrRequestOptions.url.match(/idSiteModel/)){ | ||
// the first call for the site model | ||
@@ -379,3 +420,3 @@ done(); | ||
// the calls to sendPasswordResetEmail | ||
calledWith.push([m,u,o]); | ||
calledWith.push([xhrRequestOptions]); | ||
cb(); | ||
@@ -395,3 +436,3 @@ } | ||
describe('if called with correct arugments',function(){ | ||
describe('if called with an email',function(){ | ||
var emailOrUsername = 'reset@met.com'; | ||
@@ -403,11 +444,32 @@ before(function(done){ | ||
}); | ||
it('should post that data to the api',function(){ | ||
assert.deepEqual(calledWith[0][0],'POST'); | ||
assert.equal(calledWith[0][1],token.decoded.app_href + '/passwordResetTokens'); | ||
assert.deepEqual(calledWith[0][2],{body:{email:emailOrUsername}}); | ||
it('should post that email',function(){ | ||
var xhrInvocation = calledWith[0][0]; | ||
assert.deepEqual(xhrInvocation.method,'POST'); | ||
assert.equal(xhrInvocation.url,token.decoded.app_href + '/passwordResetTokens'); | ||
assert.deepEqual(xhrInvocation.json,{ email:emailOrUsername }); | ||
}); | ||
}); | ||
describe('if called with an object',function(){ | ||
var data = { | ||
email: 'reset@met.com', | ||
accountStore: { | ||
href: 'anHref' + Math.random() | ||
} | ||
}; | ||
before(function(done){ | ||
client.sendPasswordResetEmail(data,function(){ | ||
done(); | ||
}); | ||
}); | ||
it('should post that object',function(){ | ||
var xhrInvocation = calledWith[1][0]; | ||
assert.deepEqual(xhrInvocation.method,'POST'); | ||
assert.equal(xhrInvocation.url,token.decoded.app_href + '/passwordResetTokens'); | ||
assert.deepEqual(xhrInvocation.json,data); | ||
}); | ||
}); | ||
}); | ||
}); |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
1176141
68
2630
289
1
+ Addedxhr@^2.1.0
+ Addeddom-walk@0.1.2(transitive)
+ Addedglobal@4.4.0(transitive)
+ Addedis-function@1.0.2(transitive)
+ Addedmin-document@2.19.0(transitive)
+ Addedparse-headers@2.0.5(transitive)
+ Addedprocess@0.11.10(transitive)
+ Addedxhr@2.6.0(transitive)
+ Addedxtend@4.0.2(transitive)
- RemovedBase64@^0.3.0
- RemovedBase64@0.3.0(transitive)