├── .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;g