├── .gitignore ├── Gruntfile.js ├── README.md ├── bower.json ├── coffee ├── config.coffee ├── lib │ ├── api.coffee │ ├── core.coffee │ ├── oauth.coffee │ ├── providers.coffee │ ├── request.coffee │ └── user.coffee ├── main.coffee └── tools │ ├── cache.coffee │ ├── cookies.coffee │ ├── location_operations.coffee │ ├── lstorage.coffee │ ├── sha1.coffee │ └── url.coffee ├── compile-jquery.sh ├── dist ├── oauth.js └── oauth.min.js ├── js └── tools │ └── jquery-lite.js ├── package.json └── templates └── bower.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | /js/* 4 | !/js/tools 5 | /js/tools/* 6 | !/js/tools/jquery-lite.js 7 | *sublime* 8 | .idea 9 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var package_info = require('./package.json'); 4 | var fs = require('fs'); 5 | 6 | 7 | module.exports = function(grunt) { 8 | // Project configuration. 9 | var gruntConf = { 10 | watch: { 11 | options: { 12 | nospawn: true 13 | }, 14 | default: { 15 | files: ['./**/*.coffee'], 16 | tasks: ['coffee', 'browserify'] 17 | } 18 | }, 19 | coffee: { 20 | default: { 21 | expand: true, 22 | cwd: 'coffee', 23 | src: ['**/*.coffee'], 24 | dest: 'js', 25 | ext: '.js', 26 | options: { 27 | bare: true 28 | } 29 | } 30 | }, 31 | concurrent: { 32 | server: { 33 | options: { 34 | logConcurrentOutput: true 35 | } 36 | } 37 | }, 38 | browserify: { 39 | dist: { 40 | files: { 41 | './dist/oauth.js': ['js/main.js'] 42 | }, 43 | options: { 44 | transform: [ 45 | [ 46 | 'envify', { 47 | oauthd_url: 'https://oauth.io', 48 | api_url: 'https://oauth.io/api', 49 | sdk_version: "web-" + package_info.version 50 | } 51 | ] 52 | ], 53 | browserifyOptions: { 54 | standalone: 'oauthio-web' 55 | } 56 | } 57 | } 58 | }, 59 | uglify: { 60 | my_target: { 61 | files: { 62 | './dist/oauth.min.js': ['dist/oauth.js'] 63 | } 64 | } 65 | }, 66 | jasmine_node: { 67 | 68 | options: { 69 | forceExit: true, 70 | match: '.', 71 | matchall: false, 72 | extensions: 'js', 73 | specNameMatcher: 'spec' 74 | }, 75 | all: ['tests/unit/spec/'] 76 | }, 77 | 78 | taskDefault: ['coffee', 'browserify', 'uglify', 'bower'] 79 | }; 80 | 81 | grunt.initConfig(gruntConf); 82 | 83 | // These plugins provide necessary tasks. 84 | grunt.loadNpmTasks('grunt-contrib-watch'); 85 | grunt.loadNpmTasks('grunt-contrib-coffee'); 86 | grunt.loadNpmTasks('grunt-concurrent'); 87 | grunt.loadNpmTasks('grunt-browserify'); 88 | grunt.loadNpmTasks('grunt-contrib-uglify'); 89 | grunt.loadNpmTasks('grunt-jasmine-node'); 90 | 91 | grunt.registerTask('bower', 'Creates an updated bower.json', function() { 92 | var done = this.async(); 93 | fs.readFile('./templates/bower.json', 'UTF-8', function(e, text) { 94 | if (e) { 95 | console.err('A problem occured while creating bower.json'); 96 | done(); 97 | return; 98 | } 99 | text = text.replace('{{sdk_version}}', package_info.version); 100 | text = text.replace('{{description}}', package_info.description); 101 | text = text.replace('{{license}}', package_info.license); 102 | fs.writeFile('./bower.json', text, function(e) { 103 | if (e) { 104 | console.err('A problem occured while creating bower.json'); 105 | done(); 106 | return; 107 | } 108 | console.log("Wrote bower.json file"); 109 | done(); 110 | }); 111 | }); 112 | }); 113 | 114 | grunt.registerTask('coverage', 'Creates a tests coverage report', function() { 115 | var exec = require('child_process').exec; 116 | var done = this.async(); 117 | exec('npm test', function(error, stdout, stderr) { 118 | console.log("Coverage report should be generated in ./coverage/lcov-report/index.html"); 119 | done(); 120 | }); 121 | }); 122 | 123 | // Default task. 124 | grunt.registerTask('default', gruntConf.taskDefault); 125 | 126 | }; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | OAuth.io JavaScript SDK 2 | ======================= 3 | 4 | This is the JavaScript SDK for [OAuth.io](https://oauth.io). OAuth.io allows you to integrate **100+ providers** really easily in your web app, without worrying about each provider's OAuth specific implementation. 5 | 6 | Installation 7 | ============ 8 | 9 | Getting the SDK 10 | --------------- 11 | 12 | To get the SDK, you can : 13 | 14 | - download the zip file from this repository 15 | - get it via Bower or npm (for browserify) 16 | 17 | **Zip file** 18 | 19 | Just copy the dist/oauth.js or dist/oauth.min.js to your project. 20 | 21 | **Bower** 22 | 23 | ```sh 24 | $ bower install oauthio-web 25 | ``` 26 | 27 | **npm for browserify** 28 | 29 | ```sh 30 | $ npm install oauthio-web 31 | ``` 32 | 33 | 34 | Integrating in your project 35 | --------------------------- 36 | 37 | In the `` of your HTML, include OAuth.js 38 | 39 | `` 40 | 41 | In your Javascript, add this line to initialize OAuth.js 42 | 43 | `OAuth.initialize('your_app_public_key');` 44 | 45 | Usage 46 | ===== 47 | 48 | To connect your user using facebook, 2 methods: 49 | 50 | Popup mode 51 | ---------- 52 | 53 | ```javascript 54 | //Using popup (option 1) 55 | OAuth.popup('facebook') 56 | .done(function(result) { 57 | //use result.access_token in your API request 58 | //or use result.get|post|put|del|patch|me methods (see below) 59 | }) 60 | .fail(function (err) { 61 | //handle error with err 62 | }); 63 | ``` 64 | 65 | Redirection mode 66 | ---------------- 67 | 68 | ```javascript 69 | //Using redirection (option 2) 70 | OAuth.redirect('facebook', "callback/url"); 71 | ``` 72 | 73 | In callback url : 74 | 75 | ```javascript 76 | OAuth.callback('facebook') 77 | .done(function(result) { 78 | //use result.access_token in your API request 79 | //or use result.get|post|put|del|patch|me methods (see below) 80 | }) 81 | .fail(function (err) { 82 | //handle error with err 83 | }); 84 | ``` 85 | 86 | Making requests 87 | --------------- 88 | 89 | You can make requests to the provider's API manually with the access token you got from the `popup` or `callback` methods, or use the request methods stored in the `result` object. 90 | 91 | **GET Request** 92 | 93 | To make a GET request, you have to call the `result.get` method like this : 94 | 95 | ```javascript 96 | //Let's say the /me endpoint on the provider API returns a JSON object 97 | //with the field "name" containing the name "John Doe" 98 | OAuth.popup(provider) 99 | .done(function(result) { 100 | result.get('/me') 101 | .done(function (response) { 102 | //this will display "John Doe" in the console 103 | console.log(response.name); 104 | }) 105 | .fail(function (err) { 106 | //handle error with err 107 | }); 108 | }) 109 | .fail(function (err) { 110 | //handle error with err 111 | }); 112 | ``` 113 | 114 | **POST Request** 115 | 116 | To make a POST request, you have to call the `result.post` method like this : 117 | 118 | ```javascript 119 | //Let's say the /message endpoint on the provider waits for 120 | //a POST request containing the fields "user_id" and "content" 121 | //and returns the field "id" containing the id of the sent message 122 | OAuth.popup(provider) 123 | .done(function(result) { 124 | result.post('/message', { 125 | data: { 126 | user_id: 93, 127 | content: 'Hello Mr. 93 !' 128 | } 129 | }) 130 | .done(function (response) { 131 | //this will display the id of the message in the console 132 | console.log(response.id); 133 | }) 134 | .fail(function (err) { 135 | //handle error with err 136 | }); 137 | }) 138 | .fail(function (err) { 139 | //handle error with err 140 | }); 141 | ``` 142 | 143 | **PUT Request** 144 | 145 | To make a PUT request, you have to call the `result.post` method like this : 146 | 147 | ```javascript 148 | //Let's say the /profile endpoint on the provider waits for 149 | //a PUT request to update the authenticated user's profile 150 | //containing the field "name" and returns the field "name" 151 | //containing the new name 152 | OAuth.popup(provider) 153 | .done(function(result) { 154 | result.put('/message', { 155 | data: { 156 | name: "John Williams Doe III" 157 | } 158 | }) 159 | .done(function (response) { 160 | //this will display the new name in the console 161 | console.log(response.name); 162 | }) 163 | .fail(function (err) { 164 | //handle error with err 165 | }); 166 | }) 167 | .fail(function (err) { 168 | //handle error with err 169 | }); 170 | ``` 171 | 172 | **PATCH Request** 173 | 174 | To make a PATCH request, you have to call the `result.patch` method like this : 175 | 176 | ```javascript 177 | //Let's say the /profile endpoint on the provider waits for 178 | //a PATCH request to update the authenticated user's profile 179 | //containing the field "name" and returns the field "name" 180 | //containing the new name 181 | OAuth.popup(provider) 182 | .done(function(result) { 183 | result.patch('/message', { 184 | data: { 185 | name: "John Williams Doe III" 186 | } 187 | }) 188 | .done(function (response) { 189 | //this will display the new name in the console 190 | console.log(response.name); 191 | }) 192 | .fail(function (err) { 193 | //handle error with err 194 | }); 195 | }) 196 | .fail(function (err) { 197 | //handle error with err 198 | }); 199 | ``` 200 | 201 | **DELETE Request** 202 | 203 | To make a DELETE request, you have to call the `result.del` method like this : 204 | 205 | ```javascript 206 | //Let's say the /picture?id=picture_id endpoint on the provider waits for 207 | //a DELETE request to delete a picture with the id "84" 208 | //and returns true or false depending on the user's rights on the picture 209 | OAuth.popup(provider) 210 | .done(function(result) { 211 | result.del('/picture?id=84') 212 | .done(function (response) { 213 | //this will display true if the user was authorized to delete 214 | //the picture 215 | console.log(response); 216 | }) 217 | .fail(function (err) { 218 | //handle error with err 219 | }); 220 | }) 221 | .fail(function (err) { 222 | //handle error with err 223 | }); 224 | ``` 225 | 226 | **Me() Request** 227 | 228 | The `me()` request is an OAuth.io feature that allows you, when the provider is supported, to retrieve a unified object describing the authenticated user. That can be very useful when you need to login a user via several providers, but don't want to handle a different response each time. 229 | 230 | To use the `me()` feature, do like the following (the example works for Facebook, Github, Twitter and many other providers in this case) : 231 | 232 | ```javascript 233 | //provider can be 'facebook', 'twitter', 'github', or any supported 234 | //provider that contain the fields 'firstname' and 'lastname' 235 | //or an equivalent (e.g. "FirstName" or "first-name") 236 | var provider = 'facebook'; 237 | 238 | OAuth.popup(provider) 239 | .done(function(result) { 240 | result.me() 241 | .done(function (response) { 242 | console.log('Firstname: ', response.firstname); 243 | console.log('Lastname: ', response.lastname); 244 | }) 245 | .fail(function (err) { 246 | //handle error with err 247 | }); 248 | }) 249 | .fail(function (err) { 250 | //handle error with err 251 | }); 252 | ``` 253 | 254 | *Filtering the results* 255 | 256 | You can filter the results of the `me()` method by passing an array of fields you need : 257 | 258 | ```javascript 259 | //... 260 | result.me(['firstname', 'lastname', 'email'/*, ...*/]) 261 | //... 262 | ``` 263 | 264 | 265 | Contributing 266 | ============ 267 | 268 | You are welcome to fork and make pull requests. We will be happy to review them and include them in the code if they bring nice improvements :) 269 | 270 | Testing the SDK 271 | =============== 272 | 273 | To test the SDK, you first need to install the npm modules `jasmine-node` and `istanbul` (to get the tests coverage) : 274 | 275 | ```sh 276 | $ sudo npm install -g jasmine-node@2.0.0 istanbul 277 | ``` 278 | 279 | Then you can run the testsuite from the SDK root directory : 280 | 281 | ```sh 282 | $ jasmine-node --verbose tests/unit/spec 283 | ``` 284 | 285 | Once you've installed `istanbul`, you can run the following command to get coverage information : 286 | 287 | ```sh 288 | $ npm test 289 | ``` 290 | 291 | The coverage report is generated in the `coverage` folder. You can have a nice HTML render of the report in `coverage/lcof-report/index.html` 292 | 293 | License 294 | ======= 295 | 296 | This SDK is published under the Apache2 License. 297 | 298 | 299 | 300 | More information in [oauth.io documentation](http://oauth.io/#/docs) 301 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "oauth-js", 3 | "version": "0.6.2", 4 | "main": "dist/oauth.min.js", 5 | "description": "OAuth that just works", 6 | "license": "Apache-2.0", 7 | "ignore": [ 8 | "coffee", 9 | "coverage", 10 | "js", 11 | "node_modules", 12 | "tests", 13 | "templates", 14 | "Gruntfile.js", 15 | "package.json", 16 | ".gitignore" 17 | ] 18 | } -------------------------------------------------------------------------------- /coffee/config.coffee: -------------------------------------------------------------------------------- 1 | module.exports = 2 | oauthd_url: process.env.oauthd_url 3 | oauthd_api: process.env.api_url 4 | version: process.env.sdk_version 5 | options: {} -------------------------------------------------------------------------------- /coffee/lib/api.coffee: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | module.exports = (OAuthio) -> 4 | $ = OAuthio.getJquery() 5 | apiCall = (type, url, params) => 6 | defer = $.Deferred() 7 | base = OAuthio.getOAuthdURL() 8 | opts = url: base + url, type: type 9 | if type == 'post' or type == 'put' 10 | opts.dataType = "json" 11 | opts.contentType = "application/json" 12 | opts.data = JSON.stringify(params) 13 | else 14 | opts.data = params 15 | $.ajax(opts).then( 16 | ((data) => defer.resolve data), 17 | ((err) => defer.reject err && err.responseJSON) 18 | ) 19 | return defer.promise() 20 | 21 | return { 22 | get: (url, params) => apiCall 'get', url, params 23 | post: (url, params) => apiCall 'post', url, params 24 | put: (url, params) => apiCall 'put', url, params 25 | del: (url, params) => apiCall 'delete', url, params 26 | } 27 | -------------------------------------------------------------------------------- /coffee/lib/core.coffee: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | config = require('../config') 4 | Url = require("../tools/url") 5 | Location = require('../tools/location_operations') 6 | cookies = require("../tools/cookies") 7 | lstorage = require("../tools/lstorage") 8 | cache = require("../tools/cache") 9 | 10 | module.exports = (window, document, jquery, navigator) -> 11 | Url = Url(document) 12 | location_operations = Location document 13 | storage = lstorage.active() && lstorage || cookies 14 | 15 | cookies.init config, document 16 | cache.init storage, config 17 | 18 | OAuthio = 19 | initialize: (public_key, options) -> 20 | config.key = public_key 21 | if options 22 | for i of options 23 | config.options[i] = options[i] 24 | return 25 | 26 | setOAuthdURL: (url) -> 27 | config.oauthd_url = url 28 | config.oauthd_base = Url.getAbsUrl(config.oauthd_url).match(/^.{2,5}:\/\/[^/]+/)[0] 29 | return 30 | 31 | getOAuthdURL: () -> return config.oauthd_url 32 | getVersion: () -> return config.version 33 | 34 | extend: (name, module) -> 35 | @[name] = module @ 36 | 37 | # private 38 | getConfig: () -> return config 39 | getWindow: () -> return window 40 | getDocument: () -> return document 41 | getNavigator: () -> return navigator 42 | getJquery: () -> return jquery 43 | getUrl: () -> return Url 44 | getCache: () -> return cache 45 | getStorage: () -> return storage 46 | getLocationOperations: () -> return location_operations 47 | 48 | return OAuthio 49 | -------------------------------------------------------------------------------- /coffee/lib/oauth.coffee: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | cookies = require("../tools/cookies") 4 | oauthio_requests = require("./request") 5 | sha1 = require("../tools/sha1") 6 | 7 | module.exports = (OAuthio) -> 8 | Url = OAuthio.getUrl() 9 | config = OAuthio.getConfig() 10 | document = OAuthio.getDocument() 11 | window = OAuthio.getWindow() 12 | $ = OAuthio.getJquery() 13 | cache = OAuthio.getCache() 14 | 15 | providers_api = require('./providers') OAuthio 16 | 17 | config.oauthd_base = Url.getAbsUrl(config.oauthd_url).match(/^.{2,5}:\/\/[^/]+/)[0] 18 | 19 | client_states = [] 20 | oauth_result = undefined 21 | 22 | (parse_urlfragment = -> 23 | results = /[\\#&]oauthio=([^&]*)/.exec(document.location.hash) 24 | if results 25 | document.location.hash = document.location.hash.replace(/&?oauthio=[^&]*/, "") 26 | oauth_result = decodeURIComponent(results[1].replace(/\+/g, " ")) 27 | cookie_state = cookies.read("oauthio_state") 28 | if cookie_state 29 | client_states.push cookie_state 30 | cookies.erase "oauthio_state" 31 | return 32 | )() 33 | 34 | location_operations = OAuthio.getLocationOperations() 35 | oauthio = request: oauthio_requests(OAuthio, client_states, providers_api) 36 | 37 | oauth = { 38 | initialize: (public_key, options) -> return OAuthio.initialize public_key, options 39 | setOAuthdURL: (url) -> 40 | config.oauthd_url = url 41 | config.oauthd_base = Url.getAbsUrl(config.oauthd_url).match(/^.{2,5}:\/\/[^/]+/)[0] 42 | return 43 | create: (provider, tokens, request) -> 44 | return cache.tryCache(oauth, provider, true) unless tokens 45 | providers_api.fetchDescription provider if typeof request isnt "object" 46 | make_res = (method) -> 47 | oauthio.request.mkHttp provider, tokens, request, method 48 | 49 | make_res_endpoint = (method, url) -> 50 | oauthio.request.mkHttpEndpoint provider, tokens, request, method, url 51 | 52 | res = {} 53 | for i of tokens 54 | res[i] = tokens[i] 55 | 56 | res.toJson = -> 57 | a = {} 58 | a.access_token = res.access_token if res.access_token? 59 | a.oauth_token = res.oauth_token if res.oauth_token? 60 | a.oauth_token_secret = res.oauth_token_secret if res.oauth_token_secret? 61 | a.expires_in = res.expires_in if res.expires_in? 62 | a.token_type = res.token_type if res.token_type? 63 | a.id_token = res.id_token if res.id_token? 64 | a.provider = res.provider if res.provider? 65 | a.email = res.email if res.email? 66 | return a 67 | 68 | res.get = make_res("GET") 69 | res.post = make_res("POST") 70 | res.put = make_res("PUT") 71 | res.patch = make_res("PATCH") 72 | res.del = make_res("DELETE") 73 | res.me = oauthio.request.mkHttpMe provider, tokens, request, "GET" 74 | 75 | res 76 | 77 | popup: (provider, opts, callback) -> 78 | gotmessage = false 79 | getMessage = (e) -> 80 | if not gotmessage 81 | return if e.origin isnt config.oauthd_base 82 | try 83 | wnd.close() 84 | opts.data = e.data 85 | oauthio.request.sendCallback opts, defer 86 | gotmessage = true 87 | wnd = undefined 88 | frm = undefined 89 | wndTimeout = undefined 90 | defer = $.Deferred() 91 | opts = opts or {} 92 | unless config.key 93 | defer?.reject new Error("OAuth object must be initialized") 94 | if not callback? 95 | return defer.promise() 96 | else 97 | return callback(new Error("OAuth object must be initialized")) 98 | if arguments.length is 2 and typeof opts == 'function' 99 | callback = opts 100 | opts = {} 101 | if cache.cacheEnabled(opts.cache) 102 | res = cache.tryCache(oauth, provider, opts.cache) 103 | if res 104 | defer?.resolve res 105 | if callback 106 | return callback(null, res) 107 | else 108 | return defer.promise() 109 | unless opts.state 110 | opts.state = sha1.create_hash() 111 | opts.state_type = "client" 112 | client_states.push opts.state 113 | url = config.oauthd_url + "/auth/" + provider + "?k=" + config.key 114 | url += "&d=" + encodeURIComponent(Url.getAbsUrl("/")) 115 | url += "&opts=" + encodeURIComponent(JSON.stringify(opts)) if opts 116 | 117 | if opts.wnd_settings 118 | wnd_settings = opts.wnd_settings 119 | delete opts.wnd_settings 120 | else 121 | wnd_settings = 122 | width: Math.floor(window.outerWidth * 0.8) 123 | height: Math.floor(window.outerHeight * 0.5) 124 | wnd_settings.width = 1000 if wnd_settings.width < 1000 125 | wnd_settings.height = 630 if wnd_settings.height < 630 126 | wnd_settings.left = Math.floor(window.screenX + (window.outerWidth - wnd_settings.width) / 2) 127 | wnd_settings.top = Math.floor(window.screenY + (window.outerHeight - wnd_settings.height) / 8) 128 | wnd_options = "width=" + wnd_settings.width + ",height=" + wnd_settings.height 129 | wnd_options += ",toolbar=0,scrollbars=1,status=1,resizable=1,location=1,menuBar=0" 130 | wnd_options += ",left=" + wnd_settings.left + ",top=" + wnd_settings.top 131 | opts = 132 | provider: provider 133 | cache: opts.cache 134 | 135 | opts.callback = (e, r) -> 136 | if window.removeEventListener 137 | window.removeEventListener "message", getMessage, false 138 | else if window.detachEvent 139 | window.detachEvent "onmessage", getMessage 140 | else document.detachEvent "onmessage", getMessage if document.detachEvent 141 | opts.callback = -> 142 | 143 | if wndTimeout 144 | clearTimeout wndTimeout 145 | wndTimeout = `undefined` 146 | (if callback then callback(e, r) else `undefined`) 147 | 148 | if window.attachEvent 149 | window.attachEvent "onmessage", getMessage 150 | else if document.attachEvent 151 | document.attachEvent "onmessage", getMessage 152 | else window.addEventListener "message", getMessage, false if window.addEventListener 153 | if typeof chrome isnt "undefined" and chrome.runtime and chrome.runtime.onMessageExternal 154 | chrome.runtime.onMessageExternal.addListener (request, sender, sendResponse) -> 155 | request.origin = sender.url.match(/^.{2,5}:\/\/[^/]+/)[0] 156 | getMessage request 157 | 158 | if not frm and (navigator.userAgent.indexOf("MSIE") isnt -1 or navigator.appVersion.indexOf("Trident/") > 0) 159 | frm = document.createElement("iframe") 160 | frm.src = config.oauthd_url + "/auth/iframe?d=" + encodeURIComponent(Url.getAbsUrl("/")) 161 | frm.width = 0 162 | frm.height = 0 163 | frm.frameBorder = 0 164 | frm.style.visibility = "hidden" 165 | document.body.appendChild frm 166 | wndTimeout = setTimeout(-> 167 | defer?.reject new Error("Authorization timed out") 168 | if opts.callback and typeof opts.callback == "function" 169 | opts.callback new Error("Authorization timed out") 170 | try 171 | wnd.close() 172 | return 173 | , 1200 * 1000) 174 | 175 | wnd = window.open(url, "Authorization", wnd_options) 176 | if wnd 177 | wnd.focus() 178 | interval = window.setInterval () -> 179 | if wnd == null || wnd.closed 180 | window.clearInterval interval 181 | if not gotmessage 182 | defer?.reject new Error("The popup was closed") 183 | opts.callback new Error("The popup was closed") if opts.callback and typeof opts.callback == "function" 184 | , 500 185 | else 186 | defer?.reject new Error("Could not open a popup") 187 | opts.callback new Error("Could not open a popup") if opts.callback and typeof opts.callback == "function" 188 | return defer?.promise() 189 | 190 | redirect: (provider, opts, url) -> 191 | if arguments.length is 2 192 | url = opts 193 | opts = {} 194 | if typeof url != 'string' 195 | throw new Error 'You must specify an url' 196 | 197 | if cache.cacheEnabled(opts.cache) 198 | res = cache.tryCache(oauth, provider, opts.cache) 199 | if res 200 | url = Url.getAbsUrl(url) + ((if (url.indexOf("#") is -1) then "#" else "&")) + "oauthio=cache:" + provider 201 | location_operations.changeHref url 202 | location_operations.reload() 203 | return 204 | unless opts.state 205 | opts.state = sha1.create_hash() 206 | opts.state_type = "client" 207 | cookies.create "oauthio_state", opts.state 208 | redirect_uri = encodeURIComponent(Url.getAbsUrl(url)) 209 | url = config.oauthd_url + "/auth/" + provider + "?k=" + config.key 210 | url += "&redirect_uri=" + redirect_uri 211 | url += "&opts=" + encodeURIComponent(JSON.stringify(opts)) if opts 212 | location_operations.changeHref url 213 | return 214 | 215 | isRedirect: (provider) -> 216 | if ! oauth_result? 217 | return false 218 | 219 | if oauth_result?.substr(0,6) is "cache:" 220 | cache_provider = oauth_result?.substr(6) 221 | if ! provider 222 | return cache_provider 223 | return cache_provider.toLowerCase() == provider.toLowerCase() 224 | 225 | try 226 | data = JSON.parse(oauth_result) 227 | catch e 228 | return false 229 | 230 | if provider 231 | return data.provider.toLowerCase() is provider.toLowerCase() 232 | return data.provider 233 | 234 | callback: (provider, opts, callback) -> 235 | defer = $.Deferred() 236 | if arguments.length is 1 and typeof provider == "function" 237 | callback = provider 238 | provider = `undefined` 239 | opts = {} 240 | if arguments.length is 1 and typeof provider == "string" 241 | opts = {} 242 | if arguments.length is 2 and typeof opts == "function" 243 | callback = opts 244 | opts = {} 245 | if cache.cacheEnabled(opts?.cache) or oauth_result?.substr(0,6) is "cache:" 246 | if ! provider && oauth_result?.substr(0,6) is "cache:" 247 | provider = oauth_result.substr(6) 248 | res = cache.tryCache(oauth, provider, true) 249 | if res 250 | if callback 251 | return callback(null, res) if res 252 | else 253 | defer?.resolve res 254 | return defer?.promise() 255 | else if oauth_result?.substr(0,6) is "cache:" 256 | err = new Error 'Could not fetch data from cache' 257 | if callback 258 | return callback(err) 259 | else 260 | defer?.reject err 261 | return defer?.promise() 262 | return unless oauth_result 263 | oauthio.request.sendCallback { 264 | data: oauth_result 265 | provider: provider 266 | cache: opts?.cache 267 | expires: opts?.expires 268 | callback: callback }, defer 269 | 270 | return defer?.promise() 271 | 272 | clearCache: (provider) -> 273 | cache.clearCache provider 274 | 275 | http_me: (opts) -> 276 | oauthio.request.http_me opts if oauthio.request.http_me 277 | return 278 | 279 | http: (opts) -> 280 | oauthio.request.http opts if oauthio.request.http 281 | return 282 | getVersion: () -> 283 | OAuthio.getVersion.apply this 284 | } 285 | return oauth 286 | -------------------------------------------------------------------------------- /coffee/lib/providers.coffee: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | config = require("../config") 4 | 5 | module.exports = (OAuthio) -> 6 | $ = OAuthio.getJquery() 7 | 8 | providers_desc = {} 9 | providers_cb = {} 10 | providers_api = 11 | execProvidersCb: (provider, e, r) -> 12 | if providers_cb[provider] 13 | cbs = providers_cb[provider] 14 | delete providers_cb[provider] 15 | 16 | for i of cbs 17 | cbs[i] e, r 18 | return 19 | 20 | fetchDescription: (provider) -> 21 | return if providers_desc[provider] 22 | providers_desc[provider] = true 23 | $.ajax( 24 | url: config.oauthd_api + "/providers/" + provider 25 | data: 26 | extend: true 27 | 28 | dataType: "json" 29 | ).done((data) -> 30 | providers_desc[provider] = data.data 31 | providers_api.execProvidersCb provider, null, data.data 32 | return 33 | ).always -> 34 | if typeof providers_desc[provider] isnt "object" 35 | delete providers_desc[provider] 36 | 37 | providers_api.execProvidersCb provider, new Error("Unable to fetch request description") 38 | return 39 | 40 | return 41 | 42 | getDescription: (provider, opts, callback) -> 43 | opts = opts or {} 44 | return callback(null, providers_desc[provider]) if typeof providers_desc[provider] is "object" 45 | providers_api.fetchDescription provider unless providers_desc[provider] 46 | return callback(null, {}) unless opts.wait 47 | providers_cb[provider] = providers_cb[provider] or [] 48 | providers_cb[provider].push callback 49 | return 50 | 51 | return providers_api 52 | -------------------------------------------------------------------------------- /coffee/lib/request.coffee: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | Url = require('../tools/url')() 4 | 5 | module.exports = (OAuthio, client_states, providers_api) -> 6 | $ = OAuthio.getJquery() 7 | config = OAuthio.getConfig() 8 | cache = OAuthio.getCache() 9 | extended_methods = [] 10 | 11 | fetched_methods = false 12 | retrieveMethods: () -> 13 | defer = $.Deferred() 14 | if not fetched_methods 15 | $.ajax(config.oauthd_url + '/api/extended-endpoints') 16 | .then (data) -> 17 | extended_methods = data.data 18 | fetched_methods = true 19 | defer.resolve() 20 | .fail (e) -> 21 | fetched_methods = true 22 | defer.reject(e) 23 | else 24 | defer.resolve extended_methods 25 | return defer.promise() 26 | 27 | generateMethods: (request_object, tokens, provider) -> 28 | if extended_methods? 29 | for v, k in extended_methods 30 | # v is a method to add 31 | name_array = v.name.split '.' 32 | pt = request_object 33 | for vv, kk in name_array 34 | if kk < name_array.length - 1 35 | if not pt[vv]? 36 | pt[vv] = {} 37 | pt = pt[vv] 38 | else 39 | pt[vv] = @mkHttpAll provider, tokens, v, arguments 40 | 41 | http: (opts) -> 42 | doRequest = -> 43 | request = options.oauthio.request or {} 44 | unless request.cors 45 | options.url = encodeURIComponent(options.url) 46 | options.url = "/" + options.url unless options.url[0] is "/" 47 | options.url = config.oauthd_url + "/request/" + options.oauthio.provider + options.url 48 | options.headers = options.headers or {} 49 | options.headers.oauthio = "k=" + config.key 50 | options.headers.oauthio += "&oauthv=1" if options.oauthio.tokens.oauth_token and options.oauthio.tokens.oauth_token_secret # make sure to use oauth 1 51 | for k of options.oauthio.tokens 52 | options.headers.oauthio += "&" + encodeURIComponent(k) + "=" + encodeURIComponent(options.oauthio.tokens[k]) 53 | delete options.oauthio 54 | 55 | return $.ajax(options) 56 | if options.oauthio.tokens 57 | #Fetching the url if a common endpoint is called 58 | options.oauthio.tokens.token = options.oauthio.tokens.access_token if options.oauthio.tokens.access_token 59 | unless options.url.match(/^[a-z]{2,16}:\/\//) 60 | options.url = "/" + options.url if options.url[0] isnt "/" 61 | options.url = request.url + options.url 62 | options.url = Url.replaceParam(options.url, options.oauthio.tokens, request.parameters) 63 | if request.query 64 | qs = [] 65 | for i of request.query 66 | qs.push encodeURIComponent(i) + "=" + encodeURIComponent(Url.replaceParam(request.query[i], options.oauthio.tokens, request.parameters)) 67 | if "?" in options.url 68 | options.url += "&" + qs 69 | else 70 | options.url += "?" + qs 71 | if request.headers 72 | options.headers = options.headers or {} 73 | for i of request.headers 74 | options.headers[i] = Url.replaceParam(request.headers[i], options.oauthio.tokens, request.parameters) 75 | delete options.oauthio 76 | $.ajax options 77 | options = {} 78 | i = undefined 79 | for i of opts 80 | options[i] = opts[i] 81 | if not options.oauthio.request or options.oauthio.request is true 82 | desc_opts = wait: !!options.oauthio.request 83 | defer = $.Deferred() 84 | providers_api.getDescription options.oauthio.provider, desc_opts, (e, desc) -> 85 | return defer.reject(e) if e 86 | if options.oauthio.tokens.oauth_token and options.oauthio.tokens.oauth_token_secret 87 | options.oauthio.request = desc.oauth1 and desc.oauth1.request 88 | else 89 | options.oauthio.request = desc.oauth2 and desc.oauth2.request 90 | defer.resolve() 91 | return 92 | 93 | return defer.then(doRequest) 94 | else 95 | return doRequest() 96 | return 97 | 98 | http_me: (opts) -> 99 | doRequest = -> 100 | defer = $.Deferred() 101 | request = options.oauthio.request or {} 102 | options.url = config.oauthd_url + "/auth/" + options.oauthio.provider + "/me" 103 | options.headers = options.headers or {} 104 | options.headers.oauthio = "k=" + config.key 105 | options.headers.oauthio += "&oauthv=1" if options.oauthio.tokens.oauth_token and options.oauthio.tokens.oauth_token_secret # make sure to use oauth 1 106 | for k of options.oauthio.tokens 107 | options.headers.oauthio += "&" + encodeURIComponent(k) + "=" + encodeURIComponent(options.oauthio.tokens[k]) 108 | delete options.oauthio 109 | 110 | promise = $.ajax(options) 111 | $.when(promise).done((data) -> 112 | defer.resolve data.data 113 | return 114 | ).fail (data) -> 115 | if data.responseJSON 116 | defer.reject data.responseJSON.data 117 | else 118 | defer.reject new Error("An error occured while trying to access the resource") 119 | return 120 | 121 | defer.promise() 122 | options = {} 123 | for k of opts 124 | options[k] = opts[k] 125 | if not options.oauthio.request or options.oauthio.request is true 126 | desc_opts = wait: !!options.oauthio.request 127 | defer = $.Deferred() 128 | providers_api.getDescription options.oauthio.provider, desc_opts, (e, desc) -> 129 | return defer.reject(e) if e 130 | if options.oauthio.tokens.oauth_token and options.oauthio.tokens.oauth_token_secret 131 | options.oauthio.request = desc.oauth1 and desc.oauth1.request 132 | else 133 | options.oauthio.request = desc.oauth2 and desc.oauth2.request 134 | defer.resolve() 135 | return 136 | 137 | return defer.then(doRequest) 138 | else 139 | return doRequest() 140 | return 141 | 142 | http_all: (options, endpoint_descriptor, parameters) -> 143 | doRequest = -> 144 | defer = $.Deferred() 145 | request = options.oauthio.request or {} 146 | options.headers = options.headers or {} 147 | options.headers.oauthio = "k=" + config.key 148 | options.headers.oauthio += "&oauthv=1" if options.oauthio.tokens.oauth_token and options.oauthio.tokens.oauth_token_secret # make sure to use oauth 1 149 | for k of options.oauthio.tokens 150 | options.headers.oauthio += "&" + encodeURIComponent(k) + "=" + encodeURIComponent(options.oauthio.tokens[k]) 151 | delete options.oauthio 152 | 153 | 154 | promise = $.ajax(options) 155 | $.when(promise).done((data) -> 156 | 157 | if typeof data.data == 'string' 158 | try 159 | data.data = JSON.parse data.data 160 | catch error 161 | data.data = data.data 162 | finally 163 | defer.resolve data.data 164 | return 165 | ).fail (data) -> 166 | if data.responseJSON 167 | defer.reject data.responseJSON.data 168 | else 169 | defer.reject new Error("An error occured while trying to access the resource") 170 | return 171 | 172 | defer.promise() 173 | 174 | return doRequest() 175 | 176 | mkHttp: (provider, tokens, request, method) -> 177 | base = this 178 | (opts, opts2) -> 179 | options = {} 180 | if typeof opts is "string" 181 | if typeof opts2 is "object" 182 | for i of opts2 183 | options[i] = opts2[i] 184 | options.url = opts 185 | else if typeof opts is "object" 186 | for i of opts 187 | options[i] = opts[i] 188 | options.type = options.type or method 189 | options.oauthio = 190 | provider: provider 191 | tokens: tokens 192 | request: request 193 | 194 | base.http options 195 | 196 | mkHttpMe: (provider, tokens, request, method) -> 197 | base = this 198 | (filter) -> 199 | options = {} 200 | options.type = options.type or method 201 | options.oauthio = 202 | provider: provider 203 | tokens: tokens 204 | request: request 205 | 206 | options.data = options.data or {} 207 | options.data.filter = filter.join(",") if filter 208 | base.http_me options 209 | 210 | mkHttpAll: (provider, tokens, endpoint_descriptor) -> 211 | base = this 212 | () -> 213 | options = {} 214 | options.type = endpoint_descriptor.method 215 | options.url = config.oauthd_url + endpoint_descriptor.endpoint.replace ':provider', provider 216 | options.oauthio = 217 | provider: provider 218 | tokens: tokens 219 | options.data = {} 220 | for k, v of arguments 221 | th_param = endpoint_descriptor.params[k] 222 | if th_param? 223 | options.data[th_param.name] = v 224 | 225 | options.data = options.data or {} 226 | base.http_all options, endpoint_descriptor, arguments 227 | 228 | sendCallback: (opts, defer) -> 229 | base = this 230 | data = undefined 231 | err = undefined 232 | try 233 | data = JSON.parse(opts.data) 234 | catch e 235 | defer.reject new Error("Error while parsing result") 236 | return opts.callback(new Error("Error while parsing result")) 237 | return if not data or not data.provider 238 | if opts.provider and data.provider.toLowerCase() isnt opts.provider.toLowerCase() 239 | err = new Error("Returned provider name does not match asked provider") 240 | defer.reject err 241 | if opts.callback and typeof opts.callback == "function" 242 | return opts.callback(err) 243 | else 244 | return 245 | if data.status is "error" or data.status is "fail" 246 | err = new Error(data.message) 247 | err.body = data.data 248 | defer.reject err 249 | if opts.callback and typeof opts.callback == "function" 250 | return opts.callback(err) 251 | else 252 | return 253 | if data.status isnt "success" or not data.data 254 | err = new Error() 255 | err.body = data.data 256 | defer.reject err 257 | if opts.callback and typeof opts.callback == "function" 258 | return opts.callback(err) 259 | else 260 | return 261 | 262 | #checking if state is known 263 | data.state = data.state.replace(/\s+/g,"") 264 | i = 0 265 | while i < client_states.length 266 | client_states[i] = client_states[i].replace(/\s+/g,"") 267 | i++ 268 | 269 | if not data.state or client_states.indexOf(data.state) == -1 270 | defer.reject new Error("State is not matching") 271 | if opts.callback and typeof opts.callback == "function" 272 | return opts.callback(new Error("State is not matching")) 273 | else 274 | return 275 | data.data.provider = data.provider unless opts.provider 276 | res = data.data 277 | res.provider = data.provider.toLowerCase() 278 | if cache.cacheEnabled(opts.cache) and res 279 | if opts.expires && ! res.expires_in 280 | res.expires_in = opts.expires 281 | cache.storeCache data.provider, res 282 | request = res.request 283 | delete res.request 284 | 285 | tokens = undefined 286 | if res.access_token 287 | tokens = access_token: res.access_token 288 | else if res.oauth_token and res.oauth_token_secret 289 | tokens = 290 | oauth_token: res.oauth_token 291 | oauth_token_secret: res.oauth_token_secret 292 | unless request 293 | defer.resolve res 294 | if opts.callback and typeof opts.callback == "function" 295 | return opts.callback(null, res) 296 | else 297 | return 298 | if request.required 299 | for i of request.required 300 | tokens[request.required[i]] = res[request.required[i]] 301 | make_res = (method) -> 302 | base.mkHttp data.provider, tokens, request, method 303 | 304 | res.toJson = -> 305 | a = {} 306 | a.access_token = res.access_token if res.access_token? 307 | a.oauth_token = res.oauth_token if res.oauth_token? 308 | a.oauth_token_secret = res.oauth_token_secret if res.oauth_token_secret? 309 | a.expires_in = res.expires_in if res.expires_in? 310 | a.token_type = res.token_type if res.token_type? 311 | a.id_token = res.id_token if res.id_token? 312 | a.provider = res.provider if res.provider? 313 | a.email = res.email if res.email? 314 | return a 315 | 316 | res.get = make_res("GET") 317 | res.post = make_res("POST") 318 | res.put = make_res("PUT") 319 | res.patch = make_res("PATCH") 320 | res.del = make_res("DELETE") 321 | res.me = base.mkHttpMe(data.provider, tokens, request, "GET") 322 | 323 | @retrieveMethods() 324 | .then () => 325 | @generateMethods res, tokens, data.provider 326 | defer.resolve res 327 | if opts.callback and typeof opts.callback == "function" 328 | opts.callback null, res 329 | else 330 | return 331 | .fail (e) => 332 | console.log 'Could not retrieve methods', e 333 | defer.resolve res 334 | if opts.callback and typeof opts.callback == "function" 335 | opts.callback null, res 336 | else 337 | return 338 | -------------------------------------------------------------------------------- /coffee/lib/user.coffee: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | module.exports = (OAuthio) -> 4 | $ = OAuthio.getJquery() 5 | config = OAuthio.getConfig() 6 | storage = OAuthio.getStorage() 7 | 8 | lastSave = null 9 | 10 | class UserObject 11 | constructor: (data) -> 12 | @token = data.token 13 | @data = data.user 14 | @providers = data.providers 15 | lastSave = @getEditableData() 16 | 17 | getEditableData: () -> 18 | data = [] 19 | for key of @data 20 | if ['id', 'email'].indexOf(key) == -1 21 | data.push 22 | key: key 23 | value: @data[key] 24 | return data 25 | 26 | save: () -> 27 | #call to save on stormpath 28 | 29 | dataToSave = {} 30 | for d in lastSave 31 | dataToSave[d.key] = @data[d.key] if @data[d.key] != d.value 32 | delete @data[d.key] if @data[d.key] == null 33 | keyIsInLastSave = (key) -> 34 | for o in lastSave 35 | return true if o.key == key 36 | return false 37 | 38 | for d in @getEditableData() 39 | if !keyIsInLastSave d.key 40 | dataToSave[d.key] = @data[d.key] 41 | @saveLocal() 42 | return OAuthio.API.put '/api/usermanagement/user?k=' + config.key + '&token=' + @token, dataToSave 43 | 44 | ## todo select(provider) 45 | select: (provider) -> 46 | OAuthResult = null 47 | return OAuthResult 48 | 49 | saveLocal: () -> 50 | copy = token: @token, user: @data, providers: @providers 51 | storage.erase 'oio_auth' 52 | storage.create 'oio_auth', JSON.stringify(copy), 21600 53 | 54 | hasProvider: (provider) -> 55 | return !! @providers and @providers.indexOf(provider) != -1 56 | 57 | getProviders: () -> 58 | defer = $.Deferred() 59 | OAuthio.API.get '/api/usermanagement/user/providers?k=' + config.key + '&token=' + @token 60 | .done (providers) => 61 | @providers = providers.data 62 | @saveLocal() 63 | defer.resolve @providers 64 | .fail (err) -> 65 | defer.reject err 66 | return defer.promise() 67 | 68 | addProvider: (oauthRes) -> 69 | defer = $.Deferred() 70 | oauthRes = oauthRes.toJson() if typeof oauthRes.toJson == 'function' 71 | oauthRes.email = @data.email 72 | @providers = [] if not @providers 73 | @providers.push oauthRes.provider 74 | OAuthio.API.post '/api/usermanagement/user/providers?k=' + config.key + '&token=' + @token, oauthRes 75 | .done (res) => 76 | @data = res.data 77 | @saveLocal() 78 | defer.resolve() 79 | .fail (err) => 80 | @providers.splice @providers.indexOf(oauthRes.provider), 1 81 | defer.reject err 82 | return defer.promise() 83 | 84 | removeProvider: (provider) -> 85 | defer = $.Deferred() 86 | @providers.splice @providers.indexOf(provider), 1 87 | OAuthio.API.del '/api/usermanagement/user/providers/' + provider + '?k=' + config.key + '&token=' + @token 88 | .done (res) => 89 | @saveLocal() 90 | defer.resolve res 91 | .fail (err) => 92 | @providers.push provider 93 | defer.reject err 94 | return defer.promise() 95 | 96 | # todo - not working 97 | changePassword: (oldPassword, newPassword) -> 98 | return OAuthio.API.post '/api/usermanagement/user/password?k=' + config.key + '&token=' + @token, 99 | password: newPassword 100 | #oldPassword ? 101 | 102 | #### 0.5.0 => remove this method 103 | isLoggued: () -> 104 | return OAuthio.User.isLogged() 105 | ########### 106 | 107 | isLogged: () -> 108 | return OAuthio.User.isLogged() 109 | 110 | logout: () -> 111 | defer = $.Deferred() 112 | storage.erase 'oio_auth' 113 | OAuthio.API.post('/api/usermanagement/user/logout?k=' + config.key + '&token=' + @token) 114 | .done -> 115 | defer.resolve() 116 | .fail (err)-> 117 | defer.reject err 118 | 119 | return defer.promise() 120 | return { 121 | initialize: (public_key, options) -> return OAuthio.initialize public_key, options 122 | setOAuthdURL: (url) -> return OAuthio.setOAuthdURL url 123 | signup: (data) -> 124 | defer = $.Deferred() 125 | data = data.toJson() if typeof data.toJson == 'function' 126 | OAuthio.API.post '/api/usermanagement/signup?k=' + config.key, data 127 | .done (res) -> 128 | storage.create 'oio_auth', JSON.stringify(res.data), res.data.expires_in || 21600 129 | defer.resolve new UserObject(res.data) 130 | .fail (err) -> 131 | defer.reject err 132 | 133 | return defer.promise() 134 | 135 | signin: (email, password) -> 136 | defer = $.Deferred() 137 | if typeof email != "string" and not password 138 | # signin(OAuthRes) 139 | signinData = email 140 | signinData = signinData.toJson() if typeof signinData.toJson == 'function' 141 | OAuthio.API.post '/api/usermanagement/signin?k=' + config.key, signinData 142 | .done (res) -> 143 | storage.create 'oio_auth', JSON.stringify(res.data), res.data.expires_in || 21600 144 | defer.resolve new UserObject(res.data) 145 | .fail (err) -> 146 | defer.reject err 147 | else 148 | # signin(email, password) 149 | OAuthio.API.post('/api/usermanagement/signin?k=' + config.key, 150 | email: email 151 | password: password 152 | ).done((res) -> 153 | storage.create 'oio_auth', JSON.stringify(res.data), res.data.expires_in || 21600 154 | defer.resolve new UserObject(res.data) 155 | ).fail (err) -> 156 | defer.reject err 157 | return defer.promise() 158 | 159 | confirmResetPassword: (newPassword, sptoken) -> 160 | return OAuthio.API.post '/api/usermanagement/user/password?k=' + config.key, 161 | password: newPassword 162 | token: sptoken 163 | 164 | resetPassword: (email, callback) -> 165 | OAuthio.API.post '/api/usermanagement/user/password/reset?k=' + config.key, email: email 166 | 167 | refreshIdentity: () -> 168 | defer = $.Deferred() 169 | OAuthio.API.get('/api/usermanagement/user?k=' + config.key + '&token=' + JSON.parse(storage.read('oio_auth')).token) 170 | .done (res) -> 171 | defer.resolve new UserObject(res.data) 172 | .fail (err) -> 173 | defer.reject err 174 | return defer.promise() 175 | 176 | getIdentity: () -> 177 | user = storage.read 'oio_auth' 178 | return null if not user 179 | return new UserObject(JSON.parse(user)) 180 | 181 | isLogged: () -> 182 | a = storage.read 'oio_auth' 183 | return true if a 184 | return false 185 | } 186 | -------------------------------------------------------------------------------- /coffee/main.coffee: -------------------------------------------------------------------------------- 1 | do -> 2 | jquery = require('./tools/jquery-lite.js') 3 | 4 | OAuthio = require('./lib/core') window, document, jquery, navigator 5 | OAuthio.extend 'OAuth', require('./lib/oauth') 6 | OAuthio.extend 'API', require('./lib/api') 7 | OAuthio.extend 'User', require('./lib/user') 8 | 9 | if angular? 10 | angular.module 'oauthio', [] 11 | .factory 'OAuth', [() -> 12 | return OAuthio.OAuth 13 | ] 14 | .factory 'User', [() -> 15 | return OAuthio.User 16 | ] 17 | 18 | exports.OAuthio = OAuthio 19 | window.User = exports.User = exports.OAuthio.User 20 | window.OAuth = exports.OAuth = exports.OAuthio.OAuth 21 | 22 | if (typeof define == 'function' && define.amd) 23 | define -> exports 24 | if (module?.exports) 25 | module.exports = exports 26 | 27 | return exports -------------------------------------------------------------------------------- /coffee/tools/cache.coffee: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | module.exports = 4 | init: (storage, config) -> 5 | @config = config 6 | @storage = storage 7 | tryCache: (OAuth, provider, cache) -> 8 | if @cacheEnabled(cache) 9 | cache = @storage.read("oauthio_provider_" + provider) 10 | return false unless cache 11 | cache = decodeURIComponent(cache) 12 | if typeof cache is "string" 13 | try cache = JSON.parse(cache) 14 | catch e 15 | return false 16 | if typeof cache is "object" 17 | res = {} 18 | for i of cache 19 | res[i] = cache[i] if i isnt "request" and typeof cache[i] isnt "function" 20 | return OAuth.create(provider, res, cache.request) 21 | false 22 | 23 | storeCache: (provider, cache) -> 24 | expires = 3600 25 | if cache.expires_in 26 | expires = cache.expires_in 27 | else if @config.options.expires || @config.options.expires == false 28 | expires = @config.options.expires 29 | 30 | @storage.create "oauthio_provider_" + provider, encodeURIComponent(JSON.stringify(cache)), expires 31 | return 32 | 33 | cacheEnabled: (cache) -> 34 | return @config.options.cache if typeof cache is "undefined" 35 | cache 36 | 37 | clearCache: (provider) -> 38 | if provider 39 | @storage.erase "oauthio_provider_" + provider 40 | else 41 | @storage.eraseFrom "oauthio_provider_" 42 | return 43 | -------------------------------------------------------------------------------- /coffee/tools/cookies.coffee: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | module.exports = 4 | init: (config, document) -> 5 | @config = config 6 | @document = document 7 | 8 | create: (name, value, expires) -> 9 | @erase name 10 | date = new Date() 11 | if expires 12 | date.setTime date.getTime() + (expires or 1200) * 1000 # def: 20 mins 13 | else 14 | date.setFullYear date.getFullYear() + 3 15 | expires = "; expires=" + date.toGMTString() 16 | @document.cookie = name + "=" + value + expires + "; path=/" 17 | return 18 | 19 | read: (name) -> 20 | nameEQ = name + "=" 21 | ca = @document.cookie.split(";") 22 | i = 0 23 | 24 | while i < ca.length 25 | c = ca[i] 26 | c = c.substring(1, c.length) while c.charAt(0) is " " 27 | return c.substring(nameEQ.length, c.length) if c.indexOf(nameEQ) is 0 28 | i++ 29 | null 30 | 31 | erase: (name) -> 32 | date = new Date() 33 | date.setTime date.getTime() - 86400000 34 | @document.cookie = name + "=; expires=" + date.toGMTString() + "; path=/" 35 | return 36 | 37 | eraseFrom: (prefix) -> 38 | cookies = @document.cookie.split(";") 39 | for cookie in cookies 40 | cname = cookie.split("=")[0].trim() 41 | if cname.substr(0, prefix.length) == prefix 42 | @erase(cname) 43 | return -------------------------------------------------------------------------------- /coffee/tools/location_operations.coffee: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | module.exports = (document) -> 4 | return { 5 | reload: -> 6 | document.location.reload() 7 | getHash: -> 8 | return document.location.hash 9 | setHash: (newHash) -> 10 | document.location.hash = newHash 11 | changeHref: (newLocation) -> 12 | document.location.href = newLocation 13 | } -------------------------------------------------------------------------------- /coffee/tools/lstorage.coffee: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | useCache = (callback) -> 4 | cacheobj = localStorage.getItem('oauthio_cache') 5 | if cacheobj 6 | cacheobj = JSON.parse(cacheobj) 7 | else 8 | cacheobj = {} 9 | return callback cacheobj, -> 10 | localStorage.setItem('oauthio_cache', JSON.stringify(cacheobj)) 11 | 12 | 13 | module.exports = 14 | init: (config, document) -> 15 | @config = config 16 | @document = document 17 | 18 | active: -> localStorage? 19 | 20 | create: (name, value, expires) -> 21 | @erase name 22 | date = new Date() 23 | localStorage.setItem(name, value) 24 | useCache (cacheobj, cacheupdate) -> 25 | cacheobj[name] = if expires then date.getTime() + (expires or 1200) * 1000 else false 26 | cacheupdate() 27 | return 28 | 29 | read: (name) -> 30 | return useCache (cacheobj, cacheupdate) -> 31 | if ! cacheobj[name]? 32 | return null 33 | if cacheobj[name] == false 34 | return localStorage.getItem(name) 35 | else if (new Date()).getTime() > cacheobj[name] 36 | localStorage.removeItem(name) 37 | delete cacheobj[name] 38 | cacheupdate() 39 | return null 40 | else 41 | return localStorage.getItem(name) 42 | 43 | erase: (name) -> 44 | useCache (cacheobj, cacheupdate) -> 45 | localStorage.removeItem(name) 46 | delete cacheobj[name] 47 | cacheupdate() 48 | 49 | eraseFrom: (prefix) -> 50 | useCache (cacheobj, cacheupdate) -> 51 | cachenames = Object.keys(cacheobj) 52 | for name in cachenames 53 | if name.substr(0, prefix.length) == prefix 54 | localStorage.removeItem(name) 55 | delete cacheobj[name] 56 | cacheupdate() 57 | return 58 | -------------------------------------------------------------------------------- /coffee/tools/sha1.coffee: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # * A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined 4 | # * in FIPS 180-1 5 | # * Version 2.2 Copyright Paul Johnston 2000 - 2009. 6 | # * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet 7 | # * Distributed under the BSD License 8 | # * See http://pajhome.org.uk/crypt/md5 for details. 9 | # 10 | 11 | # 12 | # * Configurable variables. You may need to tweak these to be compatible with 13 | # * the server-side, but the defaults work in most cases. 14 | # 15 | hexcase = 0 # hex output format. 0 - lowercase; 1 - uppercase 16 | b64pad = "" # base-64 pad character. "=" for strict RFC compliance 17 | 18 | # 19 | # * These are the functions you'll usually want to call 20 | # * They take string arguments and return either hex or base-64 encoded strings 21 | # 22 | ### istanbul ignore next ### 23 | module.exports = 24 | hex_sha1: (s) -> 25 | @rstr2hex @rstr_sha1(@str2rstr_utf8(s)) 26 | 27 | b64_sha1: (s) -> 28 | @rstr2b64 @rstr_sha1(@str2rstr_utf8(s)) 29 | 30 | any_sha1: (s, e) -> 31 | @rstr2any @rstr_sha1(@str2rstr_utf8(s)), e 32 | 33 | hex_hmac_sha1: (k, d) -> 34 | @rstr2hex @rstr_hmac_sha1(@str2rstr_utf8(k), @str2rstr_utf8(d)) 35 | 36 | b64_hmac_sha1: (k, d) -> 37 | @rstr2b64 @rstr_hmac_sha1(@str2rstr_utf8(k), @str2rstr_utf8(d)) 38 | 39 | any_hmac_sha1: (k, d, e) -> 40 | @rstr2any @rstr_hmac_sha1(@str2rstr_utf8(k), @str2rstr_utf8(d)), e 41 | 42 | 43 | # 44 | # * Perform a simple self-test to see if the VM is working 45 | # 46 | sha1_vm_test: -> 47 | thishex_sha1("abc").toLowerCase() is "a9993e364706816aba3e25717850c26c9cd0d89d" 48 | 49 | 50 | # 51 | # * Calculate the SHA1 of a raw string 52 | # 53 | rstr_sha1: (s) -> 54 | @binb2rstr @binb_sha1(@rstr2binb(s), s.length * 8) 55 | 56 | 57 | # 58 | # * Calculate the HMAC-SHA1 of a key and some data (raw strings) 59 | # 60 | rstr_hmac_sha1: (key, data) -> 61 | bkey = @rstr2binb(key) 62 | bkey = @binb_sha1(bkey, key.length * 8) if bkey.length > 16 63 | ipad = Array(16) 64 | opad = Array(16) 65 | i = 0 66 | 67 | while i < 16 68 | ipad[i] = bkey[i] ^ 0x36363636 69 | opad[i] = bkey[i] ^ 0x5C5C5C5C 70 | i++ 71 | hash = @binb_sha1(ipad.concat(@rstr2binb(data)), 512 + data.length * 8) 72 | @binb2rstr @binb_sha1(opad.concat(hash), 512 + 160) 73 | 74 | 75 | # 76 | # * Convert a raw string to a hex string 77 | # 78 | rstr2hex: (input) -> 79 | try 80 | hexcase 81 | catch e 82 | hexcase = 0 83 | hex_tab = (if hexcase then "0123456789ABCDEF" else "0123456789abcdef") 84 | output = "" 85 | x = undefined 86 | i = 0 87 | 88 | while i < input.length 89 | x = input.charCodeAt(i) 90 | output += hex_tab.charAt((x >>> 4) & 0x0F) + hex_tab.charAt(x & 0x0F) 91 | i++ 92 | output 93 | 94 | 95 | # 96 | # * Convert a raw string to a base-64 string 97 | # 98 | rstr2b64: (input) -> 99 | try 100 | b64pad 101 | catch e 102 | b64pad = "" 103 | tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" 104 | output = "" 105 | len = input.length 106 | i = 0 107 | 108 | while i < len 109 | triplet = (input.charCodeAt(i) << 16) | ((if i + 1 < len then input.charCodeAt(i + 1) << 8 else 0)) | ((if i + 2 < len then input.charCodeAt(i + 2) else 0)) 110 | j = 0 111 | 112 | while j < 4 113 | if i * 8 + j * 6 > input.length * 8 114 | output += b64pad 115 | else 116 | output += tab.charAt((triplet >>> 6 * (3 - j)) & 0x3F) 117 | j++ 118 | i += 3 119 | output 120 | 121 | 122 | # 123 | # * Convert a raw string to an arbitrary string encoding 124 | # 125 | rstr2any: (input, encoding) -> 126 | divisor = encoding.length 127 | remainders = Array() 128 | i = undefined 129 | q = undefined 130 | x = undefined 131 | quotient = undefined 132 | 133 | # Convert to an array of 16-bit big-endian values, forming the dividend 134 | dividend = Array(Math.ceil(input.length / 2)) 135 | i = 0 136 | while i < dividend.length 137 | dividend[i] = (input.charCodeAt(i * 2) << 8) | input.charCodeAt(i * 2 + 1) 138 | i++ 139 | 140 | # 141 | # * Repeatedly perform a long division. The binary array forms the dividend, 142 | # * the length of the encoding is the divisor. Once computed, the quotient 143 | # * forms the dividend for the next step. We stop when the dividend is zero. 144 | # * All remainders are stored for later use. 145 | # 146 | while dividend.length > 0 147 | quotient = Array() 148 | x = 0 149 | i = 0 150 | while i < dividend.length 151 | x = (x << 16) + dividend[i] 152 | q = Math.floor(x / divisor) 153 | x -= q * divisor 154 | quotient[quotient.length] = q if quotient.length > 0 or q > 0 155 | i++ 156 | remainders[remainders.length] = x 157 | dividend = quotient 158 | 159 | # Convert the remainders to the output string 160 | output = "" 161 | i = remainders.length - 1 162 | while i >= 0 163 | output += encoding.charAt(remainders[i]) 164 | i-- 165 | 166 | # Append leading zero equivalents 167 | full_length = Math.ceil(input.length * 8 / (Math.log(encoding.length) / Math.log(2))) 168 | i = output.length 169 | while i < full_length 170 | output = encoding[0] + output 171 | i++ 172 | output 173 | 174 | 175 | # 176 | # * Encode a string as utf-8. 177 | # * For efficiency, this assumes the input is valid utf-16. 178 | # 179 | str2rstr_utf8: (input) -> 180 | output = "" 181 | i = -1 182 | x = undefined 183 | y = undefined 184 | while ++i < input.length 185 | 186 | # Decode utf-16 surrogate pairs 187 | x = input.charCodeAt(i) 188 | y = (if i + 1 < input.length then input.charCodeAt(i + 1) else 0) 189 | if 0xD800 <= x and x <= 0xDBFF and 0xDC00 <= y and y <= 0xDFFF 190 | x = 0x10000 + ((x & 0x03FF) << 10) + (y & 0x03FF) 191 | i++ 192 | 193 | # Encode output as utf-8 194 | if x <= 0x7F 195 | output += String.fromCharCode(x) 196 | else if x <= 0x7FF 197 | output += String.fromCharCode(0xC0 | ((x >>> 6) & 0x1F), 0x80 | (x & 0x3F)) 198 | else if x <= 0xFFFF 199 | output += String.fromCharCode(0xE0 | ((x >>> 12) & 0x0F), 0x80 | ((x >>> 6) & 0x3F), 0x80 | (x & 0x3F)) 200 | else output += String.fromCharCode(0xF0 | ((x >>> 18) & 0x07), 0x80 | ((x >>> 12) & 0x3F), 0x80 | ((x >>> 6) & 0x3F), 0x80 | (x & 0x3F)) if x <= 0x1FFFFF 201 | output 202 | 203 | 204 | # 205 | # * Encode a string as utf-16 206 | # 207 | str2rstr_utf16le: (input) -> 208 | output = "" 209 | i = 0 210 | 211 | while i < input.length 212 | output += String.fromCharCode(input.charCodeAt(i) & 0xFF, (input.charCodeAt(i) >>> 8) & 0xFF) 213 | i++ 214 | output 215 | 216 | str2rstr_utf16be: (input) -> 217 | output = "" 218 | i = 0 219 | 220 | while i < input.length 221 | output += String.fromCharCode((input.charCodeAt(i) >>> 8) & 0xFF, input.charCodeAt(i) & 0xFF) 222 | i++ 223 | output 224 | 225 | 226 | # 227 | # * Convert a raw string to an array of big-endian words 228 | # * Characters >255 have their high-byte silently ignored. 229 | # 230 | rstr2binb: (input) -> 231 | output = Array(input.length >> 2) 232 | i = 0 233 | 234 | while i < output.length 235 | output[i] = 0 236 | i++ 237 | i = 0 238 | 239 | while i < input.length * 8 240 | output[i >> 5] |= (input.charCodeAt(i / 8) & 0xFF) << (24 - i % 32) 241 | i += 8 242 | output 243 | 244 | 245 | # 246 | # * Convert an array of big-endian words to a string 247 | # 248 | binb2rstr: (input) -> 249 | output = "" 250 | i = 0 251 | 252 | while i < input.length * 32 253 | output += String.fromCharCode((input[i >> 5] >>> (24 - i % 32)) & 0xFF) 254 | i += 8 255 | output 256 | 257 | 258 | # 259 | # * Calculate the SHA-1 of an array of big-endian words, and a bit length 260 | # 261 | binb_sha1: (x, len) -> 262 | 263 | # append padding 264 | x[len >> 5] |= 0x80 << (24 - len % 32) 265 | x[((len + 64 >> 9) << 4) + 15] = len 266 | w = Array(80) 267 | a = 1732584193 268 | b = -271733879 269 | c = -1732584194 270 | d = 271733878 271 | e = -1009589776 272 | i = 0 273 | 274 | while i < x.length 275 | olda = a 276 | oldb = b 277 | oldc = c 278 | oldd = d 279 | olde = e 280 | j = 0 281 | 282 | while j < 80 283 | if j < 16 284 | w[j] = x[i + j] 285 | else 286 | w[j] = @bit_rol(w[j - 3] ^ w[j - 8] ^ w[j - 14] ^ w[j - 16], 1) 287 | t = @safe_add(@safe_add(@bit_rol(a, 5), @sha1_ft(j, b, c, d)), @safe_add(@safe_add(e, w[j]), @sha1_kt(j))) 288 | e = d 289 | d = c 290 | c = @bit_rol(b, 30) 291 | b = a 292 | a = t 293 | j++ 294 | a = @safe_add(a, olda) 295 | b = @safe_add(b, oldb) 296 | c = @safe_add(c, oldc) 297 | d = @safe_add(d, oldd) 298 | e = @safe_add(e, olde) 299 | i += 16 300 | Array a, b, c, d, e 301 | 302 | 303 | # 304 | # * Perform the appropriate triplet combination function for the current 305 | # * iteration 306 | # 307 | sha1_ft: (t, b, c, d) -> 308 | return (b & c) | ((~b) & d) if t < 20 309 | return b ^ c ^ d if t < 40 310 | return (b & c) | (b & d) | (c & d) if t < 60 311 | b ^ c ^ d 312 | 313 | 314 | # 315 | # * Determine the appropriate additive constant for the current iteration 316 | # 317 | sha1_kt: (t) -> 318 | (if (t < 20) then 1518500249 else (if (t < 40) then 1859775393 else (if (t < 60) then -1894007588 else -899497514))) 319 | 320 | 321 | # 322 | # * Add integers, wrapping at 2^32. This uses 16-bit operations internally 323 | # * to work around bugs in some JS interpreters. 324 | # 325 | safe_add: (x, y) -> 326 | lsw = (x & 0xFFFF) + (y & 0xFFFF) 327 | msw = (x >> 16) + (y >> 16) + (lsw >> 16) 328 | (msw << 16) | (lsw & 0xFFFF) 329 | 330 | 331 | # 332 | # * Bitwise rotate a 32-bit number to the left. 333 | # 334 | bit_rol: (num, cnt) -> 335 | (num << cnt) | (num >>> (32 - cnt)) 336 | 337 | create_hash: -> 338 | hash = @b64_sha1((new Date()).getTime() + ":" + Math.floor(Math.random() * 9999999)) 339 | hash.replace(/\+/g, "-").replace(/\//g, "_").replace /\=+$/, "" 340 | -------------------------------------------------------------------------------- /coffee/tools/url.coffee: -------------------------------------------------------------------------------- 1 | module.exports = (document) -> 2 | getAbsUrl: (url) -> 3 | return url if url.match(/^.{2,5}:\/\//) 4 | return document.location.protocol + "//" + document.location.host + url if url[0] is "/" 5 | base_url = document.location.protocol + "//" + document.location.host + document.location.pathname 6 | return base_url + "/" + url if base_url[base_url.length - 1] isnt "/" and url[0] isnt "#" 7 | base_url + url 8 | 9 | replaceParam: (param, rep, rep2) -> 10 | param = param.replace(/\{\{(.*?)\}\}/g, (m, v) -> 11 | rep[v] or "" 12 | ) 13 | if rep2 14 | param = param.replace(/\{(.*?)\}/g, (m, v) -> 15 | rep2[v] or "" 16 | ) 17 | param 18 | -------------------------------------------------------------------------------- /compile-jquery.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | # In the jquery sources folder, you have to: 3 | # 4 | # git clone git@github.com:jquery/jquery.git 5 | # git checkout 2.1.1 6 | # npm install 7 | # 8 | # Make sure to have grunt-cli installed with 9 | # npm install -g grunt-cli 10 | 11 | OAUTHJS_PATH=`pwd` 12 | JQUERY_PATH="../jquery" 13 | 14 | cd $JQUERY_PATH 15 | grunt custom:-attributes,-attributes/attr,-attributes/classes,-attributes/prop,-attributes/support,-attributes/val,-attributes,-css/addGetHookIf,-css/curCSS,-css/defaultDisplay,-css/hiddenVisibleSelectors,-css/support,-css/swap,-css/var,-css/var/cssExpand,-css/var/getStyles,-css/var/isHidden,-css/var/rmargin,-css/var/rnumnonpx,-css,-data/var/data_user,-deprecated,-dimensions,-effects,-effects/animatedSelector,-effects/Tween,-event/alias,-event/support,-intro,-manipulation/_evalUrl,-manipulation/support,-manipulation/var,-manipulation/var/rcheckableType,-manipulation,-offset,-outro,-queue,-queue/delay,-selector-native,-selector-sizzle,-sizzle,-sizzle/dist,-sizzle/dist/sizzle,-sizzle/test,-traversing,-traversing/findFilter,-traversing/var,-traversing/var/rneedsContext,-wrap,-exports,-exports/global,-exports/amd 16 | cat ./dist/jquery.js | head -n $((`cat ./dist/jquery.js | wc -l`-2)) > ./dist/jquery-lite.js 17 | LASTLINES=`cat ./dist/jquery.js | tail -n 1` 18 | echo 'return jQuery;' >> ./dist/jquery-lite.js 19 | echo $LASTLINES >> ./dist/jquery-lite.js 20 | 21 | cd $OAUTHJS_PATH 22 | cp $JQUERY_PATH/dist/jquery-lite.js ./js/tools/jquery-lite.js && echo "copied jquery to oauth-js" 23 | grunt && echo "compiled oauth-js" 24 | -------------------------------------------------------------------------------- /dist/oauth.min.js: -------------------------------------------------------------------------------- 1 | !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.oauthioWeb=a()}}(function(){var a;return function b(a,c,d){function e(g,h){if(!c[g]){if(!a[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:{}};a[g][0].call(k.exports,function(b){var c=a[g][1][b];return e(c?c:b)},k,k.exports,b,a,c,d)}return c[g].exports}for(var f="function"==typeof require&&require,g=0;g0)&&(l=k.createElement("iframe"),l.src=j.oauthd_url+"/auth/iframe?d="+encodeURIComponent(g.getAbsUrl("/")),l.width=0,l.height=0,l.frameBorder=0,l.style.visibility="hidden",k.body.appendChild(l)),v=setTimeout(function(){null!=e&&e.reject(new Error("Authorization timed out")),b.callback&&"function"==typeof b.callback&&b.callback(new Error("Authorization timed out"));try{u.close()}catch(a){}},12e5),u=r.open(t,"Authorization",w),u?(u.focus(),q=r.setInterval(function(){return null!==u&&!u.closed||(r.clearInterval(q),p||(null!=e&&e.reject(new Error("The popup was closed")),!b.callback||"function"!=typeof b.callback))?void 0:b.callback(new Error("The popup was closed"))},500)):(null!=e&&e.reject(new Error("Could not open a popup")),b.callback&&"function"==typeof b.callback&&b.callback(new Error("Could not open a popup"))),null!=e?e.promise():void 0)):(null!=e&&e.reject(new Error("OAuth object must be initialized")),null==d?e.promise():d(new Error("OAuth object must be initialized")))},redirect:function(a,b,c){var e,i;if(2===arguments.length&&(c=b,b={}),"string"!=typeof c)throw new Error("You must specify an url");return h.cacheEnabled(b.cache)&&(i=h.tryCache(m,a,b.cache))?(c=g.getAbsUrl(c)+(-1===c.indexOf("#")?"#":"&")+"oauthio=cache:"+a,l.changeHref(c),void l.reload()):(b.state||(b.state=f.create_hash(),b.state_type="client"),d.create("oauthio_state",b.state),e=encodeURIComponent(g.getAbsUrl(c)),c=j.oauthd_url+"/auth/"+a+"?k="+j.key,c+="&redirect_uri="+e,b&&(c+="&opts="+encodeURIComponent(JSON.stringify(b))),void l.changeHref(c))},isRedirect:function(a){var b,c,d;if(null==n)return!1;if("cache:"===(null!=n?n.substr(0,6):void 0))return b=null!=n?n.substr(6):void 0,a?b.toLowerCase()===a.toLowerCase():b;try{c=JSON.parse(n)}catch(e){return d=e,!1}return a?c.provider.toLowerCase()===a.toLowerCase():c.provider},callback:function(a,b,d){var e,f,g;if(e=c.Deferred(),1===arguments.length&&"function"==typeof a&&(d=a,a=void 0,b={}),1===arguments.length&&"string"==typeof a&&(b={}),2===arguments.length&&"function"==typeof b&&(d=b,b={}),h.cacheEnabled(null!=b?b.cache:void 0)||"cache:"===(null!=n?n.substr(0,6):void 0))if(a||"cache:"!==(null!=n?n.substr(0,6):void 0)||(a=n.substr(6)),g=h.tryCache(m,a,!0)){if(!d)return null!=e&&e.resolve(g),null!=e?e.promise():void 0;if(g)return d(null,g)}else if("cache:"===(null!=n?n.substr(0,6):void 0))return f=new Error("Could not fetch data from cache"),d?d(f):(null!=e&&e.reject(f),null!=e?e.promise():void 0);return n?(o.request.sendCallback({data:n,provider:a,cache:null!=b?b.cache:void 0,expires:null!=b?b.expires:void 0,callback:d},e),null!=e?e.promise():void 0):void 0},clearCache:function(a){return h.clearCache(a)},http_me:function(a){o.request.http_me&&o.request.http_me(a)},http:function(a){o.request.http&&o.request.http(a)},getVersion:function(){return b.getVersion.apply(this)}}}},{"../tools/cookies":10,"../tools/sha1":14,"./providers":5,"./request":6}],5:[function(a,b,c){"use strict";var d;d=a("../config"),b.exports=function(a){var b,c,e,f;return b=a.getJquery(),f={},e={},c={execProvidersCb:function(a,b,c){var d,f;if(e[a]){d=e[a],delete e[a];for(f in d)d[f](b,c)}},fetchDescription:function(a){f[a]||(f[a]=!0,b.ajax({url:d.oauthd_api+"/providers/"+a,data:{extend:!0},dataType:"json"}).done(function(b){f[a]=b.data,c.execProvidersCb(a,null,b.data)}).always(function(){"object"!=typeof f[a]&&(delete f[a],c.execProvidersCb(a,new Error("Unable to fetch request description")))}))},getDescription:function(a,b,d){return b=b||{},"object"==typeof f[a]?d(null,f[a]):(f[a]||c.fetchDescription(a),b.wait?(e[a]=e[a]||[],void e[a].push(d)):d(null,{}))}}}},{"../config":1}],6:[function(a,b,c){"use strict";var d,e=[].indexOf||function(a){for(var b=0,c=this.length;c>b;b++)if(b in this&&this[b]===a)return b;return-1};d=a("../tools/url")(),b.exports=function(a,b,c){var f,g,h,i,j;return f=a.getJquery(),h=a.getConfig(),g=a.getCache(),i=[],j=!1,{retrieveMethods:function(){var a;return a=f.Deferred(),j?a.resolve(i):f.ajax(h.oauthd_url+"/api/extended-endpoints").then(function(b){return i=b.data,j=!0,a.resolve()}).fail(function(b){return j=!0,a.reject(b)}),a.promise()},generateMethods:function(a,b,c){var d,e,f,g,h,j,k,l,m;if(null!=i){for(k=[],e=d=0,g=i.length;g>d;e=++d)l=i[e],h=l.name.split("."),j=a,k.push(function(){var a,d,e;for(e=[],f=a=0,d=h.length;d>a;f=++a)m=h[f],f=0?k.url+="&"+c:k.url+="?"+c}if(g.headers){k.headers=k.headers||{};for(a in g.headers)k.headers[a]=d.replaceParam(g.headers[a],k.oauthio.tokens,g.parameters)}return delete k.oauthio,f.ajax(k)}},k={},j=void 0;for(j in a)k[j]=a[j];return k.oauthio.request&&k.oauthio.request!==!0?i():(g={wait:!!k.oauthio.request},b=f.Deferred(),c.getDescription(k.oauthio.provider,g,function(a,c){return a?b.reject(a):(k.oauthio.tokens.oauth_token&&k.oauthio.tokens.oauth_token_secret?k.oauthio.request=c.oauth1&&c.oauth1.request:k.oauthio.request=c.oauth2&&c.oauth2.request,void b.resolve())}),b.then(i))},http_me:function(a){var b,d,e,g,i;e=function(){var a,b,c,d;a=f.Deferred(),d=i.oauthio.request||{},i.url=h.oauthd_url+"/auth/"+i.oauthio.provider+"/me",i.headers=i.headers||{},i.headers.oauthio="k="+h.key,i.oauthio.tokens.oauth_token&&i.oauthio.tokens.oauth_token_secret&&(i.headers.oauthio+="&oauthv=1");for(b in i.oauthio.tokens)i.headers.oauthio+="&"+encodeURIComponent(b)+"="+encodeURIComponent(i.oauthio.tokens[b]);return delete i.oauthio,c=f.ajax(i),f.when(c).done(function(b){a.resolve(b.data)}).fail(function(b){b.responseJSON?a.reject(b.responseJSON.data):a.reject(new Error("An error occured while trying to access the resource"))}),a.promise()},i={};for(g in a)i[g]=a[g];return i.oauthio.request&&i.oauthio.request!==!0?e():(d={wait:!!i.oauthio.request},b=f.Deferred(),c.getDescription(i.oauthio.provider,d,function(a,c){return a?b.reject(a):(i.oauthio.tokens.oauth_token&&i.oauthio.tokens.oauth_token_secret?i.oauthio.request=c.oauth1&&c.oauth1.request:i.oauthio.request=c.oauth2&&c.oauth2.request,void b.resolve())}),b.then(e))},http_all:function(a,b,c){var d;return(d=function(){var b,c,d,e;b=f.Deferred(),e=a.oauthio.request||{},a.headers=a.headers||{},a.headers.oauthio="k="+h.key,a.oauthio.tokens.oauth_token&&a.oauthio.tokens.oauth_token_secret&&(a.headers.oauthio+="&oauthv=1");for(c in a.oauthio.tokens)a.headers.oauthio+="&"+encodeURIComponent(c)+"="+encodeURIComponent(a.oauthio.tokens[c]);return delete a.oauthio,d=f.ajax(a),f.when(d).done(function(a){var c;if("string"==typeof a.data)try{a.data=JSON.parse(a.data)}catch(d){c=d,a.data=a.data}finally{b.resolve(a.data)}}).fail(function(a){a.responseJSON?b.reject(a.responseJSON.data):b.reject(new Error("An error occured while trying to access the resource"))}),b.promise()})()},mkHttp:function(a,b,c,d){var e;return e=this,function(f,g){var h,i;if(i={},"string"==typeof f){if("object"==typeof g)for(h in g)i[h]=g[h];i.url=f}else if("object"==typeof f)for(h in f)i[h]=f[h];return i.type=i.type||d,i.oauthio={provider:a,tokens:b,request:c},e.http(i)}},mkHttpMe:function(a,b,c,d){var e;return e=this,function(f){var g;return g={},g.type=g.type||d,g.oauthio={provider:a,tokens:b,request:c},g.data=g.data||{},f&&(g.data.filter=f.join(",")),e.http_me(g)}},mkHttpAll:function(a,b,c){var d;return d=this,function(){var e,f,g,i;f={},f.type=c.method,f.url=h.oauthd_url+c.endpoint.replace(":provider",a),f.oauthio={provider:a,tokens:b},f.data={};for(e in arguments)i=arguments[e],g=c.params[e],null!=g&&(f.data[g.name]=i);return f.data=f.data||{},d.http_all(f,c,arguments)}},sendCallback:function(a,c){var d,e,f,h,i,j,k,l,m;d=this,e=void 0,h=void 0;try{e=JSON.parse(a.data)}catch(n){return f=n,c.reject(new Error("Error while parsing result")),a.callback(new Error("Error while parsing result"))}if(e&&e.provider){if(a.provider&&e.provider.toLowerCase()!==a.provider.toLowerCase())return h=new Error("Returned provider name does not match asked provider"),c.reject(h),a.callback&&"function"==typeof a.callback?a.callback(h):void 0;if("error"===e.status||"fail"===e.status)return h=new Error(e.message),h.body=e.data,c.reject(h),a.callback&&"function"==typeof a.callback?a.callback(h):void 0;if("success"!==e.status||!e.data)return h=new Error,h.body=e.data,c.reject(h),a.callback&&"function"==typeof a.callback?a.callback(h):void 0;for(e.state=e.state.replace(/\s+/g,""),i=0;if;f++)b=e[f],this.data[b.key]!==b.value&&(c[b.key]=this.data[b.key]),null===this.data[b.key]&&delete this.data[b.key];for(h=function(a){var b,c,d;for(b=0,c=e.length;c>b;b++)if(d=e[b],d.key===a)return!0;return!1},k=this.getEditableData(),g=0,j=k.length;j>g;g++)b=k[g],h(b.key)||(c[b.key]=this.data[b.key]);return this.saveLocal(),a.API.put("/api/usermanagement/user?k="+d.key+"&token="+this.token,c)},c.prototype.select=function(a){var b;return b=null},c.prototype.saveLocal=function(){var a;return a={token:this.token,user:this.data,providers:this.providers},f.erase("oio_auth"),f.create("oio_auth",JSON.stringify(a),21600)},c.prototype.hasProvider=function(a){return!!this.providers&&-1!==this.providers.indexOf(a)},c.prototype.getProviders=function(){var c;return c=b.Deferred(),a.API.get("/api/usermanagement/user/providers?k="+d.key+"&token="+this.token).done(function(a){return function(b){return a.providers=b.data,a.saveLocal(),c.resolve(a.providers)}}(this)).fail(function(a){return c.reject(a)}),c.promise()},c.prototype.addProvider=function(c){var e;return e=b.Deferred(),"function"==typeof c.toJson&&(c=c.toJson()),c.email=this.data.email,this.providers||(this.providers=[]),this.providers.push(c.provider),a.API.post("/api/usermanagement/user/providers?k="+d.key+"&token="+this.token,c).done(function(a){return function(b){return a.data=b.data,a.saveLocal(),e.resolve()}}(this)).fail(function(a){return function(b){return a.providers.splice(a.providers.indexOf(c.provider),1),e.reject(b)}}(this)),e.promise()},c.prototype.removeProvider=function(c){var e;return e=b.Deferred(),this.providers.splice(this.providers.indexOf(c),1),a.API.del("/api/usermanagement/user/providers/"+c+"?k="+d.key+"&token="+this.token).done(function(a){return function(b){return a.saveLocal(),e.resolve(b)}}(this)).fail(function(a){return function(b){return a.providers.push(c),e.reject(b)}}(this)),e.promise()},c.prototype.changePassword=function(b,c){return a.API.post("/api/usermanagement/user/password?k="+d.key+"&token="+this.token,{password:c})},c.prototype.isLoggued=function(){return a.User.isLogged()},c.prototype.isLogged=function(){return a.User.isLogged()},c.prototype.logout=function(){var c;return c=b.Deferred(),f.erase("oio_auth"),a.API.post("/api/usermanagement/user/logout?k="+d.key+"&token="+this.token).done(function(){return c.resolve()}).fail(function(a){return c.reject(a)}),c.promise()},c}(),{initialize:function(b,c){return a.initialize(b,c)},setOAuthdURL:function(b){return a.setOAuthdURL(b)},signup:function(e){var g;return g=b.Deferred(),"function"==typeof e.toJson&&(e=e.toJson()),a.API.post("/api/usermanagement/signup?k="+d.key,e).done(function(a){return f.create("oio_auth",JSON.stringify(a.data),a.data.expires_in||21600),g.resolve(new c(a.data))}).fail(function(a){return g.reject(a)}),g.promise()},signin:function(e,g){var h,i;return h=b.Deferred(),"string"==typeof e||g?a.API.post("/api/usermanagement/signin?k="+d.key,{email:e,password:g}).done(function(a){return f.create("oio_auth",JSON.stringify(a.data),a.data.expires_in||21600),h.resolve(new c(a.data))}).fail(function(a){return h.reject(a)}):(i=e,"function"==typeof i.toJson&&(i=i.toJson()),a.API.post("/api/usermanagement/signin?k="+d.key,i).done(function(a){return f.create("oio_auth",JSON.stringify(a.data),a.data.expires_in||21600),h.resolve(new c(a.data))}).fail(function(a){return h.reject(a)})),h.promise()},confirmResetPassword:function(b,c){return a.API.post("/api/usermanagement/user/password?k="+d.key,{password:b,token:c})},resetPassword:function(b,c){return a.API.post("/api/usermanagement/user/password/reset?k="+d.key,{email:b})},refreshIdentity:function(){var e;return e=b.Deferred(),a.API.get("/api/usermanagement/user?k="+d.key+"&token="+JSON.parse(f.read("oio_auth")).token).done(function(a){return e.resolve(new c(a.data))}).fail(function(a){return e.reject(a)}),e.promise()},getIdentity:function(){var a;return a=f.read("oio_auth"),a?new c(JSON.parse(a)):null},isLogged:function(){var a;return a=f.read("oio_auth"),!!a}}}},{}],8:[function(b,c,d){!function(){var e,f;return f=b("./tools/jquery-lite.js"),e=b("./lib/core")(window,document,f,navigator),e.extend("OAuth",b("./lib/oauth")),e.extend("API",b("./lib/api")),e.extend("User",b("./lib/user")),"undefined"!=typeof angular&&null!==angular&&angular.module("oauthio",[]).factory("OAuth",[function(){return e.OAuth}]).factory("User",[function(){return e.User}]),d.OAuthio=e,window.User=d.User=d.OAuthio.User,window.OAuth=d.OAuth=d.OAuthio.OAuth,"function"==typeof a&&a.amd&&a(function(){return d}),("undefined"!=typeof c&&null!==c?c.exports:void 0)&&(c.exports=d),d}()},{"./lib/api":2,"./lib/core":3,"./lib/oauth":4,"./lib/user":7,"./tools/jquery-lite.js":11}],9:[function(a,b,c){"use strict";b.exports={init:function(a,b){return this.config=b,this.storage=a},tryCache:function(a,b,c){var d,e,f;if(this.cacheEnabled(c)){if(c=this.storage.read("oauthio_provider_"+b),!c)return!1;c=decodeURIComponent(c)}if("string"==typeof c)try{c=JSON.parse(c)}catch(g){return d=g,!1}if("object"==typeof c){f={};for(e in c)"request"!==e&&"function"!=typeof c[e]&&(f[e]=c[e]);return a.create(b,f,c.request)}return!1},storeCache:function(a,b){var c;c=3600,b.expires_in?c=b.expires_in:(this.config.options.expires||this.config.options.expires===!1)&&(c=this.config.options.expires),this.storage.create("oauthio_provider_"+a,encodeURIComponent(JSON.stringify(b)),c)},cacheEnabled:function(a){return"undefined"==typeof a?this.config.options.cache:a},clearCache:function(a){a?this.storage.erase("oauthio_provider_"+a):this.storage.eraseFrom("oauthio_provider_")}}},{}],10:[function(a,b,c){"use strict";b.exports={init:function(a,b){return this.config=a,this.document=b},create:function(a,b,c){var d;this.erase(a),d=new Date,c?d.setTime(d.getTime()+1e3*(c||1200)):d.setFullYear(d.getFullYear()+3),c="; expires="+d.toGMTString(),this.document.cookie=a+"="+b+c+"; path=/"},read:function(a){var b,c,d,e;for(e=a+"=",c=this.document.cookie.split(";"),d=0;de;e++)c=d[e],b=c.split("=")[0].trim(),b.substr(0,a.length)===a&&this.erase(b)}}},{}],11:[function(a,b,c){!function(a,c){"object"==typeof b&&"object"==typeof b.exports?b.exports=a.document?c(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return c(a)}:c(a)}("undefined"!=typeof window?window:this,function(a,b){function c(a){var b=a.length,c=B.type(a);return"function"===c||B.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}function d(a){var b=L[a]={};return B.each(a.match(K)||[],function(a,c){b[c]=!0}),b}function e(){z.removeEventListener("DOMContentLoaded",e,!1),a.removeEventListener("load",e,!1),B.ready()}function f(){Object.defineProperty(this.cache={},0,{get:function(){return{}}}),this.expando=B.expando+Math.random()}function g(a,b,c){var d;if(void 0===c&&1===a.nodeType)if(d="data-"+b.replace(Q,"-$1").toLowerCase(),c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:P.test(c)?B.parseJSON(c):c}catch(e){}data_user.set(a,b,c)}else c=void 0;return c}function h(){return!0}function i(){return!1}function j(){try{return z.activeElement}catch(a){}}function k(a){return function(b,c){"string"!=typeof b&&(c=b,b="*");var d,e=0,f=b.toLowerCase().match(K)||[];if(B.isFunction(c))for(;d=f[e++];)"+"===d[0]?(d=d.slice(1)||"*",(a[d]=a[d]||[]).unshift(c)):(a[d]=a[d]||[]).push(c)}}function l(a,b,c,d){function e(h){var i;return f[h]=!0,B.each(a[h]||[],function(a,h){var j=h(b,c,d);return"string"!=typeof j||g||f[j]?g?!(i=j):void 0:(b.dataTypes.unshift(j),e(j),!1)}),i}var f={},g=a===ga;return e(b.dataTypes[0])||!f["*"]&&e("*")}function m(a,b){var c,d,e=B.ajaxSettings.flatOptions||{};for(c in b)void 0!==b[c]&&((e[c]?a:d||(d={}))[c]=b[c]);return d&&B.extend(!0,a,d),a}function n(a,b,c){for(var d,e,f,g,h=a.contents,i=a.dataTypes;"*"===i[0];)i.shift(),void 0===d&&(d=a.mimeType||b.getResponseHeader("Content-Type"));if(d)for(e in h)if(h[e]&&h[e].test(d)){i.unshift(e);break}if(i[0]in c)f=i[0];else{for(e in c){if(!i[0]||a.converters[e+" "+i[0]]){f=e;break}g||(g=e)}f=f||g}return f?(f!==i[0]&&i.unshift(f),c[f]):void 0}function o(a,b,c,d){var e,f,g,h,i,j={},k=a.dataTypes.slice();if(k[1])for(g in a.converters)j[g.toLowerCase()]=a.converters[g];for(f=k.shift();f;)if(a.responseFields[f]&&(c[a.responseFields[f]]=b),!i&&d&&a.dataFilter&&(b=a.dataFilter(b,a.dataType)),i=f,f=k.shift())if("*"===f)f=i;else if("*"!==i&&i!==f){if(g=j[i+" "+f]||j["* "+f],!g)for(e in j)if(h=e.split(" "),h[1]===f&&(g=j[i+" "+h[0]]||j["* "+h[0]])){g===!0?g=j[e]:j[e]!==!0&&(f=h[0],k.unshift(h[1]));break}if(g!==!0)if(g&&a["throws"])b=g(b);else try{b=g(b)}catch(l){return{state:"parsererror",error:g?l:"No conversion from "+i+" to "+f}}}return{state:"success",data:b}}function p(a,b,c,d){var e;if(B.isArray(b))B.each(b,function(b,e){c||ka.test(a)?d(a,e):p(a+"["+("object"==typeof e?b:"")+"]",e,c,d)});else if(c||"object"!==B.type(b))d(a,b);else for(e in b)p(a+"["+e+"]",b[e],c,d)}var q=[],r=q.slice,s=q.concat,t=q.push,u=q.indexOf,v={},w=v.toString,x=v.hasOwnProperty,y={},z=a.document,A="2.1.1 -attributes,-attributes/attr,-attributes/classes,-attributes/prop,-attributes/support,-attributes/val,-css/addGetHookIf,-css/curCSS,-css/defaultDisplay,-css/hiddenVisibleSelectors,-css/support,-css/swap,-css/var,-css/var/cssExpand,-css/var/getStyles,-css/var/isHidden,-css/var/rmargin,-css/var/rnumnonpx,-css,-effects,-effects/Tween,-effects/animatedSelector,-dimensions,-offset,-data/var/data_user,-deprecated,-event/alias,-event/support,-intro,-manipulation/_evalUrl,-manipulation/support,-manipulation/var,-manipulation/var/rcheckableType,-manipulation,-outro,-queue,-queue/delay,-selector-native,-selector-sizzle,-sizzle/dist,-sizzle/dist/sizzle,-sizzle/dist/min,-sizzle/test,-sizzle/test/jquery,-traversing,-traversing/findFilter,-traversing/var/rneedsContext,-traversing/var,-wrap,-exports,-exports/amd",B=function(a,b){return new B.fn.init(a,b)},C=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,D=/^-ms-/,E=/-([\da-z])/gi,F=function(a,b){return b.toUpperCase()};B.fn=B.prototype={jquery:A,constructor:B,selector:"",length:0,toArray:function(){return r.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:r.call(this)},pushStack:function(a){var b=B.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return B.each(this,a,b)},map:function(a){return this.pushStack(B.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(r.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:t,sort:q.sort,splice:q.splice},B.extend=B.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||B.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(a=arguments[h]))for(b in a)c=g[b],d=a[b],g!==d&&(j&&d&&(B.isPlainObject(d)||(e=B.isArray(d)))?(e?(e=!1,f=c&&B.isArray(c)?c:[]):f=c&&B.isPlainObject(c)?c:{},g[b]=B.extend(j,f,d)):void 0!==d&&(g[b]=d));return g},B.extend({expando:"jQuery"+(A+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===B.type(a)},isArray:Array.isArray,isWindow:function(a){return null!=a&&a===a.window},isNumeric:function(a){return!B.isArray(a)&&a-parseFloat(a)>=0},isPlainObject:function(a){return"object"!==B.type(a)||a.nodeType||B.isWindow(a)?!1:!a.constructor||x.call(a.constructor.prototype,"isPrototypeOf")},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?v[w.call(a)]||"object":typeof a},globalEval:function(a){var b,c=eval;a=B.trim(a),a&&(1===a.indexOf("use strict")?(b=z.createElement("script"),b.text=a,z.head.appendChild(b).parentNode.removeChild(b)):c(a))},camelCase:function(a){return a.replace(D,"ms-").replace(E,F)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,d){var e,f=0,g=a.length,h=c(a);if(d){if(h)for(;g>f&&(e=b.apply(a[f],d),e!==!1);f++);else for(f in a)if(e=b.apply(a[f],d),e===!1)break}else if(h)for(;g>f&&(e=b.call(a[f],f,a[f]),e!==!1);f++);else for(f in a)if(e=b.call(a[f],f,a[f]),e===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(C,"")},makeArray:function(a,b){var d=b||[];return null!=a&&(c(Object(a))?B.merge(d,"string"==typeof a?[a]:a):t.call(d,a)),d},inArray:function(a,b,c){return null==b?-1:u.call(b,a,c)},merge:function(a,b){for(var c=+b.length,d=0,e=a.length;c>d;d++)a[e++]=b[d];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,d){var e,f=0,g=a.length,h=c(a),i=[];if(h)for(;g>f;f++)e=b(a[f],f,d),null!=e&&i.push(e);else for(f in a)e=b(a[f],f,d),null!=e&&i.push(e);return s.apply([],i)},guid:1,proxy:function(a,b){var c,d,e;return"string"==typeof b&&(c=a[b],b=a,a=c),B.isFunction(a)?(d=r.call(arguments,2),e=function(){return a.apply(b||this,d.concat(r.call(arguments)))},e.guid=a.guid=a.guid||B.guid++,e):void 0},now:Date.now,support:y}),B.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){v["[object "+b+"]"]=b.toLowerCase()});var G,H=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,I=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,J=B.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:I.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||G).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof B?b[0]:b,B.merge(this,B.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:z,!0)),H.test(c[1])&&B.isPlainObject(b))for(c in b)B.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}return d=z.getElementById(c[2]),d&&d.parentNode&&(this.length=1, 2 | this[0]=d),this.context=z,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):B.isFunction(a)?"undefined"!=typeof G.ready?G.ready(a):a(B):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),B.makeArray(a,this))};J.prototype=B.fn,G=B(z);var K=/\S+/g,L={};B.Callbacks=function(a){a="string"==typeof a?L[a]||d(a):B.extend({},a);var b,c,e,f,g,h,i=[],j=!a.once&&[],k=function(d){for(b=a.memory&&d,c=!0,h=f||0,f=0,g=i.length,e=!0;i&&g>h;h++)if(i[h].apply(d[0],d[1])===!1&&a.stopOnFalse){b=!1;break}e=!1,i&&(j?j.length&&k(j.shift()):b?i=[]:l.disable())},l={add:function(){if(i){var c=i.length;!function d(b){B.each(b,function(b,c){var e=B.type(c);"function"===e?a.unique&&l.has(c)||i.push(c):c&&c.length&&"string"!==e&&d(c)})}(arguments),e?g=i.length:b&&(f=c,k(b))}return this},remove:function(){return i&&B.each(arguments,function(a,b){for(var c;(c=B.inArray(b,i,c))>-1;)i.splice(c,1),e&&(g>=c&&g--,h>=c&&h--)}),this},has:function(a){return a?B.inArray(a,i)>-1:!(!i||!i.length)},empty:function(){return i=[],g=0,this},disable:function(){return i=j=b=void 0,this},disabled:function(){return!i},lock:function(){return j=void 0,b||l.disable(),this},locked:function(){return!j},fireWith:function(a,b){return!i||c&&!j||(b=b||[],b=[a,b.slice?b.slice():b],e?j.push(b):k(b)),this},fire:function(){return l.fireWith(this,arguments),this},fired:function(){return!!c}};return l},B.extend({Deferred:function(a){var b=[["resolve","done",B.Callbacks("once memory"),"resolved"],["reject","fail",B.Callbacks("once memory"),"rejected"],["notify","progress",B.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return B.Deferred(function(c){B.each(b,function(b,f){var g=B.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&B.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?B.extend(a,d):d}},e={};return d.pipe=d.then,B.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b,c,d,e=0,f=r.call(arguments),g=f.length,h=1!==g||a&&B.isFunction(a.promise)?g:0,i=1===h?a:B.Deferred(),j=function(a,c,d){return function(e){c[a]=this,d[a]=arguments.length>1?r.call(arguments):e,d===b?i.notifyWith(c,d):--h||i.resolveWith(c,d)}};if(g>1)for(b=new Array(g),c=new Array(g),d=new Array(g);g>e;e++)f[e]&&B.isFunction(f[e].promise)?f[e].promise().done(j(e,d,f)).fail(i.reject).progress(j(e,c,b)):--h;return h||i.resolveWith(d,f),i.promise()}});var M;B.fn.ready=function(a){return B.ready.promise().done(a),this},B.extend({isReady:!1,readyWait:1,holdReady:function(a){a?B.readyWait++:B.ready(!0)},ready:function(a){(a===!0?--B.readyWait:B.isReady)||(B.isReady=!0,a!==!0&&--B.readyWait>0||(M.resolveWith(z,[B]),B.fn.triggerHandler&&(B(z).triggerHandler("ready"),B(z).off("ready"))))}}),B.ready.promise=function(b){return M||(M=B.Deferred(),"complete"===z.readyState?setTimeout(B.ready):(z.addEventListener("DOMContentLoaded",e,!1),a.addEventListener("load",e,!1))),M.promise(b)},B.ready.promise();var N=B.access=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===B.type(c)){e=!0;for(h in c)B.access(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,B.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(B(a),c)})),b))for(;i>h;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f};B.acceptData=function(a){return 1===a.nodeType||9===a.nodeType||!+a.nodeType},f.uid=1,f.accepts=B.acceptData,f.prototype={key:function(a){if(!f.accepts(a))return 0;var b={},c=a[this.expando];if(!c){c=f.uid++;try{b[this.expando]={value:c},Object.defineProperties(a,b)}catch(d){b[this.expando]=c,B.extend(a,b)}}return this.cache[c]||(this.cache[c]={}),c},set:function(a,b,c){var d,e=this.key(a),f=this.cache[e];if("string"==typeof b)f[b]=c;else if(B.isEmptyObject(f))B.extend(this.cache[e],b);else for(d in b)f[d]=b[d];return f},get:function(a,b){var c=this.cache[this.key(a)];return void 0===b?c:c[b]},access:function(a,b,c){var d;return void 0===b||b&&"string"==typeof b&&void 0===c?(d=this.get(a,b),void 0!==d?d:this.get(a,B.camelCase(b))):(this.set(a,b,c),void 0!==c?c:b)},remove:function(a,b){var c,d,e,f=this.key(a),g=this.cache[f];if(void 0===b)this.cache[f]={};else{B.isArray(b)?d=b.concat(b.map(B.camelCase)):(e=B.camelCase(b),b in g?d=[b,e]:(d=e,d=d in g?[d]:d.match(K)||[])),c=d.length;for(;c--;)delete g[d[c]]}},hasData:function(a){return!B.isEmptyObject(this.cache[a[this.expando]]||{})},discard:function(a){a[this.expando]&&delete this.cache[a[this.expando]]}};var O=new f,P=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,Q=/([A-Z])/g;B.extend({hasData:function(a){return data_user.hasData(a)||O.hasData(a)},data:function(a,b,c){return data_user.access(a,b,c)},removeData:function(a,b){data_user.remove(a,b)},_data:function(a,b,c){return O.access(a,b,c)},_removeData:function(a,b){O.remove(a,b)}}),B.fn.extend({data:function(a,b){var c,d,e,f=this[0],h=f&&f.attributes;if(void 0===a){if(this.length&&(e=data_user.get(f),1===f.nodeType&&!O.get(f,"hasDataAttrs"))){for(c=h.length;c--;)h[c]&&(d=h[c].name,0===d.indexOf("data-")&&(d=B.camelCase(d.slice(5)),g(f,d,e[d])));O.set(f,"hasDataAttrs",!0)}return e}return"object"==typeof a?this.each(function(){data_user.set(this,a)}):N(this,function(b){var c,d=B.camelCase(a);if(f&&void 0===b){if(c=data_user.get(f,a),void 0!==c)return c;if(c=data_user.get(f,d),void 0!==c)return c;if(c=g(f,d,void 0),void 0!==c)return c}else this.each(function(){var c=data_user.get(this,d);data_user.set(this,d,b),-1!==a.indexOf("-")&&void 0!==c&&data_user.set(this,a,b)})},null,b,arguments.length>1,null,!0)},removeData:function(a){return this.each(function(){data_user.remove(this,a)})}});var R=(/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,"undefined"),S=/^key/,T=/^(?:mouse|pointer|contextmenu)|click/,U=/^(?:focusinfocus|focusoutblur)$/,V=/^([^.]*)(?:\.(.+)|)$/;B.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q=O.get(a);if(q)for(c.handler&&(f=c,c=f.handler,e=f.selector),c.guid||(c.guid=B.guid++),(i=q.events)||(i=q.events={}),(g=q.handle)||(g=q.handle=function(b){return typeof B!==R&&B.event.triggered!==b.type?B.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(K)||[""],j=b.length;j--;)h=V.exec(b[j])||[],n=p=h[1],o=(h[2]||"").split(".").sort(),n&&(l=B.event.special[n]||{},n=(e?l.delegateType:l.bindType)||n,l=B.event.special[n]||{},k=B.extend({type:n,origType:p,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&B.expr.match.needsContext.test(e),namespace:o.join(".")},f),(m=i[n])||(m=i[n]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,o,g)!==!1||a.addEventListener&&a.addEventListener(n,g,!1)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),B.event.global[n]=!0)},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q=O.hasData(a)&&O.get(a);if(q&&(i=q.events)){for(b=(b||"").match(K)||[""],j=b.length;j--;)if(h=V.exec(b[j])||[],n=p=h[1],o=(h[2]||"").split(".").sort(),n){for(l=B.event.special[n]||{},n=(d?l.delegateType:l.bindType)||n,m=i[n]||[],h=h[2]&&new RegExp("(^|\\.)"+o.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;f--;)k=m[f],!e&&p!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,o,q.handle)!==!1||B.removeEvent(a,n,q.handle),delete i[n])}else for(n in i)B.event.remove(a,n+b[j],c,d,!0);B.isEmptyObject(i)&&(delete q.handle,O.remove(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,j,k,l,m=[d||z],n=x.call(b,"type")?b.type:b,o=x.call(b,"namespace")?b.namespace.split("."):[];if(g=h=d=d||z,3!==d.nodeType&&8!==d.nodeType&&!U.test(n+B.event.triggered)&&(n.indexOf(".")>=0&&(o=n.split("."),n=o.shift(),o.sort()),j=n.indexOf(":")<0&&"on"+n,b=b[B.expando]?b:new B.Event(n,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=o.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+o.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:B.makeArray(c,[b]),l=B.event.special[n]||{},e||!l.trigger||l.trigger.apply(d,c)!==!1)){if(!e&&!l.noBubble&&!B.isWindow(d)){for(i=l.delegateType||n,U.test(i+n)||(g=g.parentNode);g;g=g.parentNode)m.push(g),h=g;h===(d.ownerDocument||z)&&m.push(h.defaultView||h.parentWindow||a)}for(f=0;(g=m[f++])&&!b.isPropagationStopped();)b.type=f>1?i:l.bindType||n,k=(O.get(g,"events")||{})[b.type]&&O.get(g,"handle"),k&&k.apply(g,c),k=j&&g[j],k&&k.apply&&B.acceptData(g)&&(b.result=k.apply(g,c),b.result===!1&&b.preventDefault());return b.type=n,e||b.isDefaultPrevented()||l._default&&l._default.apply(m.pop(),c)!==!1||!B.acceptData(d)||j&&B.isFunction(d[n])&&!B.isWindow(d)&&(h=d[j],h&&(d[j]=null),B.event.triggered=n,d[n](),B.event.triggered=void 0,h&&(d[j]=h)),b.result}},dispatch:function(a){a=B.event.fix(a);var b,c,d,e,f,g=[],h=r.call(arguments),i=(O.get(this,"events")||{})[a.type]||[],j=B.event.special[a.type]||{};if(h[0]=a,a.delegateTarget=this,!j.preDispatch||j.preDispatch.call(this,a)!==!1){for(g=B.event.handlers.call(this,a,i),b=0;(e=g[b++])&&!a.isPropagationStopped();)for(a.currentTarget=e.elem,c=0;(f=e.handlers[c++])&&!a.isImmediatePropagationStopped();)a.namespace_re&&!a.namespace_re.test(f.namespace)||(a.handleObj=f,a.data=f.data,d=((B.event.special[f.origType]||{}).handle||f.handler).apply(e.elem,h),void 0!==d&&(a.result=d)===!1&&(a.preventDefault(),a.stopPropagation()));return j.postDispatch&&j.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!==this;i=i.parentNode||this)if(i.disabled!==!0||"click"!==a.type){for(d=[],c=0;h>c;c++)f=b[c],e=f.selector+" ",void 0===d[e]&&(d[e]=f.needsContext?B(e,this).index(i)>=0:B.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h0?4:0,i=a>=200&&300>a||304===a,c&&(u=n(m,y,c)),u=o(m,u,y,i),i?(m.ifModified&&(v=y.getResponseHeader("Last-Modified"),v&&(B.lastModified[e]=v),v=y.getResponseHeader("etag"),v&&(B.etag[e]=v)),204===a||"HEAD"===m.type?x="nocontent":304===a?x="notmodified":(x=u.state,k=u.data,l=u.error,i=!l)):(l=x,!a&&x||(x="error",0>a&&(a=0))),y.status=a,y.statusText=(b||x)+"",i?r.resolveWith(p,[k,x,y]):r.rejectWith(p,[y,x,l]),y.statusCode(t),t=void 0,j&&q.trigger(i?"ajaxSuccess":"ajaxError",[y,m,i?k:l]),s.fireWith(p,[y,x]),j&&(q.trigger("ajaxComplete",[y,m]),--B.active||B.event.trigger("ajaxStop")))}"object"==typeof a&&(b=a,a=void 0),b=b||{};var d,e,f,g,h,i,j,k,m=B.ajaxSetup({},b),p=m.context||m,q=m.context&&(p.nodeType||p.jquery)?B(p):B.event,r=B.Deferred(),s=B.Callbacks("once memory"),t=m.statusCode||{},u={},v={},w=0,x="canceled",y={readyState:0,getResponseHeader:function(a){var b;if(2===w){if(!g)for(g={};b=aa.exec(f);)g[b[1].toLowerCase()]=b[2];b=g[a.toLowerCase()]}return null==b?null:b},getAllResponseHeaders:function(){return 2===w?f:null},setRequestHeader:function(a,b){var c=a.toLowerCase();return w||(a=v[c]=v[c]||a,u[a]=b),this},overrideMimeType:function(a){return w||(m.mimeType=a),this},statusCode:function(a){var b;if(a)if(2>w)for(b in a)t[b]=[t[b],a[b]];else y.always(a[y.status]);return this},abort:function(a){var b=a||x;return d&&d.abort(b),c(0,b),this}};if(r.promise(y).complete=s.add,y.success=y.done,y.error=y.fail,m.url=((a||m.url||Z)+"").replace($,"").replace(da,Y[1]+"//"),m.type=b.method||b.type||m.method||m.type,m.dataTypes=B.trim(m.dataType||"*").toLowerCase().match(K)||[""],null==m.crossDomain&&(i=ea.exec(m.url.toLowerCase()),m.crossDomain=!(!i||i[1]===Y[1]&&i[2]===Y[2]&&(i[3]||("http:"===i[1]?"80":"443"))===(Y[3]||("http:"===Y[1]?"80":"443")))),m.data&&m.processData&&"string"!=typeof m.data&&(m.data=B.param(m.data,m.traditional)),l(fa,m,b,y),2===w)return y;j=m.global,j&&0===B.active++&&B.event.trigger("ajaxStart"),m.type=m.type.toUpperCase(),m.hasContent=!ca.test(m.type),e=m.url,m.hasContent||(m.data&&(e=m.url+=(X.test(e)?"&":"?")+m.data,delete m.data),m.cache===!1&&(m.url=_.test(e)?e.replace(_,"$1_="+W++):e+(X.test(e)?"&":"?")+"_="+W++)),m.ifModified&&(B.lastModified[e]&&y.setRequestHeader("If-Modified-Since",B.lastModified[e]),B.etag[e]&&y.setRequestHeader("If-None-Match",B.etag[e])),(m.data&&m.hasContent&&m.contentType!==!1||b.contentType)&&y.setRequestHeader("Content-Type",m.contentType),y.setRequestHeader("Accept",m.dataTypes[0]&&m.accepts[m.dataTypes[0]]?m.accepts[m.dataTypes[0]]+("*"!==m.dataTypes[0]?", "+ha+"; q=0.01":""):m.accepts["*"]);for(k in m.headers)y.setRequestHeader(k,m.headers[k]);if(m.beforeSend&&(m.beforeSend.call(p,y,m)===!1||2===w))return y.abort();x="abort";for(k in{success:1,error:1,complete:1})y[k](m[k]);if(d=l(ga,m,b,y)){y.readyState=1,j&&q.trigger("ajaxSend",[y,m]),m.async&&m.timeout>0&&(h=setTimeout(function(){y.abort("timeout")},m.timeout));try{w=1,d.send(u,c)}catch(z){if(!(2>w))throw z;c(-1,z)}}else c(-1,"No Transport");return y},getJSON:function(a,b,c){return B.get(a,b,c,"json")},getScript:function(a,b){return B.get(a,void 0,b,"script")}}),B.each(["get","post"],function(a,b){B[b]=function(a,c,d,e){return B.isFunction(c)&&(e=e||d,d=c,c=void 0),B.ajax({url:a,type:b,dataType:e,data:c,success:d})}}),B.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(a,b){B.fn[b]=function(a){return this.on(b,a)}});var ja=/%20/g,ka=/\[\]$/,la=/\r?\n/g,ma=/^(?:submit|button|image|reset|file)$/i,na=/^(?:input|select|textarea|keygen)/i;B.param=function(a,b){var c,d=[],e=function(a,b){b=B.isFunction(b)?b():null==b?"":b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};if(void 0===b&&(b=B.ajaxSettings&&B.ajaxSettings.traditional),B.isArray(a)||a.jquery&&!B.isPlainObject(a))B.each(a,function(){e(this.name,this.value)});else for(c in a)p(c,a[c],b,e);return d.join("&").replace(ja,"+")},B.fn.extend({serialize:function(){return B.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var a=B.prop(this,"elements");return a?B.makeArray(a):this}).filter(function(){var a=this.type;return this.name&&!B(this).is(":disabled")&&na.test(this.nodeName)&&!ma.test(a)&&(this.checked||!rcheckableType.test(a))}).map(function(a,b){var c=B(this).val();return null==c?null:B.isArray(c)?B.map(c,function(a){return{name:b.name,value:a.replace(la,"\r\n")}}):{name:b.name,value:c.replace(la,"\r\n")}}).get()}}),B.ajaxSettings.xhr=function(){try{return new XMLHttpRequest}catch(a){}};var oa=0,pa={},qa={0:200,1223:204},ra=B.ajaxSettings.xhr();a.ActiveXObject&&B(a).on("unload",function(){for(var a in pa)pa[a]()}),y.cors=!!ra&&"withCredentials"in ra,y.ajax=ra=!!ra,B.ajaxTransport(function(a){var b;return y.cors||ra&&!a.crossDomain?{send:function(c,d){var e,f=a.xhr(),g=++oa;if(f.open(a.type,a.url,a.async,a.username,a.password),a.xhrFields)for(e in a.xhrFields)f[e]=a.xhrFields[e];a.mimeType&&f.overrideMimeType&&f.overrideMimeType(a.mimeType),a.crossDomain||c["X-Requested-With"]||(c["X-Requested-With"]="XMLHttpRequest");for(e in c)f.setRequestHeader(e,c[e]);b=function(a){return function(){b&&(delete pa[g],b=f.onload=f.onerror=null,"abort"===a?f.abort():"error"===a?d(f.status,f.statusText):d(qa[f.status]||f.status,f.statusText,"string"==typeof f.responseText?{text:f.responseText}:void 0,f.getAllResponseHeaders()))}},f.onload=b(),f.onerror=b("error"),b=pa[g]=b("abort");try{f.send(a.hasContent&&a.data||null)}catch(h){if(b)throw h}},abort:function(){b&&b()}}:void 0}),B.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/(?:java|ecma)script/},converters:{"text script":function(a){return B.globalEval(a),a}}}),B.ajaxPrefilter("script",function(a){void 0===a.cache&&(a.cache=!1),a.crossDomain&&(a.type="GET")}),B.ajaxTransport("script",function(a){if(a.crossDomain){var b,c;return{send:function(d,e){b=B("