diff --git a/dist/stormpath-sdk-angularjs.js b/dist/stormpath-sdk-angularjs.js index 3058020..ce23368 100644 --- a/dist/stormpath-sdk-angularjs.js +++ b/dist/stormpath-sdk-angularjs.js @@ -2,7 +2,7 @@ * stormpath-sdk-angularjs * Copyright Stormpath, Inc. 2016 * - * @version v1.1.1-dev-2016-10-28 + * @version v1.1.1-dev-2016-10-31 * @link https://github.com/stormpath/stormpath-sdk-angularjs * @license Apache-2.0 */ @@ -2923,7 +2923,7 @@ angular.module('stormpath') * Currently, this provider does not have any configuration methods. */ -angular.module('stormpath.userService',['stormpath.CONFIG']) +angular.module('stormpath.userService',['stormpath.CONFIG', 'stormpath.util']) .provider('$user', [function $userProvider(){ /** @@ -2976,8 +2976,8 @@ angular.module('stormpath.userService',['stormpath.CONFIG']) }; this.$get = [ - '$q','$http','STORMPATH_CONFIG','$rootScope','$spFormEncoder','$spErrorTransformer', - function userServiceFactory($q,$http,STORMPATH_CONFIG,$rootScope,$spFormEncoder,$spErrorTransformer){ + '$q','$http','STORMPATH_CONFIG','$rootScope','$spFormEncoder','$spErrorTransformer', '$verifyResponse', + function userServiceFactory($q,$http,STORMPATH_CONFIG,$rootScope,$spFormEncoder,$spErrorTransformer, $verifyResponse){ function UserService(){ this.cachedUserOp = null; @@ -3145,6 +3145,13 @@ angular.module('stormpath.userService',['stormpath.CONFIG']) self.cachedUserOp = op; $http.get(STORMPATH_CONFIG.getUrl('CURRENT_USER_URI'),{withCredentials:true}).then(function(response){ + var responseStatus = $verifyResponse(response); + + if (!responseStatus.valid) { + self.currentUser = false; + return op.reject(responseStatus.error); + } + self.cachedUserOp = null; self.currentUser = new User(response.data.account || response.data); currentUserEvent(self.currentUser); @@ -3454,38 +3461,119 @@ angular.module('stormpath.userService',['stormpath.CONFIG']) ]; }]); +'use strict'; + +/** +* @ngdoc overview +* +* @name stormpath.util +* +* @description +* This module provides general utility functions. +*/ + +/** +* @ngdoc object +* +* @name stormpath.util.$verifyResponse +* +* @description +* A factory that creates a {@link stormpath.util.$verifyResponse#$verifyResponse $verifyResponse} +* function. +*/ +angular.module('stormpath.util', []) +.factory('$verifyResponse', function() { + /** + * @ngdoc function + * + * @name stormpath.util.$verifyResponse#$verifyResponse + * @methodOf stormpath.util.$verifyResponse + * + * @param {Object} response The response returned from a $http.get() request + * @returns {Object} Object containing the validation result. It has a boolean field `value` + * that is true if the request passes validation, and otherwise false. If validate is false, + * it will return an error field, containing an object with the error message in `retVal.error.message` + * + * @description + * Checks whether the response has a valid format. Currently, to be valid, it + * has to be a well-formed response and have the 'application/json' content type. + */ + return function verifyResponse(response) { + if (!response || typeof response.headers !== 'function') { + return { + valid: false, + error: { + message: 'Invalid response object format' + } + }; + } + + var contentType = response.headers('Content-Type'); + + if (!contentType.startsWith('application/json')) { + return { + valid: false, + error: { + message: 'Incorrect current user API endpoint response content type: ' + contentType + } + }; + } + + return { + valid: true + }; + }; +}); + (function () { 'use strict'; - function ViewModelService($http, STORMPATH_CONFIG) { + function ViewModelService($http, $verifyResponse, STORMPATH_CONFIG) { this.$http = $http; + this.$verifyResponse = $verifyResponse; this.STORMPATH_CONFIG = STORMPATH_CONFIG; } ViewModelService.prototype.getLoginModel = function getLoginModel() { + var self = this; + return this.$http.get(this.STORMPATH_CONFIG.getUrl('AUTHENTICATION_ENDPOINT'), { headers: { 'Accept': 'application/json' } }).then(function (response) { + var responseStatus = self.$verifyResponse(response); + + if (!responseStatus.valid) { + throw responseStatus.error; + } + return response.data; }); }; ViewModelService.prototype.getRegisterModel = function getRegisterModel() { + var self = this; + return this.$http.get(this.STORMPATH_CONFIG.getUrl('REGISTER_URI'), { headers: { 'Accept': 'application/json' } }).then(function (response) { + var responseStatus = self.$verifyResponse(response); + + if (!responseStatus.valid) { + throw responseStatus.error; + } + return response.data; }); }; - angular.module('stormpath.viewModelService', []) + angular.module('stormpath.viewModelService', ['stormpath.util']) .provider('$viewModel', function () { - this.$get = ['$http', 'STORMPATH_CONFIG', function viewModelFactory($http, STORMPATH_CONFIG) { - return new ViewModelService($http, STORMPATH_CONFIG); + this.$get = ['$http', '$verifyResponse', 'STORMPATH_CONFIG', function viewModelFactory($http, $verifyResponse, STORMPATH_CONFIG) { + return new ViewModelService($http, $verifyResponse, STORMPATH_CONFIG); }]; }); }()); diff --git a/dist/stormpath-sdk-angularjs.min.js b/dist/stormpath-sdk-angularjs.min.js index 56db26a..c2d50f4 100644 --- a/dist/stormpath-sdk-angularjs.min.js +++ b/dist/stormpath-sdk-angularjs.min.js @@ -2,8 +2,8 @@ * stormpath-sdk-angularjs * Copyright Stormpath, Inc. 2016 * - * @version v1.1.1-dev-2016-10-28 + * @version v1.1.1-dev-2016-10-31 * @link https://github.com/stormpath/stormpath-sdk-angularjs * @license Apache-2.0 */ -"undefined"!=typeof module&&"undefined"!=typeof exports&&module.exports===exports&&(module.exports="stormpath"),function(a,b,c){"use strict";b.module("stormpath",["stormpath.CONFIG","stormpath.auth","stormpath.userService","stormpath.viewModelService","stormpath.socialLogin","stormpath.facebookLogin","stormpath.googleLogin"]).factory("SpAuthInterceptor",[function(){function a(){}return a.prototype.request=function(a){return a.withCredentials=!0,a},new a}]).factory("StormpathAgentInterceptor",["$window",function(a){function c(b){var c=a.document.createElement("a");return c.href=b,c}function d(){}return d.prototype.request=function(d){var e=c(d.url),f=a.location;return e.host===f.host&&(d.headers["X-Stormpath-Agent"]="stormpath-sdk-angularjs/1.1.1 angularjs/"+b.version.full),d},new d}]).config(["$httpProvider",function(a){a.interceptors.push("SpAuthInterceptor"),a.interceptors.push("StormpathAgentInterceptor")}]).provider("$stormpath",[function(){this.$get=["$user","$injector","STORMPATH_CONFIG","$rootScope","$location",function(a,b,d,e,f){function g(){var a=new m;return this.encodeUrlForm=a.encode.bind(a),b.has("$state")&&(n=b.get("$state")),b.has("$route")&&(o=b.get("$route")),this}function h(a,b){e.$broadcast(d.STATE_CHANGE_UNAUTHENTICATED,a,b)}function i(a,b){e.$broadcast(d.STATE_CHANGE_UNAUTHORIZED,a,b)}function j(b,c){var d=b;if(d&&d.authorize&&d.authorize.group)return a.currentUser.inGroup(d.authorize.group);if(c){var e=c.filter(function(b){return a.currentUser.inGroup(b)});return e.length>0}return console.error("Unknown authorize configuration for spStateConfig",b),!1}function k(a){e.$broadcast(d.ROUTE_CHANGE_UNAUTHENTICATED,a)}function l(a){e.$broadcast(d.ROUTE_CHANGE_UNAUTHORIZED,a)}function m(){return this.delimiter="&",this.arrayPrefixGenerators={brackets:function(a){return a+"[]"},indices:function(a,b){return a+"["+b+"]"},repeat:function(a){return a}},this}var n,o;return g.prototype.stateChangeInterceptor=function(b){e.$on("$stateChangeStart",function(d,e,f){var g=e.sp||{},k=e.data&&e.data.authorities?e.data.authorities:c;(g.authenticate||g.authorize||k&&k.length)&&!a.currentUser?(d.preventDefault(),a.get().then(function(){g.authorize||k&&k.length?j(g,k)?n.go(e.name,f):i(e,f):n.go(e.name,f)},function(){h(e,f)})):g.waitForUser&&null===a.currentUser?(d.preventDefault(),a.get()["finally"](function(){n.go(e.name,f)})):a.currentUser&&(g.authorize||k&&k.length)?j(g,k)||(d.preventDefault(),i(e,f)):e.name===b.loginState&&a.currentUser!==!1&&(d.preventDefault(),a.get()["finally"](function(){a.currentUser&&a.currentUser.href?n.go(b.defaultPostLoginState):n.go(e.name,f)}))})},g.prototype.routeChangeInterceptor=function(b){function c(a){setTimeout(function(){a.$$route.originalPath===f.path()?o.reload():f.path(a)})}e.$on("$routeChangeStart",function(d,e){if(e.$$route){var f=e.$$route.sp||{};!f.authenticate&&!f.authorize||a.currentUser?f.waitForUser&&null===a.currentUser?(d.preventDefault(),a.get()["finally"](function(){c(e)})):a.currentUser&&f.authorize?j(f)||(d.preventDefault(),l(e)):e.$$route.originalPath===b.loginRoute&&a.currentUser&&a.currentUser.href&&(d.preventDefault(),c(b.defaultPostLoginRoute)):(d.preventDefault(),a.get().then(function(){f.authorize?j(f)?c(e):i(e):c(e)},function(){k(e)}))}})},g.prototype.uiRouter=function(a){var b=this;a="object"==typeof a?a:{},this.stateChangeInterceptor(a),a.loginState&&(b.unauthenticatedWather=e.$on(d.STATE_CHANGE_UNAUTHENTICATED,function(c,d,e){b.postLogin={toState:d,toParams:e},n.go(a.loginState)})),e.$on(d.AUTHENTICATION_SUCCESS_EVENT_NAME,function(){b.postLogin&&a.autoRedirect!==!1?n.go(b.postLogin.toState,b.postLogin.toParams).then(function(){b.postLogin=null}):a.defaultPostLoginState&&n.go(a.defaultPostLoginState)}),a.forbiddenState&&(b.forbiddenWatcher=e.$on(d.STATE_CHANGE_UNAUTHORIZED,function(){n.go(a.forbiddenState)}))},g.prototype.ngRouter=function(a){var b=this;a="object"==typeof a?a:{},this.routeChangeInterceptor(a),a.loginRoute&&(this.unauthenticatedWather=e.$on(d.ROUTE_CHANGE_UNAUTHENTICATED,function(c,d){b.postLogin={toRoute:d},f.path(a.loginRoute)})),e.$on(d.AUTHENTICATION_SUCCESS_EVENT_NAME,function(){b.postLogin&&a.autoRedirect!==!1?(f.path(b.postLogin.toRoute),b.postLogin=null):a.defaultPostLoginRoute&&f.path(a.defaultPostLoginRoute)}),a.forbiddenRoute&&(this.forbiddenWatcher=e.$on(d.ROUTE_CHANGE_UNAUTHORIZED,function(){f.path(a.forbiddenRoute)}))},g.prototype.regexAttrParser=function(a){var b;return b=a instanceof RegExp?a:a&&/^\/.+\/[gim]?$/.test(a)?new RegExp(a.split("/")[1],a.split("/")[2]):a},m.prototype.stringify=function(a,b,c){if(a instanceof Date?a=a.toISOString():null===a&&(a=""),"string"==typeof a||"number"==typeof a||"boolean"==typeof a)return[encodeURIComponent(b)+"="+encodeURIComponent(a)];var d=[];if("undefined"==typeof a)return d;for(var e=Object.keys(a),f=0,g=e.length;f-1}),a.viewModel=b})["catch"](function(a){throw new Error("Could not load login view model from back-end: "+a.message)}),a.formModel={},a.posting=!1,a.submit=function(){a.posting=!0,a.error=null,b.authenticate(a.formModel)["catch"](function(b){a.posting=!1,a.error=b.message})}}]).directive("spLoginForm",function(){return{templateUrl:function(a,b){return b.templateUrl||"spLoginForm.tpl.html"},controller:"SpLoginFormCtrl"}}),b.module("stormpath").controller("SpPasswordResetRequestCtrl",["$scope","$user",function(a,b){a.sent=!1,a.posting=!1,a.formModel={username:""},a.error=null,a.submit=function(){a.posting=!0,a.error=null,b.passwordResetRequest({email:a.formModel.email}).then(function(){a.sent=!0})["catch"](function(b){a.error=b.message})["finally"](function(){a.posting=!1})}}]).controller("SpPasswordResetCtrl",["$scope","$location","$user",function(a,b,c){var d=b.search().sptoken;a.showVerificationError=!1,a.verifying=!1,a.verified=!1,a.posting=!1,a.reset=!1,a.error=null,a.resendFailed=!1,a.formModel={password:"",confirmPassword:""},"string"==typeof d?(a.verifying=!0,c.verifyPasswordResetToken(d).then(function(){a.verified=!0})["catch"](function(){a.showVerificationError=!0})["finally"](function(){a.verifying=!1})):a.showVerificationError=!0,a.submit=function(){return a.formModel.password!==a.formModel.confirmPassword?void(a.error="Passwords do not match"):(a.posting=!0,a.error=null,a.showVerificationError=!1,void c.resetPassword(d,{password:a.formModel.password}).then(function(){a.reset=!0})["catch"](function(b){a.error=b.message})["finally"](function(){a.posting=!1}))}}]).directive("spPasswordResetRequestForm",function(){return{templateUrl:function(a,b){return b.templateUrl||"spPasswordResetRequestForm.tpl.html"},controller:"SpPasswordResetRequestCtrl"}}).directive("spPasswordResetForm",function(){return{templateUrl:function(a,b){return b.templateUrl||"spPasswordResetForm.tpl.html"},controller:"SpPasswordResetCtrl"}}),b.module("stormpath").controller("SpRegistrationFormCtrl",["$scope","$user","$auth","$location","$viewModel","$injector",function(a,b,c,d,e,f){a.formModel="object"==typeof a.formModel?a.formModel:{},a.created=!1,a.enabled=!1,a.creating=!1,a.authenticating=!1,a.viewModel=null,e.getRegisterModel().then(function(b){var c=["facebook","google"];b.accountStores=b.accountStores.filter(function(a){var b=a.provider.providerId;return c.indexOf(b)>-1}),a.viewModel=b})["catch"](function(a){throw new Error("Could not load login view model from back-end: "+a.message)}),a.submit=function(){a.creating=!0,a.error=null,b.create(a.formModel).then(function(b){a.created=!0,a.enabled="ENABLED"===b.status,a.enabled&&a.autoLogin?(a.authenticating=!0,c.authenticate({username:a.formModel.email,password:a.formModel.password}).then(function(){var b=f.has("$state")?f.get("$state"):null;a.postLoginState&&b?b.go(a.postLoginState):a.postLoginPath&&d.path(a.postLoginPath)})["catch"](function(b){a.error=b.message})["finally"](function(){a.authenticating=!1,a.creating=!1})):a.creating=!1})["catch"](function(b){a.creating=!1,a.error=b.message})}}]).directive("spRegistrationForm",function(){return{templateUrl:function(a,b){return b.templateUrl||"spRegistrationForm.tpl.html"},controller:"SpRegistrationFormCtrl",link:function(a,b,c){a.autoLogin="true"===c.autoLogin,a.postLoginPath=c.postLoginPath||"",a.postLoginState=c.postLoginState||""}}}),function(){function c(a,b){switch(b.status){case"connected":a.resolve({providerData:{providerId:"facebook",accessToken:b.authResponse.accessToken}});break;case"not_authorized":a.reject(new Error("Please log into this app"));break;default:a.reject(new Error("Please log into Facebook."))}}function d(a,b){this.name="Facebook",this.clientId=null,this.$q=a,this.$spJsLoader=b}d.prototype.init=function(){var b=this.clientId;a.fbAsyncInit=function(){FB.init({appId:b,status:!0,cookie:!0,xfbml:!0,version:"v2.4"})},a.FB?a.fbAsyncInit():this.$spJsLoader.load("facebook-jssdk","//connect.facebook.net/en_US/sdk.js")},d.prototype.login=function(a){var b=this.$q.defer();return FB.login(c.bind(null,b),a),b.promise},b.module("stormpath.facebookLogin",[]).provider("$facebookLogin",function(){this.$get=["$q","$spJsLoader",function(a,b){return new d(a,b)}]})}(),function(){function a(a,b){this.name="Google",this.clientId=null,this.googleAuth=null,this.$q=a,this.$spJsLoader=b}a.prototype.setGoogleAuth=function(a){this.googleAuth=a},a.prototype.init=function(a){var b=this.clientId,c=this.setGoogleAuth.bind(this);this.$spJsLoader.load("google-jssdk","//apis.google.com/js/api:client.js").then(function(){gapi.load("auth2",function(){var a=gapi.auth2.init({client_id:b,cookiepolicy:"single_host_origin"});c(a)})})},a.prototype.login=function(a){var b=this.$q.defer();return a=a||{},a.redirect_uri="postmessage",this.googleAuth.grantOfflineAccess(a).then(function(a){b.resolve({providerData:{providerId:"google",code:a.code}})},function(a){b.reject(a)}),b.promise},b.module("stormpath.googleLogin",[]).provider("$googleLogin",function(){this.$get=["$q","$spJsLoader",function(b,c){return new a(b,c)}]})}(),function(){function a(a,b,c,d){this.providersPromise=null,this.STORMPATH_CONFIG=a,this.$injector=b,this.$http=c,this.$q=d}b.module("stormpath.socialLogin",["stormpath.CONFIG"]).config(["$injector","STORMPATH_CONFIG",function(b,c){var d=["$http","$q","$injector",function(b,d,e){return new a(c,e,b,d)}];b.get("$provide").factory(c.SOCIAL_LOGIN_SERVICE_NAME,d)}]).factory("$spJsLoader",["$q",function(a){return{load:function(b,c,d){var e=a.defer(),f=document.getElementsByTagName("script")[0],g=document.createElement("script");return document.getElementById(b)?e.resolve():(g.id=b,g.src=c,g.innerHTML=d,g.onload=e.resolve,f.parentNode.insertBefore(g,f)),e.promise}}}]).directive("spSocialLogin",["$viewModel","$auth","$injector",function(a,b,c){return{link:function(a,d,e){var f,g=a.$parent;try{f=c.get("$"+e.spSocialLogin+"Login")}catch(h){return}f.clientId=e.spClientId,f.init(d),a.providerName=f.name,d.bind("click",function(){var a={scope:e.spScope};g.posting=!0,f.login(a).then(function(a){return b.authenticate(a)})["catch"](function(a){g.posting=!1,a.message?g.error=a.message:g.error="An error occured when communicating with server."})})}}}])}(),function(){function a(a){return"api_key: "+a+"\nauthorize: true"}function c(b,c){var d=a(c);b.load("linkedin-jssdk","//platform.linkedin.com/in.js",d)}function d(a,b){this.name="LinkedIn",this.clientId=null,this.$q=a,this.$spJsLoader=b}d.prototype.init=function(a){c(this.$spJsLoader,this.clientId)},d.prototype.login=function(a){var b=this.$q.defer();return IN.User.authorize(function(){b.resolve({providerData:{providerId:"linkedin",accessToken:IN.ENV.auth.oauth_token}})}),b.promise},b.module("stormpath.linkedinLogin",[]).provider("$linkedinLogin",function(){this.$get=["$q","$spJsLoader",function(a,b){return new d(a,b)}]})}(),b.module("stormpath.userService",["stormpath.CONFIG"]).provider("$user",[function(){function a(a){var b=this;Object.keys(a).map(function(c){b[c]=a[c]})}a.prototype.inGroup=function(a){return this.groups.items.filter(function(b){return b.name===a}).length>0},a.prototype.matchesGroupExpression=function(a){return this.groups.items.filter(function(b){return a.test(b.name)}).length>0},a.prototype.groupTest=function(a){return!!(a instanceof RegExp&&this.matchesGroupExpression(a))||!!this.inGroup(a)},this.$get=["$q","$http","STORMPATH_CONFIG","$rootScope","$spFormEncoder","$spErrorTransformer",function(b,c,d,e,f,g){function h(){return this.cachedUserOp=null,this.currentUser=null,this}function i(a){e.$broadcast(d.REGISTERED_EVENT_NAME,a)}function j(a){e.$broadcast(d.GET_USER_EVENT,a)}function k(){e.$broadcast(d.NOT_LOGGED_IN_EVENT)}h.prototype.create=function(a){return c(f.formPost({url:d.getUrl("REGISTER_URI"),method:"POST",data:a})).then(function(a){var c=a.data.account||a.data;return i(c),b.resolve(c)},function(a){return b.reject(g.transformError(a))})},h.prototype.get=function(e){var f=b.defer(),g=this;return g.cachedUserOp?g.cachedUserOp.promise:null!==g.currentUser&&g.currentUser!==!1&&e!==!0?(f.resolve(g.currentUser),f.promise):(g.cachedUserOp=f,c.get(d.getUrl("CURRENT_USER_URI"),{withCredentials:!0}).then(function(b){g.cachedUserOp=null,g.currentUser=new a(b.data.account||b.data),j(g.currentUser),f.resolve(g.currentUser)},function(a){g.currentUser=!1,401===a.status&&k(),g.cachedUserOp=null,f.reject(a)}),f.promise)},h.prototype.resendVerificationEmail=function(a){return c({method:"POST",url:d.getUrl("EMAIL_VERIFICATION_ENDPOINT"),data:a})},h.prototype.verify=function(a){return c({url:d.getUrl("EMAIL_VERIFICATION_ENDPOINT")+"?sptoken="+a})},h.prototype.verifyPasswordResetToken=function(a){return c.get(d.getUrl("CHANGE_PASSWORD_ENDPOINT")+"?sptoken="+a)},h.prototype.passwordResetRequest=function(a){return c(f.formPost({method:"POST",url:d.getUrl("FORGOT_PASSWORD_ENDPOINT"),data:a}))["catch"](function(a){return b.reject(g.transformError(a))})},h.prototype.resetPassword=function(a,e){return e.sptoken=a,c(f.formPost({method:"POST",url:d.getUrl("CHANGE_PASSWORD_ENDPOINT"),data:e}))["catch"](function(a){return b.reject(g.transformError(a))})};var l=new h;return e.$on(d.SESSION_END_EVENT,function(){l.currentUser=!1}),l}]}]),function(){function a(a,b){this.$http=a,this.STORMPATH_CONFIG=b}a.prototype.getLoginModel=function(){return this.$http.get(this.STORMPATH_CONFIG.getUrl("AUTHENTICATION_ENDPOINT"),{headers:{Accept:"application/json"}}).then(function(a){return a.data})},a.prototype.getRegisterModel=function(){return this.$http.get(this.STORMPATH_CONFIG.getUrl("REGISTER_URI"),{headers:{Accept:"application/json"}}).then(function(a){return a.data})},b.module("stormpath.viewModelService",[]).provider("$viewModel",function(){this.$get=["$http","STORMPATH_CONFIG",function(b,c){return new a(b,c)}]})}()}(window,window.angular); \ No newline at end of file +"undefined"!=typeof module&&"undefined"!=typeof exports&&module.exports===exports&&(module.exports="stormpath"),function(a,b,c){"use strict";b.module("stormpath",["stormpath.CONFIG","stormpath.auth","stormpath.userService","stormpath.viewModelService","stormpath.socialLogin","stormpath.facebookLogin","stormpath.googleLogin"]).factory("SpAuthInterceptor",[function(){function a(){}return a.prototype.request=function(a){return a.withCredentials=!0,a},new a}]).factory("StormpathAgentInterceptor",["$window",function(a){function c(b){var c=a.document.createElement("a");return c.href=b,c}function d(){}return d.prototype.request=function(d){var e=c(d.url),f=a.location;return e.host===f.host&&(d.headers["X-Stormpath-Agent"]="stormpath-sdk-angularjs/1.1.1 angularjs/"+b.version.full),d},new d}]).config(["$httpProvider",function(a){a.interceptors.push("SpAuthInterceptor"),a.interceptors.push("StormpathAgentInterceptor")}]).provider("$stormpath",[function(){this.$get=["$user","$injector","STORMPATH_CONFIG","$rootScope","$location",function(a,b,d,e,f){function g(){var a=new m;return this.encodeUrlForm=a.encode.bind(a),b.has("$state")&&(n=b.get("$state")),b.has("$route")&&(o=b.get("$route")),this}function h(a,b){e.$broadcast(d.STATE_CHANGE_UNAUTHENTICATED,a,b)}function i(a,b){e.$broadcast(d.STATE_CHANGE_UNAUTHORIZED,a,b)}function j(b,c){var d=b;if(d&&d.authorize&&d.authorize.group)return a.currentUser.inGroup(d.authorize.group);if(c){var e=c.filter(function(b){return a.currentUser.inGroup(b)});return e.length>0}return console.error("Unknown authorize configuration for spStateConfig",b),!1}function k(a){e.$broadcast(d.ROUTE_CHANGE_UNAUTHENTICATED,a)}function l(a){e.$broadcast(d.ROUTE_CHANGE_UNAUTHORIZED,a)}function m(){return this.delimiter="&",this.arrayPrefixGenerators={brackets:function(a){return a+"[]"},indices:function(a,b){return a+"["+b+"]"},repeat:function(a){return a}},this}var n,o;return g.prototype.stateChangeInterceptor=function(b){e.$on("$stateChangeStart",function(d,e,f){var g=e.sp||{},k=e.data&&e.data.authorities?e.data.authorities:c;(g.authenticate||g.authorize||k&&k.length)&&!a.currentUser?(d.preventDefault(),a.get().then(function(){g.authorize||k&&k.length?j(g,k)?n.go(e.name,f):i(e,f):n.go(e.name,f)},function(){h(e,f)})):g.waitForUser&&null===a.currentUser?(d.preventDefault(),a.get()["finally"](function(){n.go(e.name,f)})):a.currentUser&&(g.authorize||k&&k.length)?j(g,k)||(d.preventDefault(),i(e,f)):e.name===b.loginState&&a.currentUser!==!1&&(d.preventDefault(),a.get()["finally"](function(){a.currentUser&&a.currentUser.href?n.go(b.defaultPostLoginState):n.go(e.name,f)}))})},g.prototype.routeChangeInterceptor=function(b){function c(a){setTimeout(function(){a.$$route.originalPath===f.path()?o.reload():f.path(a)})}e.$on("$routeChangeStart",function(d,e){if(e.$$route){var f=e.$$route.sp||{};!f.authenticate&&!f.authorize||a.currentUser?f.waitForUser&&null===a.currentUser?(d.preventDefault(),a.get()["finally"](function(){c(e)})):a.currentUser&&f.authorize?j(f)||(d.preventDefault(),l(e)):e.$$route.originalPath===b.loginRoute&&a.currentUser&&a.currentUser.href&&(d.preventDefault(),c(b.defaultPostLoginRoute)):(d.preventDefault(),a.get().then(function(){f.authorize?j(f)?c(e):i(e):c(e)},function(){k(e)}))}})},g.prototype.uiRouter=function(a){var b=this;a="object"==typeof a?a:{},this.stateChangeInterceptor(a),a.loginState&&(b.unauthenticatedWather=e.$on(d.STATE_CHANGE_UNAUTHENTICATED,function(c,d,e){b.postLogin={toState:d,toParams:e},n.go(a.loginState)})),e.$on(d.AUTHENTICATION_SUCCESS_EVENT_NAME,function(){b.postLogin&&a.autoRedirect!==!1?n.go(b.postLogin.toState,b.postLogin.toParams).then(function(){b.postLogin=null}):a.defaultPostLoginState&&n.go(a.defaultPostLoginState)}),a.forbiddenState&&(b.forbiddenWatcher=e.$on(d.STATE_CHANGE_UNAUTHORIZED,function(){n.go(a.forbiddenState)}))},g.prototype.ngRouter=function(a){var b=this;a="object"==typeof a?a:{},this.routeChangeInterceptor(a),a.loginRoute&&(this.unauthenticatedWather=e.$on(d.ROUTE_CHANGE_UNAUTHENTICATED,function(c,d){b.postLogin={toRoute:d},f.path(a.loginRoute)})),e.$on(d.AUTHENTICATION_SUCCESS_EVENT_NAME,function(){b.postLogin&&a.autoRedirect!==!1?(f.path(b.postLogin.toRoute),b.postLogin=null):a.defaultPostLoginRoute&&f.path(a.defaultPostLoginRoute)}),a.forbiddenRoute&&(this.forbiddenWatcher=e.$on(d.ROUTE_CHANGE_UNAUTHORIZED,function(){f.path(a.forbiddenRoute)}))},g.prototype.regexAttrParser=function(a){var b;return b=a instanceof RegExp?a:a&&/^\/.+\/[gim]?$/.test(a)?new RegExp(a.split("/")[1],a.split("/")[2]):a},m.prototype.stringify=function(a,b,c){if(a instanceof Date?a=a.toISOString():null===a&&(a=""),"string"==typeof a||"number"==typeof a||"boolean"==typeof a)return[encodeURIComponent(b)+"="+encodeURIComponent(a)];var d=[];if("undefined"==typeof a)return d;for(var e=Object.keys(a),f=0,g=e.length;f-1}),a.viewModel=b})["catch"](function(a){throw new Error("Could not load login view model from back-end: "+a.message)}),a.formModel={},a.posting=!1,a.submit=function(){a.posting=!0,a.error=null,b.authenticate(a.formModel)["catch"](function(b){a.posting=!1,a.error=b.message})}}]).directive("spLoginForm",function(){return{templateUrl:function(a,b){return b.templateUrl||"spLoginForm.tpl.html"},controller:"SpLoginFormCtrl"}}),b.module("stormpath").controller("SpPasswordResetRequestCtrl",["$scope","$user",function(a,b){a.sent=!1,a.posting=!1,a.formModel={username:""},a.error=null,a.submit=function(){a.posting=!0,a.error=null,b.passwordResetRequest({email:a.formModel.email}).then(function(){a.sent=!0})["catch"](function(b){a.error=b.message})["finally"](function(){a.posting=!1})}}]).controller("SpPasswordResetCtrl",["$scope","$location","$user",function(a,b,c){var d=b.search().sptoken;a.showVerificationError=!1,a.verifying=!1,a.verified=!1,a.posting=!1,a.reset=!1,a.error=null,a.resendFailed=!1,a.formModel={password:"",confirmPassword:""},"string"==typeof d?(a.verifying=!0,c.verifyPasswordResetToken(d).then(function(){a.verified=!0})["catch"](function(){a.showVerificationError=!0})["finally"](function(){a.verifying=!1})):a.showVerificationError=!0,a.submit=function(){return a.formModel.password!==a.formModel.confirmPassword?void(a.error="Passwords do not match"):(a.posting=!0,a.error=null,a.showVerificationError=!1,void c.resetPassword(d,{password:a.formModel.password}).then(function(){a.reset=!0})["catch"](function(b){a.error=b.message})["finally"](function(){a.posting=!1}))}}]).directive("spPasswordResetRequestForm",function(){return{templateUrl:function(a,b){return b.templateUrl||"spPasswordResetRequestForm.tpl.html"},controller:"SpPasswordResetRequestCtrl"}}).directive("spPasswordResetForm",function(){return{templateUrl:function(a,b){return b.templateUrl||"spPasswordResetForm.tpl.html"},controller:"SpPasswordResetCtrl"}}),b.module("stormpath").controller("SpRegistrationFormCtrl",["$scope","$user","$auth","$location","$viewModel","$injector",function(a,b,c,d,e,f){a.formModel="object"==typeof a.formModel?a.formModel:{},a.created=!1,a.enabled=!1,a.creating=!1,a.authenticating=!1,a.viewModel=null,e.getRegisterModel().then(function(b){var c=["facebook","google"];b.accountStores=b.accountStores.filter(function(a){var b=a.provider.providerId;return c.indexOf(b)>-1}),a.viewModel=b})["catch"](function(a){throw new Error("Could not load login view model from back-end: "+a.message)}),a.submit=function(){a.creating=!0,a.error=null,b.create(a.formModel).then(function(b){a.created=!0,a.enabled="ENABLED"===b.status,a.enabled&&a.autoLogin?(a.authenticating=!0,c.authenticate({username:a.formModel.email,password:a.formModel.password}).then(function(){var b=f.has("$state")?f.get("$state"):null;a.postLoginState&&b?b.go(a.postLoginState):a.postLoginPath&&d.path(a.postLoginPath)})["catch"](function(b){a.error=b.message})["finally"](function(){a.authenticating=!1,a.creating=!1})):a.creating=!1})["catch"](function(b){a.creating=!1,a.error=b.message})}}]).directive("spRegistrationForm",function(){return{templateUrl:function(a,b){return b.templateUrl||"spRegistrationForm.tpl.html"},controller:"SpRegistrationFormCtrl",link:function(a,b,c){a.autoLogin="true"===c.autoLogin,a.postLoginPath=c.postLoginPath||"",a.postLoginState=c.postLoginState||""}}}),function(){function c(a,b){switch(b.status){case"connected":a.resolve({providerData:{providerId:"facebook",accessToken:b.authResponse.accessToken}});break;case"not_authorized":a.reject(new Error("Please log into this app"));break;default:a.reject(new Error("Please log into Facebook."))}}function d(a,b){this.name="Facebook",this.clientId=null,this.$q=a,this.$spJsLoader=b}d.prototype.init=function(){var b=this.clientId;a.fbAsyncInit=function(){FB.init({appId:b,status:!0,cookie:!0,xfbml:!0,version:"v2.4"})},a.FB?a.fbAsyncInit():this.$spJsLoader.load("facebook-jssdk","//connect.facebook.net/en_US/sdk.js")},d.prototype.login=function(a){var b=this.$q.defer();return FB.login(c.bind(null,b),a),b.promise},b.module("stormpath.facebookLogin",[]).provider("$facebookLogin",function(){this.$get=["$q","$spJsLoader",function(a,b){return new d(a,b)}]})}(),function(){function a(a,b){this.name="Google",this.clientId=null,this.googleAuth=null,this.$q=a,this.$spJsLoader=b}a.prototype.setGoogleAuth=function(a){this.googleAuth=a},a.prototype.init=function(a){var b=this.clientId,c=this.setGoogleAuth.bind(this);this.$spJsLoader.load("google-jssdk","//apis.google.com/js/api:client.js").then(function(){gapi.load("auth2",function(){var a=gapi.auth2.init({client_id:b,cookiepolicy:"single_host_origin"});c(a)})})},a.prototype.login=function(a){var b=this.$q.defer();return a=a||{},a.redirect_uri="postmessage",this.googleAuth.grantOfflineAccess(a).then(function(a){b.resolve({providerData:{providerId:"google",code:a.code}})},function(a){b.reject(a)}),b.promise},b.module("stormpath.googleLogin",[]).provider("$googleLogin",function(){this.$get=["$q","$spJsLoader",function(b,c){return new a(b,c)}]})}(),function(){function a(a,b,c,d){this.providersPromise=null,this.STORMPATH_CONFIG=a,this.$injector=b,this.$http=c,this.$q=d}b.module("stormpath.socialLogin",["stormpath.CONFIG"]).config(["$injector","STORMPATH_CONFIG",function(b,c){var d=["$http","$q","$injector",function(b,d,e){return new a(c,e,b,d)}];b.get("$provide").factory(c.SOCIAL_LOGIN_SERVICE_NAME,d)}]).factory("$spJsLoader",["$q",function(a){return{load:function(b,c,d){var e=a.defer(),f=document.getElementsByTagName("script")[0],g=document.createElement("script");return document.getElementById(b)?e.resolve():(g.id=b,g.src=c,g.innerHTML=d,g.onload=e.resolve,f.parentNode.insertBefore(g,f)),e.promise}}}]).directive("spSocialLogin",["$viewModel","$auth","$injector",function(a,b,c){return{link:function(a,d,e){var f,g=a.$parent;try{f=c.get("$"+e.spSocialLogin+"Login")}catch(h){return}f.clientId=e.spClientId,f.init(d),a.providerName=f.name,d.bind("click",function(){var a={scope:e.spScope};g.posting=!0,f.login(a).then(function(a){return b.authenticate(a)})["catch"](function(a){g.posting=!1,a.message?g.error=a.message:g.error="An error occured when communicating with server."})})}}}])}(),function(){function a(a){return"api_key: "+a+"\nauthorize: true"}function c(b,c){var d=a(c);b.load("linkedin-jssdk","//platform.linkedin.com/in.js",d)}function d(a,b){this.name="LinkedIn",this.clientId=null,this.$q=a,this.$spJsLoader=b}d.prototype.init=function(a){c(this.$spJsLoader,this.clientId)},d.prototype.login=function(a){var b=this.$q.defer();return IN.User.authorize(function(){b.resolve({providerData:{providerId:"linkedin",accessToken:IN.ENV.auth.oauth_token}})}),b.promise},b.module("stormpath.linkedinLogin",[]).provider("$linkedinLogin",function(){this.$get=["$q","$spJsLoader",function(a,b){return new d(a,b)}]})}(),b.module("stormpath.userService",["stormpath.CONFIG","stormpath.util"]).provider("$user",[function(){function a(a){var b=this;Object.keys(a).map(function(c){b[c]=a[c]})}a.prototype.inGroup=function(a){return this.groups.items.filter(function(b){return b.name===a}).length>0},a.prototype.matchesGroupExpression=function(a){return this.groups.items.filter(function(b){return a.test(b.name)}).length>0},a.prototype.groupTest=function(a){return!!(a instanceof RegExp&&this.matchesGroupExpression(a))||!!this.inGroup(a)},this.$get=["$q","$http","STORMPATH_CONFIG","$rootScope","$spFormEncoder","$spErrorTransformer","$verifyResponse",function(b,c,d,e,f,g,h){function i(){return this.cachedUserOp=null,this.currentUser=null,this}function j(a){e.$broadcast(d.REGISTERED_EVENT_NAME,a)}function k(a){e.$broadcast(d.GET_USER_EVENT,a)}function l(){e.$broadcast(d.NOT_LOGGED_IN_EVENT)}i.prototype.create=function(a){return c(f.formPost({url:d.getUrl("REGISTER_URI"),method:"POST",data:a})).then(function(a){var c=a.data.account||a.data;return j(c),b.resolve(c)},function(a){return b.reject(g.transformError(a))})},i.prototype.get=function(e){var f=b.defer(),g=this;return g.cachedUserOp?g.cachedUserOp.promise:null!==g.currentUser&&g.currentUser!==!1&&e!==!0?(f.resolve(g.currentUser),f.promise):(g.cachedUserOp=f,c.get(d.getUrl("CURRENT_USER_URI"),{withCredentials:!0}).then(function(b){var c=h(b);return c.valid?(g.cachedUserOp=null,g.currentUser=new a(b.data.account||b.data),k(g.currentUser),void f.resolve(g.currentUser)):(g.currentUser=!1,f.reject(c.error))},function(a){g.currentUser=!1,401===a.status&&l(),g.cachedUserOp=null,f.reject(a)}),f.promise)},i.prototype.resendVerificationEmail=function(a){return c({method:"POST",url:d.getUrl("EMAIL_VERIFICATION_ENDPOINT"),data:a})},i.prototype.verify=function(a){return c({url:d.getUrl("EMAIL_VERIFICATION_ENDPOINT")+"?sptoken="+a})},i.prototype.verifyPasswordResetToken=function(a){return c.get(d.getUrl("CHANGE_PASSWORD_ENDPOINT")+"?sptoken="+a)},i.prototype.passwordResetRequest=function(a){return c(f.formPost({method:"POST",url:d.getUrl("FORGOT_PASSWORD_ENDPOINT"),data:a}))["catch"](function(a){return b.reject(g.transformError(a))})},i.prototype.resetPassword=function(a,e){return e.sptoken=a,c(f.formPost({method:"POST",url:d.getUrl("CHANGE_PASSWORD_ENDPOINT"),data:e}))["catch"](function(a){return b.reject(g.transformError(a))})};var m=new i;return e.$on(d.SESSION_END_EVENT,function(){m.currentUser=!1}),m}]}]),b.module("stormpath.util",[]).factory("$verifyResponse",function(){return function(a){if(!a||"function"!=typeof a.headers)return{valid:!1,error:{message:"Invalid response object format"}};var b=a.headers("Content-Type");return b.startsWith("application/json")?{valid:!0}:{valid:!1,error:{message:"Incorrect current user API endpoint response content type: "+b}}}}),function(){function a(a,b,c){this.$http=a,this.$verifyResponse=b,this.STORMPATH_CONFIG=c}a.prototype.getLoginModel=function(){var a=this;return this.$http.get(this.STORMPATH_CONFIG.getUrl("AUTHENTICATION_ENDPOINT"),{headers:{Accept:"application/json"}}).then(function(b){var c=a.$verifyResponse(b);if(!c.valid)throw c.error;return b.data})},a.prototype.getRegisterModel=function(){var a=this;return this.$http.get(this.STORMPATH_CONFIG.getUrl("REGISTER_URI"),{headers:{Accept:"application/json"}}).then(function(b){var c=a.$verifyResponse(b);if(!c.valid)throw c.error;return b.data})},b.module("stormpath.viewModelService",["stormpath.util"]).provider("$viewModel",function(){this.$get=["$http","$verifyResponse","STORMPATH_CONFIG",function(b,c,d){return new a(b,c,d)}]})}()}(window,window.angular); \ No newline at end of file diff --git a/dist/stormpath-sdk-angularjs.tpls.js b/dist/stormpath-sdk-angularjs.tpls.js index 09d4369..3593152 100644 --- a/dist/stormpath-sdk-angularjs.tpls.js +++ b/dist/stormpath-sdk-angularjs.tpls.js @@ -2,7 +2,7 @@ * stormpath-sdk-angularjs * Copyright Stormpath, Inc. 2016 * - * @version v1.1.1-dev-2016-10-28 + * @version v1.1.1-dev-2016-10-31 * @link https://github.com/stormpath/stormpath-sdk-angularjs * @license Apache-2.0 */ diff --git a/dist/stormpath-sdk-angularjs.tpls.min.js b/dist/stormpath-sdk-angularjs.tpls.min.js index 7b346e5..3ca267f 100644 --- a/dist/stormpath-sdk-angularjs.tpls.min.js +++ b/dist/stormpath-sdk-angularjs.tpls.min.js @@ -2,7 +2,7 @@ * stormpath-sdk-angularjs * Copyright Stormpath, Inc. 2016 * - * @version v1.1.1-dev-2016-10-28 + * @version v1.1.1-dev-2016-10-31 * @link https://github.com/stormpath/stormpath-sdk-angularjs * @license Apache-2.0 */ diff --git a/src/stormpath.user.js b/src/stormpath.user.js index 535f543..55eca22 100644 --- a/src/stormpath.user.js +++ b/src/stormpath.user.js @@ -21,7 +21,7 @@ * Currently, this provider does not have any configuration methods. */ -angular.module('stormpath.userService',['stormpath.CONFIG']) +angular.module('stormpath.userService',['stormpath.CONFIG', 'stormpath.util']) .provider('$user', [function $userProvider(){ /** @@ -74,8 +74,8 @@ angular.module('stormpath.userService',['stormpath.CONFIG']) }; this.$get = [ - '$q','$http','STORMPATH_CONFIG','$rootScope','$spFormEncoder','$spErrorTransformer', - function userServiceFactory($q,$http,STORMPATH_CONFIG,$rootScope,$spFormEncoder,$spErrorTransformer){ + '$q','$http','STORMPATH_CONFIG','$rootScope','$spFormEncoder','$spErrorTransformer', '$verifyResponse', + function userServiceFactory($q,$http,STORMPATH_CONFIG,$rootScope,$spFormEncoder,$spErrorTransformer, $verifyResponse){ function UserService(){ this.cachedUserOp = null; @@ -243,6 +243,13 @@ angular.module('stormpath.userService',['stormpath.CONFIG']) self.cachedUserOp = op; $http.get(STORMPATH_CONFIG.getUrl('CURRENT_USER_URI'),{withCredentials:true}).then(function(response){ + var responseStatus = $verifyResponse(response); + + if (!responseStatus.valid) { + self.currentUser = false; + return op.reject(responseStatus.error); + } + self.cachedUserOp = null; self.currentUser = new User(response.data.account || response.data); currentUserEvent(self.currentUser); diff --git a/src/stormpath.verify-response.js b/src/stormpath.verify-response.js new file mode 100644 index 0000000..8424cb6 --- /dev/null +++ b/src/stormpath.verify-response.js @@ -0,0 +1,63 @@ +'use strict'; + +/** +* @ngdoc overview +* +* @name stormpath.util +* +* @description +* This module provides general utility functions. +*/ + +/** +* @ngdoc object +* +* @name stormpath.util.$verifyResponse +* +* @description +* A factory that creates a {@link stormpath.util.$verifyResponse#$verifyResponse $verifyResponse} +* function. +*/ +angular.module('stormpath.util', []) +.factory('$verifyResponse', function() { + /** + * @ngdoc function + * + * @name stormpath.util.$verifyResponse#$verifyResponse + * @methodOf stormpath.util.$verifyResponse + * + * @param {Object} response The response returned from a $http.get() request + * @returns {Object} Object containing the validation result. It has a boolean field `value` + * that is true if the request passes validation, and otherwise false. If validate is false, + * it will return an error field, containing an object with the error message in `retVal.error.message` + * + * @description + * Checks whether the response has a valid format. Currently, to be valid, it + * has to be a well-formed response and have the 'application/json' content type. + */ + return function verifyResponse(response) { + if (!response || typeof response.headers !== 'function') { + return { + valid: false, + error: { + message: 'Invalid response object format' + } + }; + } + + var contentType = response.headers('Content-Type'); + + if (!contentType.startsWith('application/json')) { + return { + valid: false, + error: { + message: 'Incorrect current user API endpoint response content type: ' + contentType + } + }; + } + + return { + valid: true + }; + }; +}); diff --git a/src/stormpath.view-model.js b/src/stormpath.view-model.js index f2c20e3..055f908 100644 --- a/src/stormpath.view-model.js +++ b/src/stormpath.view-model.js @@ -1,35 +1,52 @@ (function () { 'use strict'; - function ViewModelService($http, STORMPATH_CONFIG) { + function ViewModelService($http, $verifyResponse, STORMPATH_CONFIG) { this.$http = $http; + this.$verifyResponse = $verifyResponse; this.STORMPATH_CONFIG = STORMPATH_CONFIG; } ViewModelService.prototype.getLoginModel = function getLoginModel() { + var self = this; + return this.$http.get(this.STORMPATH_CONFIG.getUrl('AUTHENTICATION_ENDPOINT'), { headers: { 'Accept': 'application/json' } }).then(function (response) { + var responseStatus = self.$verifyResponse(response); + + if (!responseStatus.valid) { + throw responseStatus.error; + } + return response.data; }); }; ViewModelService.prototype.getRegisterModel = function getRegisterModel() { + var self = this; + return this.$http.get(this.STORMPATH_CONFIG.getUrl('REGISTER_URI'), { headers: { 'Accept': 'application/json' } }).then(function (response) { + var responseStatus = self.$verifyResponse(response); + + if (!responseStatus.valid) { + throw responseStatus.error; + } + return response.data; }); }; - angular.module('stormpath.viewModelService', []) + angular.module('stormpath.viewModelService', ['stormpath.util']) .provider('$viewModel', function () { - this.$get = ['$http', 'STORMPATH_CONFIG', function viewModelFactory($http, STORMPATH_CONFIG) { - return new ViewModelService($http, STORMPATH_CONFIG); + this.$get = ['$http', '$verifyResponse', 'STORMPATH_CONFIG', function viewModelFactory($http, $verifyResponse, STORMPATH_CONFIG) { + return new ViewModelService($http, $verifyResponse, STORMPATH_CONFIG); }]; }); }());