Compare commits
	
		
			176 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 291bfd6a79 | |||
| def91fb60d | |||
| 738573a79c | |||
| 58f245f90c | |||
| c5735f402c | |||
| db43f09ce2 | |||
| e7ffe06d9d | |||
| 3d9d7b00d7 | |||
| cf5c106f64 | |||
| f4b3dbd495 | |||
| 6f6d07e670 | |||
| 23db17a31e | |||
| d87645d135 | |||
| 989dbfb150 | |||
| 36fe8e2a80 | |||
| ff6d9665e2 | |||
| 2587d03860 | |||
| f14f42404e | |||
| 0aa85baf6d | |||
| 39c0c775ed | |||
| 2bfc752ea1 | |||
| d7c4d0ff13 | |||
| 18170e94f4 | |||
| ff76a4116e | |||
| c9c45ebe4e | |||
| d84b7bd6ea | |||
| e78999d4a1 | |||
| 687391e56b | |||
| 6a131f6650 | |||
| 9f48a44958 | |||
| be9e8852b8 | |||
| d015e66f17 | |||
| 77c64df163 | |||
| fcca994c65 | |||
| e27af15485 | |||
| c59e23d114 | |||
| 6225b1e2fe | |||
| 9298776620 | |||
| 2c0b757c13 | |||
| f6017e7e49 | |||
| 4da61d835e | |||
| 215d3f0e92 | |||
| bc82bb6f1b | |||
| d13728dd3d | |||
|  | 9ac5c3ed89 | ||
| c2ec21c446 | |||
| a7a9a16847 | |||
| 82ed16e162 | |||
| 1e459ce186 | |||
| e6fa1d5314 | |||
| b7aa754c48 | |||
| 23d599835b | |||
| 0a980fd560 | |||
|  | f79fa608d7 | ||
| a4b7833989 | |||
|  | 2438081487 | ||
|  | c77d280b00 | ||
|  | 69a8e6ec01 | ||
|  | 19af663368 | ||
|  | bcf2f315b8 | ||
|  | 9a8d742ff1 | ||
|  | 1d3f4ca8bd | ||
|  | 6b0470607c | ||
|  | bfd9e0ce1c | ||
|  | 19b0e7a5d2 | ||
|  | e99b9ff4d6 | ||
|  | 83030fb416 | ||
|  | 523f57944f | ||
|  | dedd851ff9 | ||
|  | 4d4d1af45d | ||
|  | 69d5cb382a | ||
|  | 6acb027b3a | ||
|  | 081b2a23de | ||
|  | 815ba04d37 | ||
|  | d7d07b841a | ||
|  | 26e9a1c08b | ||
|  | 704337e30b | ||
|  | db284fbf91 | ||
|  | 1fe8733a06 | ||
|  | 7bb0fca116 | ||
|  | a5742d1a2a | ||
|  | effee987be | ||
|  | bf71399d12 | ||
|  | 5f68ea19e2 | ||
|  | 8961a4e519 | ||
|  | 6b7fd877ec | ||
|  | 4d80074046 | ||
|  | b60e9b8fce | ||
|  | 9a7aa3261f | ||
|  | 80d462c231 | ||
|  | 937a681c5d | ||
|  | 5b11e2bca2 | ||
|  | 84505d1b0e | ||
|  | 52675f84c7 | ||
|  | 26ea3d931e | ||
|  | c250ab07f4 | ||
|  | ee631b97c7 | ||
|  | dac7c3936f | ||
|  | 0c0b85b1af | ||
|  | 9c093ca3a1 | ||
|  | 623d94e045 | ||
|  | 516eda4ea6 | ||
|  | 5d42f3e2cc | ||
|  | 2cc96fef6e | ||
|  | 39b8e19bae | ||
|  | e42079d856 | ||
|  | 69add2a80f | ||
|  | 197c0fdcb2 | ||
|  | 84a574e31b | ||
|  | 39c18ab184 | ||
|  | 5a5488f504 | ||
|  | 1993853d0d | ||
|  | 28dbf9ab23 | ||
|  | 1ca6f0a324 | ||
|  | c38554a9dd | ||
|  | 7f1e67aaee | ||
|  | e930881e0f | ||
|  | 3b34173ac8 | ||
|  | 146891a618 | ||
|  | 35405f8612 | ||
|  | 9574d9b982 | ||
|  | 9f9610b6f5 | ||
|  | 186c0ea45a | ||
|  | 9cb2ad036b | ||
|  | 1a43a58af1 | ||
|  | 3879129674 | ||
|  | d5befcaa39 | ||
|  | 33a0362524 | ||
|  | 5163463dd2 | ||
|  | 4a237b0703 | ||
|  | 1d639dc080 | ||
|  | 26be6411b5 | ||
|  | a3b038ffdc | ||
|  | 2bf75a7429 | ||
|  | 30d9c2e8b0 | ||
|  | b9664e4e65 | ||
|  | b811a242b4 | ||
|  | fe62cbc5e1 | ||
|  | f10dee9167 | ||
|  | 32609e20fa | ||
|  | e39492fd4c | ||
|  | 7908154372 | ||
|  | 239980e5c2 | ||
|  | fbd53e486f | ||
|  | ec09adcf60 | ||
|  | 372f633625 | ||
|  | 985c65483a | ||
|  | 5e10e1893d | ||
|  | 0562b58761 | ||
|  | 91cd5d87fd | ||
|  | 45f8f640c8 | ||
|  | ac47b7314d | ||
|  | b4804b4c97 | ||
|  | 181027a07f | ||
|  | 90e42e13d4 | ||
|  | ec33e667b3 | ||
|  | 672662271d | ||
|  | 0a0a5041b7 | ||
|  | 87ba1e4298 | ||
|  | c4cc619928 | ||
|  | 5e6dc31c35 | ||
|  | daa92fa829 | ||
|  | 0aa1f614fa | ||
|  | 80217fd39b | ||
|  | 4fd5aa05de | ||
|  | 7741621c16 | ||
|  | 652b59fad3 | ||
|  | faf6814a53 | ||
|  | ebb44c3dcb | ||
|  | c2bb0afb67 | ||
|  | a4f29edf4e | ||
|  | 3a805d071a | ||
|  | 9969c4dba9 | ||
|  | f72c1a333c | ||
|  | abb788780d | ||
|  | 02bb01fdf4 | 
							
								
								
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -1 +1,5 @@ | ||||
| prefactor | ||||
| .well-known | ||||
| node_modules/ | ||||
| DS_Store | ||||
| .vscode | ||||
|  | ||||
							
								
								
									
										16
									
								
								.jshintrc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								.jshintrc
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,16 @@ | ||||
| { "node": true | ||||
| , "browser": true | ||||
| , "jquery": true | ||||
| , "strict": true | ||||
| , "indent": 2 | ||||
| , "onevar": true | ||||
| , "laxcomma": true | ||||
| , "laxbreak": true | ||||
| , "eqeqeq": true | ||||
| , "immed": true | ||||
| , "undef": true | ||||
| , "unused": true | ||||
| , "latedef": true | ||||
| , "curly": true | ||||
| , "trailing": true | ||||
| } | ||||
| @ -1 +1 @@ | ||||
| well-known | ||||
| _apis | ||||
							
								
								
									
										7
									
								
								CHANGELOG
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								CHANGELOG
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | ||||
| v1.2.2 - Works in browsers and node.js for some oauth3 exchanges | ||||
|   * Resource Owner Password | ||||
|   * Implicit Grant | ||||
|   * Client-side public/private keypair generation | ||||
|   * Server-side public key authentication | ||||
|   * Server-side grant storage | ||||
|   * BUG: Does not support app:// urls | ||||
							
								
								
									
										41
									
								
								LICENSE
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								LICENSE
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,41 @@ | ||||
| Copyright 2017 Daplie, Inc | ||||
| 
 | ||||
| This is open source software; you can redistribute it and/or modify it under the | ||||
| terms of either: | ||||
| 
 | ||||
|    a) the "MIT License" | ||||
|    b) the "Apache-2.0 License" | ||||
| 
 | ||||
| MIT License | ||||
| 
 | ||||
|    Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
|    of this software and associated documentation files (the "Software"), to deal | ||||
|    in the Software without restriction, including without limitation the rights | ||||
|    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
|    copies of the Software, and to permit persons to whom the Software is | ||||
|    furnished to do so, subject to the following conditions: | ||||
| 
 | ||||
|    The above copyright notice and this permission notice shall be included in all | ||||
|    copies or substantial portions of the Software. | ||||
| 
 | ||||
|    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
|    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
|    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
|    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
|    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
|    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
|    SOFTWARE. | ||||
| 
 | ||||
| Apache-2.0 License Summary | ||||
| 
 | ||||
|    Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|    you may not use this file except in compliance with the License. | ||||
|    You may obtain a copy of the License at | ||||
| 
 | ||||
|      http://www.apache.org/licenses/LICENSE-2.0 | ||||
| 
 | ||||
|    Unless required by applicable law or agreed to in writing, software | ||||
|    distributed under the License is distributed on an "AS IS" BASIS, | ||||
|    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|    See the License for the specific language governing permissions and | ||||
|    limitations under the License. | ||||
							
								
								
									
										158
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										158
									
								
								README.md
									
									
									
									
									
								
							| @ -1,6 +1,12 @@ | ||||
| oauth3.js | ||||
| ========= | ||||
| 
 | ||||
| | *oauth3.js* | ||||
| | [issuer.html](https://git.oauth3.org/OAuth3/issuer.html) | ||||
| | [issuer.rest.walnut.js](https://git.oauth3.org/OAuth3/issuer.rest.walnut.js) | ||||
| | [issuer.srv](https://git.oauth3.org/OAuth3/issuer.srv) | ||||
| | Sponsored by [ppl](https://ppl.family) | ||||
| 
 | ||||
| The world's smallest, fastest, and most secure OAuth3 (and OAuth2) JavaScript implementation | ||||
| (Yes! works in browsers and node.js with no extra dependencies or bloat and no hacks!) | ||||
| 
 | ||||
| @ -19,13 +25,12 @@ If you have no idea what you're doing | ||||
| 
 | ||||
| 1. Create a folder for your project named after your app, such as `example.com/` | ||||
| 2. Inside of the folder `example.com/` a folder called `assets/` | ||||
| 3. Inside of the folder `example.com/assets` a folder called `org.oauth3/` | ||||
| 4. Download [oauth.js-v1.zip](https://git.daplie.com/Daplie/oauth3.js/repository/archive.zip?ref=v1) | ||||
| 3. Inside of the folder `example.com/assets` a folder called `oauth3.org/` | ||||
| 4. Download [oauth3.js-v1.zip](https://git.oauth3.org/OAuth3/oauth3.js/repository/archive.zip?ref=v1) | ||||
| 5. Double-click to unzip the folder. | ||||
| 6. Copy the file `oauth3.core.js` into the folder `example.com/assets/org.oauth3/` | ||||
| 7. Copy the folder `well-known` into the folder `example.com/` | ||||
| 8. Rename the folder `well-known` to `.well-known` (when you do this, it become invisible, that's okay) | ||||
| 9. Add `<script src="assets/org.oauth3/oauth3.core.js"></script>` to your `index.html` | ||||
| 6. Copy the file `oauth3.core.js` into the folder `example.com/assets/oauth3.org/` | ||||
| 7. Copy the folder `_apis` into the folder `example.com/` | ||||
| 9. Add `<script src="assets/oauth3.org/oauth3.core.js"></script>` to your `index.html` | ||||
| 9. Add `<script src="app.js"></script>` to your `index.html` | ||||
| 10. Create files in `example.com` called `app.js` and `index.html` and put this in it: | ||||
| 
 | ||||
| @ -44,7 +49,7 @@ If you have no idea what you're doing | ||||
|   <script src="https://code.jquery.com/jquery-3.1.1.js" | ||||
|     integrity="sha256-16cdPddA6VdVInumRGo6IbivbERE8p7CQR3HzTBuELA=" | ||||
|     crossorigin="anonymous"></script> | ||||
|   <script src="assets/org.oauth3/oauth3.core.js"></script> | ||||
|   <script src="assets/oauth3.org/oauth3.core.js"></script> | ||||
|   <script src="app.js"></script> | ||||
| </body> | ||||
| </html> | ||||
| @ -53,15 +58,15 @@ If you have no idea what you're doing | ||||
| `app.js`: | ||||
| ```js | ||||
| var OAUTH3 = window.OAUTH3; | ||||
| var auth = OAUTH3.create(window.location); // use window.location to set Client URI (your app's id) | ||||
| var oauth3 = OAUTH3.create(window.location); // use window.location to set Client URI (your app's id) | ||||
| 
 | ||||
| 
 | ||||
| // this is any OAuth3-compatible provider, such as oauth3.org | ||||
| // in v1.1.0 we'll add backwards compatibility for facebook.com, google.com, etc | ||||
| // | ||||
| function onChangeProvider(_providerUri) { | ||||
| function onChangeProvider(providerUri) { | ||||
|   // example https://oauth3.org | ||||
|   return auth.setProvider(providerUri); | ||||
|   return oauth3.setIdentityProvider(providerUri); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| @ -69,7 +74,7 @@ function onChangeProvider(_providerUri) { | ||||
| // | ||||
| function onClickLogin() { | ||||
| 
 | ||||
|   return auth.authenticate().then(function (session) { | ||||
|   return oauth3.authenticate().then(function (session) { | ||||
| 
 | ||||
|     console.info('Authentication was Successful:'); | ||||
|     console.log(session); | ||||
| @ -80,12 +85,14 @@ function onClickLogin() { | ||||
|     // | ||||
|     console.info('Secure PPID (aka subject):', session.token.sub); | ||||
| 
 | ||||
|     return auth.request({ | ||||
|       url: 'https://oauth3.org/api/org.oauth3.provider/inspect' | ||||
|     return oauth3.request({ | ||||
|       url: 'https://api.oauth3.org/api/issuer@oauth3.org/jwks/:sub/:kid' | ||||
|         .replace(/:sub/g, session.token.sub) | ||||
|         .replace(/:kid/g, session.token.kid || session.token.iss) | ||||
|     , session: session | ||||
|     }).then(function (resp) { | ||||
| 
 | ||||
|       console.info("Inspect Token:"); | ||||
|       console.info("Signing Public Key JWK:"); | ||||
|       console.log(resp.data); | ||||
| 
 | ||||
|     }); | ||||
| @ -102,7 +109,7 @@ function onClickLogin() { | ||||
| // | ||||
| function onClickLogout() { | ||||
| 
 | ||||
|   return auth.logout().then(function () { | ||||
|   return oauth3.logout().then(function () { | ||||
|     localStorage.clear(); | ||||
| 
 | ||||
|     console.info('Logout was Successful'); | ||||
| @ -138,13 +145,13 @@ it might look like this: | ||||
| example.com | ||||
| │ | ||||
| │ | ||||
| ├── .well-known (hidden) | ||||
| │   └── oauth3 | ||||
| ├── _apis | ||||
| │   └── oauth3.org | ||||
| │       ├── callback.html | ||||
| │       ├── directives.json | ||||
| │       └── index.html | ||||
| ├── assets | ||||
| │   └── org.oauth3 | ||||
| │   └── oauth3.org | ||||
| │       └── oauth3.core.js | ||||
| │ | ||||
| │ | ||||
| @ -165,17 +172,17 @@ Installation (if you know what you're doing) | ||||
| pushd /path/to/your/web/app | ||||
| 
 | ||||
| 
 | ||||
| # clone the project as assets/org.oauth3 | ||||
| # clone the project as assets/oauth3.org | ||||
| mkdir -p assets | ||||
| git clone git@git.daplie.com:Daplie/oauth3.js.git assets/org.oauth3 | ||||
| pushd assets/org.oauth3 | ||||
| git clone git@git.oauth3.org:OAuth3/oauth3.js.git assets/oauth3.org | ||||
| pushd assets/oauth3.org | ||||
| git checkout v1 | ||||
| popd | ||||
| 
 | ||||
| 
 | ||||
| # symlink `.well-known/oauth3` to `assets/org.oauth3/.well-known/oauth3` | ||||
| mkdir -p .well-known | ||||
| ln -sf  ../assets/org.oauth3/.well-known/oauth3 .well-known/oauth3 | ||||
| # symlink `_apis/oauth3.org` to `assets/oauth3.org/_apis/oauth3.org` | ||||
| mkdir -p _apis | ||||
| ln -sf  ../assets/oauth3.org/_apis/oauth3 _apis/oauth3.org | ||||
| ``` | ||||
| 
 | ||||
| **Advanced Installation with `bower`** | ||||
| @ -185,17 +192,17 @@ ln -sf  ../assets/org.oauth3/.well-known/oauth3 .well-known/oauth3 | ||||
| bower install oauth3 | ||||
| 
 | ||||
| 
 | ||||
| # create a `.well-known` folder and an `assets` folder | ||||
| mkdir -p .well-known assets | ||||
| # create a `_apis` folder and an `assets` folder | ||||
| mkdir -p _apis assets | ||||
| 
 | ||||
| 
 | ||||
| # symlink `.well-known/oauth3` to `bower_components/oauth3/.well-known/oauth3` | ||||
| ln -sf  ../bower_components/oauth3/.well-known/oauth3 .well-known/oauth3 | ||||
| # symlink `_apis/oauth3.org` to `bower_components/oauth3.org/_apis/oauth3.org` | ||||
| ln -sf  ../bower_components/oauth3.org/_apis/oauth3.org _apis/oauth3.org | ||||
| 
 | ||||
| 
 | ||||
| # symlink `assets/org.oauth3` to `bower_components/oauth3` | ||||
| ln -sf  ../bower_components/oauth3/.well-known/oauth3 .well-known/oauth3 | ||||
| ln -sf  ../bower_components/oauth3 assets/org.oauth3 | ||||
| # symlink `assets/oauth3.org` to `bower_components/oauth3.org` | ||||
| ln -sf  ../bower_components/oauth3.org/_apis/oauth3.org _apis/oauth3.org | ||||
| ln -sf  ../bower_components/oauth3.org assets/oauth3.org | ||||
| ``` | ||||
| 
 | ||||
| Usage | ||||
| @ -204,7 +211,7 @@ Usage | ||||
| Update your HTML to include the the following script tag: | ||||
| 
 | ||||
| ```html | ||||
| <script src="assets/org.oauth3/oauth3.core.js"></script> | ||||
| <script src="assets/oauth3.org/oauth3.core.js"></script> | ||||
| ``` | ||||
| 
 | ||||
| You can create a very simple demo application like this: | ||||
| @ -239,7 +246,7 @@ function onClickLogin() { | ||||
|     console.info('Secure PPID (aka subject):', session.token.sub); | ||||
| 
 | ||||
|     return OAUTH3.request({ | ||||
|       url: 'https://oauth3.org/api/org.oauth3.provider/inspect_token' | ||||
|       url: 'https://oauth3.org/api/issuer@oauth3.org/inspect_token' | ||||
|     , session: session | ||||
|     }).then(function (resp) { | ||||
| 
 | ||||
| @ -260,6 +267,18 @@ function onClickLogin() { | ||||
| onChangeProvider('oauth3.org'); | ||||
| ``` | ||||
| 
 | ||||
| A user's e-mail can be passed into the clientParams object as `clientParams.subject`. | ||||
| 
 | ||||
| To auto-populate the e-mail input of the login popup, make sure the input has the class `js-oauth3-email`. | ||||
| 
 | ||||
| Example: | ||||
| ```js | ||||
| if (clientParams.subject) { | ||||
|         $('.js-oauth3-email').val(clientParams.subject); | ||||
|         $('.js-authn-show').prop('disabled', false); | ||||
|       } | ||||
| ``` | ||||
| 
 | ||||
| ### Compatibility with Frameworks and Libraries | ||||
| 
 | ||||
| **jQuery**: | ||||
| @ -271,7 +290,19 @@ You're all set. Nothing else is needed. | ||||
| We've created an `Oauth3` service just for you: | ||||
| 
 | ||||
| ```html | ||||
| <script src="assets/org.oauth3/oauth3.ng.js"></script> | ||||
| <script src="assets/oauth3.org/oauth3.ng.js"></script> | ||||
| ``` | ||||
| 
 | ||||
| ```js | ||||
| // Require the module as 'oauth3.org' | ||||
| var app = angular.module('myAppName', [ 'ui.router', 'oauth3.org' ]); | ||||
| 
 | ||||
| // Require services and other submodules in the form {modulename}@oauth3.org | ||||
| app.controller('authCtrl', [ '$scope', 'azp@oauth3.org', function ($scope, Oauth3) { /* ... */ } ]); | ||||
| 
 | ||||
| // For backwards compatibility with older angular applications that rely on string-name introspection | ||||
| // you can also use the camel case version of the names in the format {Modulename}Oauth3 | ||||
| app.controller('authCtrl', function ($scope, AzpOauth3) { /* ... */ }); | ||||
| ``` | ||||
| 
 | ||||
| You can include that in addition to the standard file or, | ||||
| @ -284,31 +315,48 @@ We include a small wrapper function of just a few lines in the bottom of `oauth3 | ||||
| which exposes a `create` method to make using the underlying library require typing fewer keystrokes. | ||||
| 
 | ||||
| ``` | ||||
| auth = OAUTH3.create(location);                   // takes a location object, such as window.location | ||||
|                                                   // to create the Client URI (your app's id) | ||||
|                                                   // and save it to an internal state | ||||
| oauth3 = OAUTH3.create(location);                   // takes a location object, such as window.location | ||||
|                                                     // to create the Client URI (your app's id) | ||||
|                                                     // and save it to an internal state | ||||
| 
 | ||||
| promise = auth.init(location);                    // set and fetch your own site/app's configuration details | ||||
| // promises your site's config | ||||
| promise = oauth3.init(opts);                        // set and fetch your own site/app's configuration details | ||||
| // promises your site's config                      // opts = { location, session, issuer, audience } | ||||
| 
 | ||||
| promise = auth.setProvider(url);                  // changes the Provider URI (the site you're logging into), | ||||
| // promises the provider's config                 // gets the config for that site (from their .well-known/oauth3), | ||||
|                                                   // and caches it in internal state as the default | ||||
| promise = oauth3.setIdentityProvider(url);          // changes the Identity Provider URI (the site you're logging into), | ||||
| // promises the provider's config                   // gets the config for that site (from their _apis/oauth3.org), | ||||
|                                                     // and caches it in internal state as the default | ||||
| 
 | ||||
| promise = auth.authenticate();                    // opens login window for the provider and returns a session | ||||
|                                                   // (must be called after the setProvider promise has completed) | ||||
| promise = oauth3.setResourceProvider(url);          // changes the Resource Provider URI (the site you're getting stuff from) | ||||
| 
 | ||||
| promise = auth.authorize(permissions);            // authenticates (if not authenticated) and opens a window to | ||||
|                                                   // authorize a particular scope (contacts, photos, whatever) | ||||
| promise = oauth3.setProvider(url);                  // changes the both Identity and Resource Provider URI together | ||||
| 
 | ||||
| promise = auth.request({ url, method, data });    // make an (authorized) request to a provider's resource | ||||
|                                                   // (contacts, photos, whatever) | ||||
| promise = oauth3.authenticate();                    // opens login window for the provider and returns a session | ||||
|                                                     // (must be called after the setIdentityProvider promise has completed) | ||||
| 
 | ||||
| promise = auth.logout();                          // opens logout window for the provider | ||||
| promise = oauth3.authorize(permissions);            // authenticates (if not authenticated) and opens a window to | ||||
|                                                     // authorize a particular scope (contacts, photos, whatever) | ||||
| 
 | ||||
| auth.session();                                   // returns the current session, if any | ||||
| promise = oauth3.request({ url, method, data });    // make an (authorized) arbitrary request to an audience's resource | ||||
|                                                     // (contacts, photos, whatever) | ||||
| 
 | ||||
| promise = oauth3.api(apiname, opts);                // make an (authorized) well-known api call to an audience | ||||
|                                                     // Ex: oauth3.api('dns.list', { sld: 'example', tld: 'com' }); | ||||
| 
 | ||||
| // TODO | ||||
| api = await oauth3.package(audience, schemaname);   // make an (authorized) well-known api call to an audience | ||||
|                                                     // Ex: api = await oauth3.package('domains.example.com', 'dns@oauth3.org'); | ||||
|                                                     //     api.list({ sld: 'mydomain', tld: 'com' }); | ||||
| 
 | ||||
| 
 | ||||
| promise = oauth3.logout();                          // opens logout window for the provider | ||||
| 
 | ||||
| oauth3.session();                                   // returns the current session, if any | ||||
| ``` | ||||
| 
 | ||||
| <!-- TODO | ||||
| Track down the old https://labs.daplie.com/docs/ for API schemas | ||||
| -- | ||||
| 
 | ||||
| 
 | ||||
| Real API | ||||
| ---------- | ||||
| @ -436,10 +484,10 @@ Since we do not require the `protocol` to be specified, it is a URI | ||||
| 
 | ||||
| However, we do have a problem of disambiguation since a URI may look like a `path`: | ||||
| 
 | ||||
| 1. https://example.com/api/org.oauth3.provider | ||||
| 2. example.com/api/org.oauth.provider/ (not unique) | ||||
| 3. /api/org.oauth3.provider | ||||
| 4. api/org.oauth3.provider (not unique) | ||||
| 1. https://example.com/api/issuer@oauth3.org | ||||
| 2. example.com/api/issuer@oauth3.org/ (not unique) | ||||
| 3. /api/issuer@oauth3.org | ||||
| 4. api/issuer@oauth3.org (not unique) | ||||
| 
 | ||||
| Therefore anywhere a URI or a Path could be used, the URI must be a URL. | ||||
| We eliminate #2. | ||||
| @ -450,5 +498,5 @@ can be very ugly and confusing and we definitely need to allow relative paths. | ||||
| 
 | ||||
| A potential work-around would be to assume all paths are relative (eliminate #4 instead) | ||||
| and have the path always key off of the base URL - if oauth3 directives are to be found at | ||||
| https://example.com/username/.well-known/oauth3/directives.json then /api/whatever would refer | ||||
| https://example.com/username/_apis/oauth3.org/index.json then /api/whatever would refer | ||||
| to https://example.com/username/api/whatever. | ||||
|  | ||||
							
								
								
									
										
											BIN
										
									
								
								_apis/oauth3/blank.gif
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								_apis/oauth3/blank.gif
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 43 B | 
| @ -15,7 +15,7 @@ | ||||
| 
 | ||||
|         <!-- TODO permanently cache with appcache (or service worker?) --> | ||||
|         <!-- TODO slim this all down to a single file --> | ||||
|         <script src="/assets/org.oauth3/oauth3.core.js"></script> | ||||
|         <script src="../../assets/oauth3.org/oauth3.core.js"></script> | ||||
|         <script> | ||||
|           ;(function () { | ||||
|             'use strict'; | ||||
							
								
								
									
										
											BIN
										
									
								
								_apis/oauth3/clear.gif
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								_apis/oauth3/clear.gif
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 43 B | 
							
								
								
									
										12
									
								
								_apis/oauth3/directives.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								_apis/oauth3/directives.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | ||||
| { "terms": [ "oauth3.org/tos/draft" ] | ||||
| , "api": "api.:hostname" | ||||
| , "authorization_dialog": { "url": "#/authorization_dialog" } | ||||
| , "access_token":   { "method": "POST", "url": "api/issuer@oauth3.org/access_token" } | ||||
| , "otp":            { "method": "POST", "url": "api/issuer@oauth3.org/access_token/send_otp" } | ||||
| , "credential_otp": { "method": "POST", "url": "api/issuer@oauth3.org/access_token/send_otp" } | ||||
| , "grants":         { "method": "GET",  "url": "api/issuer@oauth3.org/grants/:sub/:azp" } | ||||
| , "publish_jwk":    { "method": "POST", "url": "api/issuer@oauth3.org/jwks/:sub" } | ||||
| , "retrieve_jwk":   { "method": "GET",  "url": "api/issuer@oauth3.org/jwks/:sub/:kid.json" } | ||||
| , "callback":       { "method": "GET",  "url": ".well-known/oauth3/callback.html#/" } | ||||
| , "logout":         { "method": "GET",  "url": "#/logout/" } | ||||
| } | ||||
							
								
								
									
										140
									
								
								_apis/oauth3/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								_apis/oauth3/index.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,140 @@ | ||||
| <!DOCTYPE html> | ||||
| <html> | ||||
|   <head> | ||||
|     <style> | ||||
|       body { | ||||
|         background-color: #ffcccc; | ||||
|       } | ||||
|     </style> | ||||
|   </head> | ||||
|   <body> | ||||
|   OAuth3 RPC | ||||
| 
 | ||||
|   <script src="../../assets/oauth3.org/oauth3.core.js"></script> | ||||
|   <script> | ||||
|     ;(function () { | ||||
|     'use strict'; | ||||
| 
 | ||||
|     // Taken from oauth3.core.js | ||||
| 
 | ||||
|     // TODO what about search within hash? | ||||
|     var prefix = "(" + window.location.hostname + ") [.well-known/oauth3/]"; | ||||
|     var params = OAUTH3.query.parse(window.location.hash || window.location.search); | ||||
|     var urlsafe64; | ||||
|     var redirect; | ||||
|     var err; | ||||
|     var oldRpc; | ||||
|     var sub = params.sub || params.subject; | ||||
|     var subData; | ||||
| 
 | ||||
|     function doRedirect(redirect) { | ||||
|       if (params.debug) { | ||||
|         console.log(prefix, 'params.redirect_uri:', params.redirect_uri); | ||||
|         console.log(prefix, 'redirect'); | ||||
|         console.log(redirect); | ||||
|       } | ||||
| 
 | ||||
|       if (!params.debug) { | ||||
|         window.location = redirect; | ||||
|       } else { | ||||
|         // yes, we're violating the security lint with purpose | ||||
|         document.body.innerHTML += window.location.host + window.location.pathname | ||||
|           + '<br/><br/>You\'ve passed the \'debug\' parameter so we\'re pausing' | ||||
|           + ' to let you look at logs or whatever it is that you intended to do.' | ||||
|           + '<br/><br/>Continue with redirect: <a href="' + redirect + '">' + redirect + '</' + 'a>'; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     function onError(err) { | ||||
|       var redirect = params.redirect_uri + '?' + OAUTH3.query.stringify({ | ||||
|         state: params.state | ||||
|       , error: err.code | ||||
|       , error_description: err.message | ||||
|       , error_uri: err.uri | ||||
|       , debug: params.debug || undefined | ||||
|       }); | ||||
| 
 | ||||
|       doRedirect(redirect); | ||||
|     } | ||||
| 
 | ||||
|     function onSuccess(urlsafe64, hasSub) { | ||||
|       if (params.debug) { | ||||
|         console.log(prefix, 'directives'); | ||||
|         console.log(resp); | ||||
| 
 | ||||
|         console.log(prefix, 'base64'); | ||||
|         console.log(urlsafe64); | ||||
|       } | ||||
| 
 | ||||
|       // TODO try postMessage back to redirect_uri domain right here | ||||
|       // window.postMessage(); | ||||
| 
 | ||||
|       // TODO SECURITY make sure it's https NOT http | ||||
|       // NOTE: this can be only up to 2,083 characters | ||||
|       redirect = params.redirect_uri + '?' + OAUTH3.query.stringify({ | ||||
|         state: params.state | ||||
|       , directives: oldRpc ? urlsafe64 : undefined | ||||
|       , data: !oldRpc ? urlsafe64 : undefined | ||||
|       , sub: hasSub && sub || undefined | ||||
|       , debug: params.debug || undefined | ||||
|       }); | ||||
| 
 | ||||
|       doRedirect(redirect); | ||||
|     } | ||||
| 
 | ||||
|     if (params.debug) { | ||||
|       console.warn(prefix, "DEBUG MODE ENABLED. Automatic redirects disabled."); | ||||
| 
 | ||||
|       console.log(prefix, 'hash||search:'); | ||||
|       console.log(window.location.hash || window.location.search); | ||||
| 
 | ||||
|       console.log(prefix, 'params:'); | ||||
|       console.log(params); | ||||
|     } | ||||
| 
 | ||||
|     if ('rpc' !== params.response_type) { | ||||
|       err = new Error("response_type '" + params.response_type + "' is not supported"); | ||||
|       err.code = "E_RESPONSE_TYPE"; | ||||
|       // TODO err.uri | ||||
|       onError(err); | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     if (params.action) { | ||||
|       oldRpc = true; | ||||
|     } | ||||
| 
 | ||||
|     var loco = window.location.href.replace(/\/\.well-known.*/, ''); | ||||
|     //var loco = 'sso.hellabit.com'; | ||||
|     var resp; | ||||
|     if (/localstorage/i.test(params._scheme)) { | ||||
|       if (sub) { | ||||
|         subData = localStorage.getItem(sub + '@oauth3.org:issuer'); | ||||
|       } | ||||
|       resp = subData || localStorage.getItem('oauth3.org:issuer') || loco; | ||||
|       onSuccess(resp, subData && true); | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     var fileWhiteList = [ | ||||
|       '.well-known/oauth3/directives.json' | ||||
|     , '.well-known/oauth3/scopes.json' | ||||
|     ]; | ||||
| 
 | ||||
|     if (-1 === fileWhiteList.indexOf(params._pathname)) { | ||||
|       err = new Error("No access to requested file: " + params._pathname); | ||||
|       err.code = "E_ACCESS_DENIED" | ||||
|       // TODO err.uri | ||||
|       onError(err); | ||||
|     } | ||||
| 
 | ||||
|     OAUTH3.request({ url: params._pathname.replace(/^\.well-known\/oauth3\//, '') }).then(function (resp) { | ||||
|       urlsafe64 = OAUTH3._base64.encodeUrlSafe(JSON.stringify(resp.data, null, 0)); | ||||
| 
 | ||||
|       onSuccess(urlsafe64); | ||||
|     }); | ||||
| 
 | ||||
|     }()); | ||||
|   </script> | ||||
|   </body> | ||||
| </html> | ||||
							
								
								
									
										26
									
								
								_apis/oauth3/scopes.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								_apis/oauth3/scopes.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,26 @@ | ||||
| { | ||||
| 
 | ||||
|       "oauth3_authn": "Basic secure authentication" | ||||
|     , "auth@oauth3.org": "Basic secure authentication" | ||||
|     , "wallet": "Access to payments and subscriptions" | ||||
|     , "bucket": "Access to file storage" | ||||
|     , "db": "Access to app data" | ||||
|     , "domains": "Domain registration (and Glue and NS records)" | ||||
|     , "domains@oauth3.org": "Domain registration (and Glue and NS records)"  | ||||
|     , "domains:glue": "Glue Record management (for vanity nameservers)" | ||||
|     , "domains:ns": "Name Server management" | ||||
|     , "dns": "DNS records (A/AAAA, TXT, SRV, MX, etc)" | ||||
| 
 | ||||
|     , "hello@example.com": "Hello World Example Access" | ||||
|     , "authn@oauth3.org": "Basic secure authentication" | ||||
|     , "wallet@oauth3.org": "Access to payments and subscriptions" | ||||
|     , "bucket@oauth3.org": "Access to file storage" | ||||
|     , "db@oauth3.org": "Access to app data" | ||||
|     , "domains@oauth3.org": "Domain registration (and Glue and NS records)"  | ||||
|     , "domains:glue@oauth3.org": "Glue Record management (for vanity nameservers)" | ||||
|     , "domains:ns@oauth3.org": "Name Server management" | ||||
|     , "dns@oauth3.org": "DNS records (A/AAAA, TXT, SRV, MX, etc)" | ||||
|     , "www@daplie.com": "Websites and webapps" | ||||
| 
 | ||||
|     , "*": "FULL ACCOUNT ACCESS" | ||||
|     } | ||||
							
								
								
									
										217
									
								
								bin/cli.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										217
									
								
								bin/cli.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,217 @@ | ||||
| 'use strict'; | ||||
| 
 | ||||
| var oauth3 = require('./oauth3.js'); | ||||
| var defaults = { | ||||
|   main: 'oauth3' | ||||
| , provider: 'oauth3.org' | ||||
| }; | ||||
| 
 | ||||
| function parseArgs(argv, opts) { | ||||
|   var args = Array.prototype.slice.call(argv); | ||||
|   var sep = /[:\.\-]/; | ||||
| 
 | ||||
|   args.shift(); // 'node' is the first parameter
 | ||||
|   args.shift(); // 'oauth3.js' will be the
 | ||||
| 
 | ||||
|   var command = args.shift() || 'help'; | ||||
|   var cmdpair = command.split(sep); | ||||
|   var cmd = cmdpair[0]; | ||||
|   var sub = cmdpair[1]; | ||||
|   var COMMAND = 'COMMAND'; | ||||
|   var maxCmdLen = COMMAND.length; | ||||
|   var maxPairLen = 0; | ||||
|   var arg1 = args[0]; | ||||
| 
 | ||||
|   // build top-level commands (tlcs) list
 | ||||
|   // also count the word-width (for the space needed to print the commands)
 | ||||
|   var pairsMap = {}; | ||||
|   var tlcs = opts.commands.filter(function (desc) { | ||||
|     var pair = desc[0].split(/\s+/)[0]; | ||||
|     var psub = pair.split(sep)[0]; | ||||
|     pairsMap[pair] = true; | ||||
|     maxPairLen = Math.max(maxPairLen, pair.length); | ||||
|     if (pair === psub) { | ||||
|       maxCmdLen = Math.max(maxCmdLen, psub.length); | ||||
|       return true; | ||||
|     } | ||||
|   }); | ||||
| 
 | ||||
|   // right pad (for making the printed lines longer)
 | ||||
|   function rpad(str, len) { | ||||
|     while (str.length < len) { | ||||
|       str += ' '; | ||||
|     } | ||||
|     return str; | ||||
|   } | ||||
| 
 | ||||
|   // oauth3.js help
 | ||||
|   // oauth3.js help <command>
 | ||||
|   // oauth3.js help <command:sub> (alias of `oauth3.js <command:sub> --help')
 | ||||
|   function help() { | ||||
|     var status = 0; | ||||
| 
 | ||||
|     function printCmd(desc) { | ||||
|       var pcmd = rpad(desc[0].split(/\s+/)[0], maxCmdLen); | ||||
|       var pdesc = desc[1]; | ||||
|       console.info('\t' + defaults.main + ' ' + pcmd, ' # ' + pdesc); | ||||
|     } | ||||
| 
 | ||||
|     function printCmds(cmds) { | ||||
|       console.info(''); | ||||
| 
 | ||||
|       var title = defaults.main + ' ' + rpad(COMMAND, maxCmdLen) + '  # description'; | ||||
|       var bars = title.replace(/./g, '-').split(''); | ||||
|       bars[bars.length - ' # description'.length] = ' '; | ||||
|       bars[bars.length - (' # description'.length + 1)] = ' '; | ||||
|       console.info('\t' + title); | ||||
|       console.info('\t' + bars.join('')); | ||||
|       cmds.forEach(printCmd); | ||||
|       console.info(''); | ||||
|     } | ||||
| 
 | ||||
|     function helpMain() { | ||||
|       console.info(''); | ||||
|       console.info('Here are all the top-level commands:'); | ||||
| 
 | ||||
|       printCmds(tlcs); | ||||
|     } | ||||
| 
 | ||||
|     if (arg1 && -1 === Object.keys(pairsMap).indexOf(arg1)) { | ||||
|       status = 1; | ||||
|       console.info(''); | ||||
|       console.info(defaults.main + ": Unknown command '" + arg1 + "'"); | ||||
|       console.info(''); | ||||
|       console.info("Try '" + defaults.main + " help'"); | ||||
|       console.info(''); | ||||
|       arg1 = null; | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     // the case of "oauth3 help --something"
 | ||||
|     if (!arg1 || '-' === arg1[0]) { | ||||
|       helpMain(); | ||||
|       process.exit(status); | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     // the case of "oauth3 help help"
 | ||||
|     if ('help' === arg1) { | ||||
|       helpMain(); | ||||
|       console.info("no more help available for 'help'"); | ||||
|       process.exit(status); | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     // matches the first part of the command
 | ||||
|     // and has second parts
 | ||||
|     if (arg1 === arg1.split(':')[0] && opts.commands.filter(function (desc) { | ||||
|       return arg1 === desc[0].split(/\s+/)[0].split(':')[0] && desc[0].split(/\s+/)[0].split(':'); | ||||
|     }).length > 1) { | ||||
|       console.info(''); | ||||
|       console.info("Here are all the '" + command + "'-related commands:"); | ||||
|       printCmds( | ||||
|         opts.commands.filter(function (desc) { | ||||
|           var pair = desc[0].split(/\s+/)[0]; | ||||
|           var psub = pair.split(sep)[0]; | ||||
|           maxPairLen = Math.max(maxPairLen, pair.length); | ||||
|           if (arg1 === psub || arg1 === pair) { | ||||
|             maxCmdLen = Math.max(maxCmdLen, pair.length); | ||||
|             return true; | ||||
|           } | ||||
|         }) | ||||
|       ); | ||||
|       console.info(''); | ||||
|     } else { | ||||
|       console.info(''); | ||||
|       console.info("Here are all the options and flags for '" + arg1 + "':"); | ||||
|       console.info(''); | ||||
|       opts.commands.some(function (desc) { | ||||
|         var pair = desc[0].split(/\s+/)[0]; | ||||
|         var psub = pair.split(sep)[0]; | ||||
|         maxPairLen = Math.max(maxPairLen, pair.length); | ||||
|         if (arg1 !== psub && arg1 !== pair) { | ||||
|           return false; | ||||
|         } | ||||
|         maxCmdLen = Math.max(maxCmdLen, pair.length); | ||||
|         console.log('\t' + desc[0] + '\t# ' + desc[1]); | ||||
|         (desc[2]||[]).forEach(function (flag) { | ||||
|           var pair = flag.split(', '); | ||||
|           var f = pair.shift(); | ||||
|           var d = pair.join(', '); | ||||
|           console.log('\t\t' + f + ' # ' + d); | ||||
|         }); | ||||
|         return true; | ||||
|       }); | ||||
|       console.info(''); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   // If the command is not in the list of commands
 | ||||
|   if (-1 === Object.keys(pairsMap).indexOf(cmd)) { | ||||
|     arg1 = cmd; | ||||
|     cmd = 'help'; | ||||
|     help(); | ||||
|     return; | ||||
|   } | ||||
| 
 | ||||
|   // If help is explictly requested
 | ||||
|   if (-1 !== [ 'help', '-h', '--help' ].indexOf(command) || -1 !== args.indexOf('-h') || -1 !== args.indexOf('--help')) { | ||||
|     help(); | ||||
|     return; | ||||
|   } | ||||
| 
 | ||||
|   // If we're ready to rock and roll!
 | ||||
|   console.log('RUN', cmd, sub || '(n/a)', arg1 || '(n/a)', '... not yet implemented'); | ||||
| } | ||||
| 
 | ||||
| parseArgs(process.argv, { | ||||
|   // CLI goals:
 | ||||
|   //
 | ||||
|   // whoami / login: you are now logged in as
 | ||||
|   //   * john@example.com [current] (just now)
 | ||||
|   //   * john@work.net (2 minutes ago)
 | ||||
|   //   * john@family.me (2 weeks ago)
 | ||||
|   commands: [ | ||||
|     [ 'login [email or cloud address]', 'alias of session:attach', [ | ||||
|         "--auto, create a new account without asking if none exists" | ||||
|       //, "--exclusive, logout all other ids, removing access to their accounts"
 | ||||
|       , "--provider, specify an authentication provider (default: :provider)".replace(/\b:provider\b/, defaults.provider) | ||||
|       //, "--email [addr], use the given id as an email address, even if it works as a cloud address"
 | ||||
|       //, "--cloud [addr], use the given id as a cloud address or fail (don't fallback to email)"
 | ||||
|       ] | ||||
|     ] | ||||
|   , [ 'logout', 'alias of session:detach' ] | ||||
|   , [ 'whoami', 'show current account(s) and login(s) and device(s)' ] | ||||
| 
 | ||||
|     // authn
 | ||||
|   , [ 'session', 'Manage your ids (credentials / logins)' ] | ||||
|   , [ 'session:new', 'alias of `login --exclusive`' ] | ||||
|   , [ 'session:attach', 'Create a session (and account if needed) for a given email address or cloud address' ] | ||||
|   , [ 'session:detach', 'remove login from session' ] | ||||
|   , [ 'session:list', 'show all of the ids in the current session' ] | ||||
| 
 | ||||
|     // authz
 | ||||
|   , [ 'accounts', 'Manage your accounts (authorization / profiles)' ] | ||||
|   , [ 'accounts:new', 'create a new account attached to the credentials of the current session' ] | ||||
|   , [ 'accounts:set', 'change account details' ] // todo changing the name should be restricted john@provider.net -> jonathan@provider.net would be bad
 | ||||
|   , [ 'accounts:list', 'show all of the accounts in the current session' ] | ||||
|   , [ 'accounts:attach', 'attach an account to an id' ] | ||||
|   , [ 'accounts:detach', 'detach an account from an id' ] | ||||
|   , [ 'accounts:select', 'select an account to use as the primary account for this session' ] | ||||
|   , [ 'accounts:update', '(deprecated) alias of set' ] | ||||
|   , [ 'accounts:login', '(deprecated) alias of login' ] | ||||
|   , [ 'accounts:whoami', '(deprecated) alias of whoami' ] | ||||
| 
 | ||||
|     // authn / authz
 | ||||
|   , [ 'devices', 'manages devices for your account(s)' ] | ||||
|   , [ 'devices:new', 'create a new device (default name is hostname, default ip is the result of :provider/api/tunnel@oauth3.org/checkip)'.replace(/\b:provider\b/, defaults.provider) ] | ||||
|   , [ 'devices:set', 'set the ip address of the device (defaults ip is the result of :provider/api/tunnel@oauth3.org/checkip)'.replace(/\b:provider\b/, defaults.provider) ] | ||||
|   , [ 'devices:attach', "attach a device to a domain's DNS record" ] | ||||
|   , [ 'devices:detach', "detach an account from a domain's DNS record" ] | ||||
|   , [ 'devices:select', '(re)claim the specified device as this device (i.e. you re-installed your OS or deleted your ~/.oauth3)' ] | ||||
|   , [ 'devices:list', 'show all devices for your account(s)' ] | ||||
| 
 | ||||
|     // help
 | ||||
|   , [ 'help', "show this menu; use '" + defaults.main + " help COMMAND' (even 'help') for options and sub-commands" ] | ||||
|   ] | ||||
| }); | ||||
| @ -26,7 +26,7 @@ | ||||
|     "sign" | ||||
|   ], | ||||
|   "license": "MIT", | ||||
|   "homepage": "https://git.daplie.com/OAuth3/oauth3.js", | ||||
|   "homepage": "https://git.oauth3.org/OAuth3/oauth3.js", | ||||
|   "ignore": [ | ||||
|     "**/.*", | ||||
|     "browserify", | ||||
| @ -39,5 +39,5 @@ | ||||
|     "test", | ||||
|     "tests" | ||||
|   ], | ||||
|   "version": "1.0.7" | ||||
|   "version": "1.0.10" | ||||
| } | ||||
|  | ||||
| @ -1,14 +0,0 @@ | ||||
| git push --tags | ||||
| 
 | ||||
| git checkout v1.0 | ||||
| git push | ||||
| 
 | ||||
| git checkout v1 | ||||
| git merge v1.0 | ||||
| git push | ||||
| 
 | ||||
| git checkout master | ||||
| git merge v1 | ||||
| git push | ||||
| 
 | ||||
| git checkout v1.0 | ||||
							
								
								
									
										96
									
								
								navigator.auth.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								navigator.auth.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,96 @@ | ||||
| (function () { | ||||
| 'use strict'; | ||||
| 
 | ||||
| function create(myOpts) { | ||||
|   return { | ||||
|     requestScope: function (opts) { | ||||
|       // TODO pre-generate URL
 | ||||
| 
 | ||||
|       // deliver existing session if it exists
 | ||||
|       var scope = opts && opts.scope || []; | ||||
|       if (myOpts.session) { | ||||
|         if (!scope.length || scope.every(function (scp) { | ||||
|           return -1 !== opts.myOpts.session.scope.indexOf(scp); | ||||
|         })) { | ||||
|           return OAUTH3.PromiseA.resolve(myOpts.session); | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       // request a new session otherwise
 | ||||
|       return OAUTH3.implicitGrant(myOpts.directives, { | ||||
|         client_id: myOpts.conf.client_uri | ||||
|       , client_uri: myOpts.conf.client_uri | ||||
|         // maybe use inline instead?
 | ||||
|       , windowType: 'popup' | ||||
|       , scope: scope | ||||
|       }).then(function (session) { | ||||
|         return session; | ||||
|       }); | ||||
|     } | ||||
|   , session: function () { | ||||
|       return myOpts.session; | ||||
|     } | ||||
|   , refresh: function (session) { | ||||
|       return OAUTH3.implicitGrant(myOpts.directives, { | ||||
|         client_id: myOpts.conf.client_uri | ||||
|       , client_uri: myOpts.conf.client_uri | ||||
|       , windowType: 'background' | ||||
|       }).then(function (_session) { | ||||
|         session = _session; | ||||
|         return session; | ||||
|       }); | ||||
|     } | ||||
|   , logout: function () { | ||||
|       return OAUTH3.logout(myOpts.directives, { | ||||
|         client_id: myOpts.conf.client_uri | ||||
|       , client_uri: myOpts.conf.client_uri | ||||
|       }); | ||||
|     } | ||||
|   , switchUser: function () { | ||||
|       // should open dialog with user selection dialog
 | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| window.navigator.auth = { | ||||
|   getUserAuthenticator: function (opts) { | ||||
|     var conf = {}; | ||||
|     var directives; | ||||
|     var session; | ||||
| 
 | ||||
|     opts = opts || {}; | ||||
|     conf.client_uri = opts.client_uri || OAUTH3.clientUri(opts.location || window.location); | ||||
| 
 | ||||
|     return OAUTH3.issuer({ broker: opts.issuer_uri || 'https://new.oauth3.org' }).then(function (issuer) { | ||||
|       conf.issuer_uri = issuer; | ||||
|       conf.provider_uri = issuer; | ||||
| 
 | ||||
|       return OAUTH3.directives(conf.provider_uri, { | ||||
|         client_id: conf.client_uri | ||||
|       , client_uri: conf.client_uri | ||||
|       }).then(function (_directives) { | ||||
|         directives = _directives; | ||||
|         var myOpts = { | ||||
|           directives: directives | ||||
|         , conf: conf | ||||
|         }; | ||||
| 
 | ||||
|         return OAUTH3.implicitGrant(directives, { | ||||
|           client_id: conf.client_uri | ||||
|         , client_uri: conf.client_uri | ||||
|         , windowType: 'background' | ||||
|         }).then(function (_session) { | ||||
|           session = _session; | ||||
|           myOpts.session = session; | ||||
|           return create(myOpts); | ||||
|         }, function (err) { | ||||
|           console.error('[DEBUG] implicitGrant err:'); | ||||
|           console.error(err); | ||||
|           return create(myOpts); | ||||
|         }); | ||||
|       }); | ||||
|     }); | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| }()); | ||||
							
								
								
									
										86
									
								
								oauth3.account.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								oauth3.account.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,86 @@ | ||||
| ;(function (exports) { | ||||
| 'use strict'; | ||||
| 
 | ||||
| var OAUTH3 = exports.OAUTH3 = exports.OAUTH3 || require('./oauth3.core.js').OAUTH3; | ||||
| 
 | ||||
| OAUTH3.api['account.listCards'] = function (providerUri, opts) { | ||||
|   var session = opts.session; | ||||
| 
 | ||||
|   return OAUTH3.request({ | ||||
|     method: 'GET' | ||||
|   , url: OAUTH3.url.normalize(providerUri) | ||||
|       + '/api/com.daplie.payments/accounts/' + session.token.sub + '/cards' | ||||
|   , session: session | ||||
|   }).then(function (res) { | ||||
|     return res; | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| OAUTH3.api['account.addCard'] = function (providerUri, opts) { | ||||
|   var session = opts.session; | ||||
| 
 | ||||
|   return OAUTH3.request({ | ||||
|     method: 'POST' | ||||
|   , url: OAUTH3.url.normalize(providerUri) | ||||
|       + '/api/com.daplie.payments/accounts/' + session.token.sub + '/cards' | ||||
|   , session: session | ||||
|   , data: opts.data | ||||
|   }).then(function (res) { | ||||
|     return res; | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| OAUTH3.api['account.removeCard'] = function (providerUri, opts) { | ||||
|   var session = opts.session; | ||||
| 
 | ||||
|   return OAUTH3.request({ | ||||
|     method: 'DELETE' | ||||
|   , url: OAUTH3.url.normalize(providerUri) | ||||
|       + '/api/com.daplie.payments/accounts/' + session.token.sub + '/cards/' + opts.last4 + '/' + opts.brand | ||||
|   , session: session | ||||
|   }).then(function (res) { | ||||
|     return res; | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| OAUTH3.api['account.listAddresses'] = function (providerUri, opts) { | ||||
|   var session = opts.session; | ||||
| 
 | ||||
|   return OAUTH3.request({ | ||||
|     method: 'GET' | ||||
|   , url: OAUTH3.url.normalize(providerUri) | ||||
|       + '/api/com.daplie.me/accounts/' + session.token.sub + '/addresses' | ||||
|   , session: session | ||||
|   }).then(function (res) { | ||||
|     return res; | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| OAUTH3.api['account.addAddress'] = function (providerUri, opts) { | ||||
|   var session = opts.session; | ||||
| 
 | ||||
|   return OAUTH3.request({ | ||||
|     method: 'POST' | ||||
|   , url: OAUTH3.url.normalize(providerUri) | ||||
|       + '/api/com.daplie.me/accounts/' + session.token.sub + '/addresses' | ||||
|   , session: session | ||||
|   , data: opts.addAddress | ||||
|   }).then(function (res) { | ||||
|     return res; | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| OAUTH3.api['account.removeAddress'] = function (providerUri, opts) { | ||||
|   var session = opts.session; | ||||
| 
 | ||||
|   return OAUTH3.request({ | ||||
|     method: 'DELETE' | ||||
|   , url: OAUTH3.url.normalize(providerUri) | ||||
|       + '/api/com.daplie.me/accounts/' + session.token.sub + '/addresses/' + opts.addressId | ||||
|   , session: session | ||||
|   }).then(function (res) { | ||||
|     return res; | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| }('undefined' !== typeof exports ? exports : window)); | ||||
							
								
								
									
										896
									
								
								oauth3.core.js
									
									
									
									
									
								
							
							
						
						
									
										896
									
								
								oauth3.core.js
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										158
									
								
								oauth3.crypto.js
									
									
									
									
									
								
							
							
						
						
									
										158
									
								
								oauth3.crypto.js
									
									
									
									
									
								
							| @ -1,5 +1,5 @@ | ||||
| ;(function (exports) { | ||||
| 'use strict'; | ||||
|   'use strict'; | ||||
| 
 | ||||
|   var OAUTH3 = exports.OAUTH3 = exports.OAUTH3 || require('./oauth3.core.js').OAUTH3; | ||||
| 
 | ||||
| @ -8,6 +8,9 @@ | ||||
|     OAUTH3.crypto.core = require('./oauth3.node.crypto'); | ||||
|   } catch (error) { | ||||
|     OAUTH3.crypto.core = {}; | ||||
|     OAUTH3.crypto.core.ready = false; | ||||
|     var finishBeforeReady = []; | ||||
|     var deferedCalls = []; | ||||
| 
 | ||||
|     // We don't currently have a fallback method for this function, so we assign
 | ||||
|     // it directly to the core object instead of the webCrypto object.
 | ||||
| @ -17,10 +20,31 @@ | ||||
|     }; | ||||
| 
 | ||||
|     var webCrypto = {}; | ||||
| 
 | ||||
|     var deferCryptoCall = function(name) { | ||||
|       return function() { | ||||
|         var args = arguments; | ||||
|         return new OAUTH3.PromiseA(function(resolve, reject) { | ||||
|           deferedCalls.push(function(){ | ||||
|             try { | ||||
|               webCrypto[name].apply(webCrypto, args) | ||||
|                 .then(function(result){ | ||||
|                   resolve(result); | ||||
|                 }); | ||||
|             } catch(e) { | ||||
|               reject(e); | ||||
|             } | ||||
|           }); | ||||
|         }); | ||||
|       }; | ||||
|     }; | ||||
| 
 | ||||
|     OAUTH3.crypto.core.sha256 = deferCryptoCall("sha256"); | ||||
|     webCrypto.sha256 = function (buf) { | ||||
|       return OAUTH3._browser.window.crypto.subtle.digest({name: 'SHA-256'}, buf); | ||||
|     }; | ||||
| 
 | ||||
|     OAUTH3.crypto.core.pbkdf2 = deferCryptoCall("pbkdf2"); | ||||
|     webCrypto.pbkdf2 = function (password, salt) { | ||||
|       return OAUTH3._browser.window.crypto.subtle.importKey('raw', OAUTH3._binStr.binStrToBuffer(password), {name: 'PBKDF2'}, false, ['deriveKey']) | ||||
|         .then(function (key) { | ||||
| @ -32,12 +56,15 @@ | ||||
|         }); | ||||
|     }; | ||||
| 
 | ||||
|     OAUTH3.crypto.core.encrypt = deferCryptoCall("encrypt"); | ||||
|     webCrypto.encrypt = function (rawKey, iv, data) { | ||||
|       return OAUTH3._browser.window.crypto.subtle.importKey('raw', rawKey, {name: 'AES-GCM'}, false, ['encrypt']) | ||||
|         .then(function (key) { | ||||
|           return OAUTH3._browser.window.crypto.subtle.encrypt({name: 'AES-GCM', iv: iv}, key, data); | ||||
|         }); | ||||
|     }; | ||||
| 
 | ||||
|     OAUTH3.crypto.core.decrypt = deferCryptoCall("decrypt"); | ||||
|     webCrypto.decrypt = function (rawKey, iv, data) { | ||||
|       return OAUTH3._browser.window.crypto.subtle.importKey('raw', rawKey, {name: 'AES-GCM'}, false, ['decrypt']) | ||||
|         .then(function (key) { | ||||
| @ -45,6 +72,7 @@ | ||||
|         }); | ||||
|     }; | ||||
| 
 | ||||
|     OAUTH3.crypto.core.genEcdsaKeyPair = deferCryptoCall("genEcdsaKeyPair"); | ||||
|     webCrypto.genEcdsaKeyPair = function () { | ||||
|       return OAUTH3._browser.window.crypto.subtle.generateKey({name: 'ECDSA', namedCurve: 'P-256'}, true, ['sign', 'verify']) | ||||
|         .then(function (keyPair) { | ||||
| @ -57,6 +85,7 @@ | ||||
|         }); | ||||
|     }; | ||||
| 
 | ||||
|     OAUTH3.crypto.core.sign = deferCryptoCall("sign"); | ||||
|     webCrypto.sign = function (jwk, msg) { | ||||
|       return OAUTH3._browser.window.crypto.subtle.importKey('jwk', jwk, {name: 'ECDSA', namedCurve: jwk.crv}, false, ['sign']) | ||||
|         .then(function (key) { | ||||
| @ -66,6 +95,8 @@ | ||||
|           return new Uint8Array(sig); | ||||
|         }); | ||||
|     }; | ||||
|      | ||||
|     OAUTH3.crypto.core.verify = deferCryptoCall("verify"); | ||||
|     webCrypto.verify = function (jwk, msg, signature) { | ||||
|       // If the JWK has properties that should only exist on the private key or is missing
 | ||||
|       // "verify" in the key_ops, importing in as a public key won't work.
 | ||||
| @ -82,6 +113,7 @@ | ||||
|     }; | ||||
| 
 | ||||
|     function checkWebCrypto() { | ||||
|       /* global OAUTH3_crypto_fallback */ | ||||
|       var loadFallback = function() { | ||||
|         var prom; | ||||
|         loadFallback = function () { return prom; }; | ||||
| @ -96,25 +128,25 @@ | ||||
|               resolve(); | ||||
|             } | ||||
|           }; | ||||
|           script.src = '/assets/org.oauth3/oauth3.crypto.fallback.js'; | ||||
|           script.src = '/assets/oauth3.org/oauth3.crypto.fallback.js'; | ||||
|           body.appendChild(script); | ||||
|         }); | ||||
|         return prom; | ||||
|       }; | ||||
|       function checkException(name, func) { | ||||
|         new OAUTH3.PromiseA(function (resolve) { resolve(func()); }) | ||||
|         return OAUTH3.PromiseA.resolve().then(func) | ||||
|           .then(function () { | ||||
|             OAUTH3.crypto.core[name] = webCrypto[name]; | ||||
|           }) | ||||
|           .catch(function (err) { | ||||
|           }, function (err) { | ||||
|             console.warn('error with WebCrypto', name, '- using fallback', err); | ||||
|             loadFallback().then(function () { | ||||
|             return loadFallback().then(function () { | ||||
|               OAUTH3.crypto.core[name] = OAUTH3_crypto_fallback[name]; | ||||
|             }); | ||||
|           }); | ||||
|       } | ||||
|       function checkResult(name, expected, func) { | ||||
|         checkException(name, function () { | ||||
|          | ||||
|         finishBeforeReady.push(checkException(name, function () { | ||||
|           return func() | ||||
|             .then(function (result) { | ||||
|               if (typeof expected === typeof result) { | ||||
| @ -127,7 +159,7 @@ | ||||
|                 throw new Error("result ("+result+") doesn't match expectation ("+expected+")"); | ||||
|               } | ||||
|             }); | ||||
|         }); | ||||
|         })); | ||||
|       } | ||||
| 
 | ||||
|       var zeroBuf = new Uint8Array([0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]); | ||||
| @ -159,12 +191,20 @@ | ||||
|         return webCrypto.verify(jwk, dataBuf, sig); | ||||
|       }); | ||||
|       // The results of these functions are less predictable, so we can't check their return value.
 | ||||
|       checkException('genEcdsaKeyPair', function () { | ||||
|       finishBeforeReady.push(checkException('genEcdsaKeyPair', function () { | ||||
|         return webCrypto.genEcdsaKeyPair(); | ||||
|       }); | ||||
|       checkException('sign', function () { | ||||
|       })); | ||||
|       finishBeforeReady.push(checkException('sign', function () { | ||||
|         return webCrypto.sign(jwk, dataBuf); | ||||
|       }); | ||||
|       })); | ||||
|        | ||||
|       OAUTH3.PromiseA.all(finishBeforeReady) | ||||
|         .then(function(results) { | ||||
|           OAUTH3.crypto.core.ready = true; | ||||
|           deferedCalls.forEach(function(request) { | ||||
|             request(); | ||||
|           }); | ||||
|         }); | ||||
|     } | ||||
|     checkWebCrypto(); | ||||
|   } | ||||
| @ -188,106 +228,68 @@ | ||||
|       return OAUTH3.PromiseA.reject(new Error('JWK of type '+jwk.kty+' missing fields ' + missing)); | ||||
|     } | ||||
| 
 | ||||
|     var jwkStr = '{' + keys.map(function (name) { return name+':'+jwk[name]; }).join(',') + '}'; | ||||
|     // I'm not actually 100% sure this behavior is guaranteed, but when we use an array as the
 | ||||
|     // replacer argument the keys are always in the order they appeared in the array.
 | ||||
|     var jwkStr = JSON.stringify(jwk, keys); | ||||
|     return OAUTH3.crypto.core.sha256(OAUTH3._binStr.binStrToBuffer(jwkStr)) | ||||
|       .then(OAUTH3._base64.bufferToUrlSafe); | ||||
|   }; | ||||
| 
 | ||||
|   OAUTH3.crypto._createKey = function (ppid) { | ||||
|     var saltProm = OAUTH3.crypto.core.randomBytes(16); | ||||
|     var kekProm = saltProm.then(function (salt) { | ||||
|       return OAUTH3.crypto.core.pbkdf2(ppid, salt); | ||||
|     }); | ||||
| 
 | ||||
|     var ecdsaProm = OAUTH3.crypto.core.genEcdsaKeyPair() | ||||
|     .then(function (keyPair) { | ||||
|   OAUTH3.crypto.createKeyPair = function () { | ||||
|     // TODO: maybe support other types of key pairs, not just ECDSA P-256
 | ||||
|     return OAUTH3.crypto.core.genEcdsaKeyPair().then(function (keyPair) { | ||||
|       return OAUTH3.crypto.thumbprintJwk(keyPair.publicKey).then(function (kid) { | ||||
|         keyPair.privateKey.alg = keyPair.publicKey.alg = 'ES256'; | ||||
|         keyPair.privateKey.kid = keyPair.publicKey.kid = kid; | ||||
|         return keyPair; | ||||
|       }); | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|   OAUTH3.crypto.encryptKeyPair = function (keyPair, password) { | ||||
|     var saltProm = OAUTH3.crypto.core.randomBytes(16); | ||||
|     var kekProm = saltProm.then(function (salt) { | ||||
|       return OAUTH3.crypto.core.pbkdf2(password, salt); | ||||
|     }); | ||||
| 
 | ||||
|     return OAUTH3.PromiseA.all([ | ||||
|       kekProm | ||||
|     , ecdsaProm | ||||
|     , saltProm | ||||
|     , OAUTH3.crypto.core.randomBytes(16) | ||||
|     , OAUTH3.crypto.core.randomBytes(12) | ||||
|     , OAUTH3.crypto.core.randomBytes(12) | ||||
|     ]).then(function (results) { | ||||
|   , ]).then(function (results) { | ||||
|       var kek        = results[0]; | ||||
|       var keyPair    = results[1]; | ||||
|       var salt       = results[2]; | ||||
|       var userSecret = results[3]; | ||||
|       var ecdsaIv    = results[4]; | ||||
|       var secretIv   = results[5]; | ||||
|       var salt       = results[1]; | ||||
|       var ecdsaIv    = results[2]; | ||||
| 
 | ||||
|       return OAUTH3.PromiseA.all([ | ||||
|         OAUTH3.crypto.core.encrypt(kek, ecdsaIv, OAUTH3._binStr.binStrToBuffer(JSON.stringify(keyPair.privateKey))) | ||||
|       , OAUTH3.crypto.core.encrypt(kek, secretIv, userSecret) | ||||
|       ]) | ||||
|       .then(function (encrypted) { | ||||
|       var privKeyBuf = OAUTH3._binStr.binStrToBuffer(JSON.stringify(keyPair.privateKey)); | ||||
|       return OAUTH3.crypto.core.encrypt(kek, ecdsaIv, privKeyBuf).then(function (encrypted) { | ||||
|         return { | ||||
|           publicKey:  keyPair.publicKey | ||||
|         , privateKey: OAUTH3._base64.bufferToUrlSafe(encrypted[0]) | ||||
|         , userSecret: OAUTH3._base64.bufferToUrlSafe(encrypted[1]) | ||||
|         , privateKey: OAUTH3._base64.bufferToUrlSafe(encrypted) | ||||
|         , salt:       OAUTH3._base64.bufferToUrlSafe(salt) | ||||
|         , ecdsaIv:    OAUTH3._base64.bufferToUrlSafe(ecdsaIv) | ||||
|         , secretIv:   OAUTH3._base64.bufferToUrlSafe(secretIv) | ||||
|         }; | ||||
|       , }; | ||||
|       }); | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|   OAUTH3.crypto._decryptKey = function (ppid, storedObj) { | ||||
|   OAUTH3.crypto.decryptKeyPair = function (storedObj, password) { | ||||
|     var salt   = OAUTH3._base64.urlSafeToBuffer(storedObj.salt); | ||||
|     var encJwk = OAUTH3._base64.urlSafeToBuffer(storedObj.privateKey); | ||||
|     var iv     = OAUTH3._base64.urlSafeToBuffer(storedObj.ecdsaIv); | ||||
| 
 | ||||
|     return OAUTH3.crypto.core.pbkdf2(ppid, salt) | ||||
|     return OAUTH3.crypto.core.pbkdf2(password, salt) | ||||
|       .then(function (key) { | ||||
|         return OAUTH3.crypto.core.decrypt(key, iv, encJwk); | ||||
|       }) | ||||
|       .then(OAUTH3._binStr.bufferToBinStr) | ||||
|       .then(JSON.parse); | ||||
|   }; | ||||
| 
 | ||||
|   OAUTH3.crypto._getKey = function (ppid) { | ||||
|     return OAUTH3.crypto.core.sha256(OAUTH3._binStr.binStrToBuffer(ppid)) | ||||
|     .then(function (hash) { | ||||
|       var name = 'kek-' + OAUTH3._base64.bufferToUrlSafe(hash); | ||||
|       var promise; | ||||
| 
 | ||||
|       if (window.localStorage.getItem(name) === null) { | ||||
|         promise = OAUTH3.crypto._createKey(ppid).then(function (key) { | ||||
|           window.localStorage.setItem(name, JSON.stringify(key)); | ||||
|           return key; | ||||
|         }); | ||||
|       } else { | ||||
|         promise = OAUTH3.PromiseA.resolve(JSON.parse(window.localStorage.getItem(name))); | ||||
|       } | ||||
| 
 | ||||
|       return promise.then(function (storedObj) { | ||||
|         return OAUTH3.crypto._decryptKey(ppid, storedObj); | ||||
|       .then(JSON.parse) | ||||
|       .then(function (privateKey) { | ||||
|         return { | ||||
|           privateKey: privateKey | ||||
|         , publicKey:  storedObj.publicKey | ||||
|       , }; | ||||
|       }); | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|   OAUTH3.crypto._signPayload = function (payload) { | ||||
|     return OAUTH3.crypto._getKey('some PPID').then(function (key) { | ||||
|       var header = {type: 'JWT', alg: key.alg, kid: key.kid}; | ||||
|       var input = [ | ||||
|         OAUTH3._base64.encodeUrlSafe(JSON.stringify(header, null)) | ||||
|       , OAUTH3._base64.encodeUrlSafe(JSON.stringify(payload, null)) | ||||
|       ].join('.'); | ||||
| 
 | ||||
|       return OAUTH3.crypto.core.sign(key, OAUTH3._binStr.binStrToBuffer(input)) | ||||
|         .then(OAUTH3._base64.bufferToUrlSafe) | ||||
|         .then(function (signature) { | ||||
|           return input + '.' + signature; | ||||
|         }); | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
| }('undefined' !== typeof exports ? exports : window)); | ||||
|  | ||||
							
								
								
									
										104
									
								
								oauth3.dns.js
									
									
									
									
									
								
							
							
						
						
									
										104
									
								
								oauth3.dns.js
									
									
									
									
									
								
							| @ -31,15 +31,23 @@ OAUTH3.api['devices.list'] = function (providerUri, opts) { | ||||
| 
 | ||||
| OAUTH3.api['devices.attach'] = function (providerUri, opts) { | ||||
|   var session = opts.session; | ||||
|   var device = opts.device; | ||||
|   var tld = opts.tld; | ||||
|   var sld = opts.sld; | ||||
|   var sub = opts.sub; | ||||
|   var ip = opts.ip; | ||||
|   var ttl = opts.ttl; | ||||
| 
 | ||||
|   return OAUTH3.request({ | ||||
|     url: OAUTH3.url.normalize(providerUri) | ||||
|       + '/api/com.daplie.domains/accounts/' + session.token.sub | ||||
|       //+ '/devices/' + device + '/'
 | ||||
|       + '/devices/' + (opts.data.uid || '_') + '/' + opts.data.device | ||||
| 			+ '/' + opts.data.tld + '/' + opts.data.sld + '/' + (opts.data.sub || '') | ||||
|       + '/api/com.daplie.domains/accounts/' + session.token.sub + '/devices/' | ||||
|       + device + '/' + tld + '/' + sld + '/' + (sub || '') | ||||
|     , method: 'POST' | ||||
|     , session: session | ||||
|     , data: { | ||||
|         addresses: ip | ||||
|       , ttl: ttl | ||||
|     } | ||||
|   }, {}).then(function (res) { | ||||
|     return res.data.devices || res.data; | ||||
|   }); | ||||
| @ -47,28 +55,15 @@ OAUTH3.api['devices.attach'] = function (providerUri, opts) { | ||||
| 
 | ||||
| OAUTH3.api['devices.detach'] = function (providerUri, opts) { | ||||
|   var session = opts.session; | ||||
|   var device = opts.device; | ||||
|   var tld = opts.tld; | ||||
|   var sld = opts.sld; | ||||
|   var sub = opts.sub; | ||||
| 
 | ||||
|   return OAUTH3.request({ | ||||
|     url: OAUTH3.url.normalize(providerUri) | ||||
|       + '/api/com.daplie.domains/accounts/' + session.token.sub | ||||
|       //+ '/devices/' + device + '/'
 | ||||
|       + '/devices/' + (opts.data.uid || '_') + '/' + opts.data.device | ||||
| 			+ '/' + opts.data.tld + '/' + opts.data.sld + '/' + (opts.data.sub || '') | ||||
|   , method: 'DELETE' | ||||
|   , session: session | ||||
|   }, {}).then(function (res) { | ||||
|     return res.data.devices || res.data; | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| OAUTH3.api['devices.detach'] = function (providerUri, opts) { | ||||
|   var session = opts.session; | ||||
| 
 | ||||
|   return OAUTH3.request({ | ||||
|     url: OAUTH3.url.normalize(providerUri) | ||||
|       + '/api/com.daplie.domains/accounts/' + session.token.sub | ||||
| 			+ '/devices/' + opts.data.device | ||||
| 			+ '/' + opts.data.tld + '/' + opts.data.sld + '/' + (opts.data.sub || '') | ||||
| 			+ '/devices/' + device + '/' + tld + '/' + sld + '/' + (sub || '') | ||||
|   , method: 'DELETE' | ||||
|   , session: session | ||||
|   }, {}).then(function (res) { | ||||
| @ -76,4 +71,69 @@ OAUTH3.api['devices.detach'] = function (providerUri, opts) { | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| OAUTH3.api['devices.destroy'] = function (providerUri, opts) { | ||||
|   var session = opts.session; | ||||
|   var device = opts.device; | ||||
| 
 | ||||
|   return OAUTH3.request({ | ||||
|     url: OAUTH3.url.normalize(providerUri) | ||||
|       + '/api/com.daplie.domains/accounts/' + session.token.sub | ||||
| 			+ '/devices/' + device | ||||
|   , method: 'DELETE' | ||||
|   , session: session | ||||
|   }, {}).then(function (res) { | ||||
|     return res.data.device || res.data; | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| OAUTH3.api['dns.set'] = function (providerUri, opts) { | ||||
|   var session = opts.session; | ||||
|   var tld = opts.tld; | ||||
|   var sld = opts.sld; | ||||
|   var sub = opts.sub; | ||||
|   var type = opts.type; | ||||
|   var value = opts.value; | ||||
|   var ttl = opts.ttl; | ||||
|   var priority = (opts.priority || ''); | ||||
|   var weight = (opts.weight || ''); | ||||
|   var port = (opts.port || ''); | ||||
| 
 | ||||
|   return OAUTH3.request({ | ||||
|     url: OAUTH3.url.normalize(providerUri) | ||||
|       + '/api/com.daplie.domains/accounts/' + session.token.sub | ||||
|       + '/dns/' + tld + '/' + sld + '/' + sub | ||||
|   , method: 'POST' | ||||
|   , session: session | ||||
|   , data: [{ | ||||
|     type: type | ||||
|   , value: value | ||||
|   , ttl: ttl | ||||
|   , priority: priority | ||||
|   , weight: weight | ||||
|   , port: port | ||||
|   }] | ||||
|   }, {}).then(function (res) { | ||||
|     return res.data || res; | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| OAUTH3.api['dns.unset'] = function (providerUri, opts) { | ||||
|   var session = opts.session; | ||||
|   var tld = opts.tld; | ||||
|   var sld = opts.sld; | ||||
|   var sub = (opts.sub || '@'); | ||||
|   var type = opts.type; | ||||
|   var value = opts.value; | ||||
| 
 | ||||
|   return OAUTH3.request({ | ||||
|     url: OAUTH3.url.normalize(providerUri) | ||||
|       + '/api/com.daplie.domains/accounts/' + session.token.sub | ||||
|       + '/dns/' + tld + '/' + sld + '/' + sub + '/' + type + '/' + value | ||||
|   , method: 'DELETE' | ||||
|   , session: session | ||||
|   }, {}).then(function (res) { | ||||
|     return res.data || res; | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| }('undefined' !== typeof exports ? exports : window)); | ||||
|  | ||||
| @ -3,6 +3,35 @@ | ||||
| 
 | ||||
| var OAUTH3 = exports.OAUTH3 = exports.OAUTH3 || require('./oauth3.core.js').OAUTH3; | ||||
| 
 | ||||
| OAUTH3.api['domains.checkAvailability'] = function (providerUri, opts) { | ||||
|   var session = opts.session; | ||||
|   var sld = opts.sld; | ||||
|   var tld = opts.tld; | ||||
| 
 | ||||
|   return OAUTH3.request({ | ||||
|     method: 'GET' | ||||
|   , url: OAUTH3.url.normalize(providerUri) | ||||
|       + '/api/com.daplie.domains/check-availability/' + sld + '/' + tld | ||||
|   , session: session | ||||
|   }).then(function (res) { | ||||
|     return res; | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| OAUTH3.api['domains.purchase'] = function (providerUri, opts) { | ||||
|   var session = opts.session; | ||||
| 
 | ||||
|   return OAUTH3.request({ | ||||
|     method: 'POST' | ||||
|   , url: OAUTH3.url.normalize(providerUri) | ||||
|       + '/api/com.daplie.domains/accounts/' + session.token.sub + '/registrations' | ||||
|   , session: session | ||||
|   , data: opts.data | ||||
|   }).then(function (res) { | ||||
|     return res; | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| OAUTH3.api['domains.list'] = function (providerUri, opts) { | ||||
|   var session = opts.session; | ||||
| 
 | ||||
| @ -16,4 +45,102 @@ OAUTH3.api['domains.list'] = function (providerUri, opts) { | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| // TODO: Manual Renew Function
 | ||||
| OAUTH3.api['domains.extend'] = function (providerUri, opts) { | ||||
|   var session = opts.session; | ||||
| 
 | ||||
|   return OAUTH3.request({ | ||||
|     method: 'POST' | ||||
|   , url: OAUTH3.url.normalize(providerUri) | ||||
|       + '/api/com.daplie.domains/accounts/' + session.token.sub + '/registrations/' + opts.data.tld + '/' + opts.data.sld + '/extend' | ||||
|   , session: session | ||||
|   , data: opts.data | ||||
|   }).then(function (res) { | ||||
|     return res; | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| OAUTH3.api['ns.list'] = function (providerUri, opts) { | ||||
|   var session = opts.session; | ||||
|   var domain = opts.domain; | ||||
|   var nameArr = domain.split('.'); | ||||
|   var reverseNameArr = nameArr.reverse(); | ||||
|   var nameSubArr = reverseNameArr.slice(3); | ||||
|   var tld; | ||||
|   var sld; | ||||
|   var sub; | ||||
| 
 | ||||
|   if (reverseNameArr[0] === 'me' && reverseNameArr[1] === 'daplie') { | ||||
|     tld = 'daplie.me'; | ||||
|     sld = reverseNameArr[2]; | ||||
|     sub = nameSubArr.reverse().join('.') || ''; | ||||
|   } else { | ||||
|     tld = nameArr[0]; | ||||
|     sld = nameArr[1]; | ||||
|     sub = reverseNameArr.slice(2).reverse().join('.') || ''; | ||||
|   } | ||||
| 
 | ||||
|   return OAUTH3.request({ | ||||
|     method: 'GET' | ||||
|   , url: OAUTH3.url.normalize(providerUri) | ||||
|       + '/api/com.daplie.domains/accounts/' + session.token.sub + '/ns/' | ||||
|       + tld + '/' + sld + '/' + sub | ||||
|   , session: session | ||||
|   }).then(function (res) { | ||||
|     return res.data; | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| OAUTH3.api['ns.add'] = function (providerUri, opts) { | ||||
|   var session = opts.session; | ||||
|   var server = opts.server; | ||||
|   var tld =  opts.tld; | ||||
|   var sld = opts.sld; | ||||
|   var sub = opts.sub; | ||||
| 
 | ||||
|   return OAUTH3.request({ | ||||
|     method: 'POST' | ||||
|   , url: OAUTH3.url.normalize(providerUri) | ||||
|       + '/api/com.daplie.domains/accounts/' + session.token.sub + '/ns/' | ||||
|       + tld + '/' + sld + '/' + sub | ||||
|   , session: session | ||||
|   , data: { nameservers: [server] } | ||||
|   }).then(function (res) { | ||||
|     return res; | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| OAUTH3.api['glue.list'] = function (providerUri, opts) { | ||||
|   var session = opts.session; | ||||
| 
 | ||||
|   return OAUTH3.request({ | ||||
|     method: 'GET' | ||||
|   , url: OAUTH3.url.normalize(providerUri) | ||||
|       + '/api/com.daplie.domains/accounts/' + session.token.sub + '/glue' | ||||
|   , session: session | ||||
|   }).then(function (res) { | ||||
|     return res.data; | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| OAUTH3.api['glue.add'] = function (providerUri, opts) { | ||||
|   var session = opts.session; | ||||
|   var ip = opts.ip; | ||||
|   var tld =  opts.tld; | ||||
|   var sld = opts.sld; | ||||
|   var sub = (opts.sub || '@'); | ||||
| 
 | ||||
|   return OAUTH3.request({ | ||||
|     method: 'POST' | ||||
|   , url: OAUTH3.url.normalize(providerUri) | ||||
|       + '/api/com.daplie.domains/accounts/' + session.token.sub + '/glue/' | ||||
|       + tld + '/' + sld + '/' + sub | ||||
|   , session: session | ||||
|   , data: { ip: ip } | ||||
|   }, {}).then(function (res) { | ||||
|     return res; | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| }('undefined' !== typeof exports ? exports : window)); | ||||
|  | ||||
							
								
								
									
										841
									
								
								oauth3.issuer.js
									
									
									
									
									
								
							
							
						
						
									
										841
									
								
								oauth3.issuer.js
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -27,10 +27,10 @@ | ||||
| 
 | ||||
|   OAUTH3.authz.scopes = function () { | ||||
|     return OAUTH3.PromiseA.resolve({ | ||||
|       pending: ['oauth3_authn']   // not yet accepted
 | ||||
|     , granted: []                 // all granted, ever
 | ||||
|     , requested: ['oauth3_authn'] // all requested, now
 | ||||
|     , accepted: []                // granted (ever) and requested (now)
 | ||||
|       pending: [ 'authn@oauth3.org' ]     // not yet accepted
 | ||||
|     , granted: []                         // all granted, ever
 | ||||
|     , requested: [ 'authn@oauth3.org' ]   // all requested, now
 | ||||
|     , accepted: []                        // granted (ever) and requested (now)
 | ||||
|     }); | ||||
|   }; | ||||
|   OAUTH3.authz.grants = function (providerUri, opts) { | ||||
|  | ||||
							
								
								
									
										62
									
								
								oauth3.ng.js
									
									
									
									
									
								
							
							
						
						
									
										62
									
								
								oauth3.ng.js
									
									
									
									
									
								
							| @ -1,38 +1,48 @@ | ||||
| ;(function () { | ||||
| 'use strict'; | ||||
| 
 | ||||
| angular | ||||
|   .module('org.oauth3', []) | ||||
|   .service('Oauth3', [ | ||||
| var modules = { | ||||
|   azp: [ | ||||
|     '$timeout' | ||||
|   , '$q' | ||||
|   , function Oauth3($timeout, $q) { | ||||
|   , '$rootScope' | ||||
|   , function Oauth3($timeout, $q, $rootScope) { | ||||
|       var OAUTH3 = window.OAUTH3; | ||||
| 
 | ||||
|     var OAUTH3 = window.OAUTH3; | ||||
|       // We need to make angular's $q appear to be a standard Promise/A+
 | ||||
|       // fortunately, this is pretty easy
 | ||||
|       function PromiseAngularQ(fn) { | ||||
|         var d = $q.defer(); | ||||
| 
 | ||||
|     // We need to make angular's $q appear to be a standard Promise/A+
 | ||||
|     // fortunately, this is pretty easy
 | ||||
|     function PromiseAngularQ(fn) { | ||||
|       var d = $q.defer(); | ||||
|         //$timeout(function () {
 | ||||
|           fn(d.resolve, d.reject); | ||||
|         //}, 0);
 | ||||
| 
 | ||||
|       //$timeout(function () {
 | ||||
|         fn(d.resolve, d.reject); | ||||
|       //}, 0);
 | ||||
|         //this.then = d.promise.then;
 | ||||
|         //this.catch = d.promise.catch;
 | ||||
|         return d.promise; | ||||
|       } | ||||
| 
 | ||||
|       //this.then = d.promise.then;
 | ||||
|       //this.catch = d.promise.catch;
 | ||||
|       return d.promise; | ||||
|       //PromiseAngularQ.create = PromiseAngularQ;
 | ||||
|       PromiseAngularQ.resolve = $q.when; | ||||
|       PromiseAngularQ.reject = $q.reject; | ||||
|       PromiseAngularQ.all = $q.all; | ||||
| 
 | ||||
|       OAUTH3.PromiseA = PromiseAngularQ; | ||||
|       OAUTH3._digest = function () { | ||||
|         $rootScope.$digest(); | ||||
|       }; | ||||
| 
 | ||||
|       window.ngOauth3 = OAUTH3; | ||||
| 
 | ||||
|       return OAUTH3; | ||||
|     } | ||||
|   ] | ||||
| }; | ||||
| 
 | ||||
|     //PromiseAngularQ.create = PromiseAngularQ;
 | ||||
|     PromiseAngularQ.resolve = $q.when; | ||||
|     PromiseAngularQ.reject = $q.reject; | ||||
|     PromiseAngularQ.all = $q.all; | ||||
| 
 | ||||
|     OAUTH3.PromiseA = PromiseAngularQ; | ||||
| 
 | ||||
|     window.ngOauth3 = OAUTH3; | ||||
| 
 | ||||
|     return OAUTH3; | ||||
|   }]); | ||||
| angular | ||||
|   .module('oauth3.org', []) | ||||
|   .service('azp@oauth3.org', modules.azp) | ||||
|   .service('AzpOauth3', modules.azp) | ||||
|   ; | ||||
| }()); | ||||
|  | ||||
| @ -28,6 +28,7 @@ OAUTH3._base64.atob = function (base64) { | ||||
| OAUTH3._base64.btoa = function (text) { | ||||
|   return new Buffer(text, 'utf8').toString('base64'); | ||||
| }; | ||||
| OAUTH3._defaultStorage = require('./oauth3.node.storage'); | ||||
| 
 | ||||
| OAUTH3._node = {}; | ||||
| OAUTH3._node.discover = function(providerUri/*, opts*/) { | ||||
| @ -43,6 +44,7 @@ OAUTH3._node.request = function(preq/*, _sys*/) { | ||||
|     method: preq.method | ||||
|   , url: preq.url || preq.uri | ||||
|   , headers: preq.headers | ||||
|   , timeout: preq.timeout || undefined | ||||
|   , json: preq.data || preq.body || preq.json || undefined // TODO which to use?
 | ||||
|   , formData: preq.formData || undefined | ||||
|   }; | ||||
| @ -59,10 +61,7 @@ OAUTH3._node._parseJson = function (resp) { | ||||
| 
 | ||||
|   // TODO toCamelCase
 | ||||
|   if (!(resp.statusCode >= 200 && resp.statusCode < 400)) { | ||||
|     // console.log('[A3] DEBUG', resp.body);
 | ||||
|     err = new Error("bad response code: " + resp.statusCode); | ||||
|     err.result = resp.body; | ||||
|     return PromiseA.reject(err); | ||||
|   } | ||||
| 
 | ||||
|   //console.log('resp.body', typeof resp.body);
 | ||||
| @ -70,15 +69,16 @@ OAUTH3._node._parseJson = function (resp) { | ||||
|     try { | ||||
|       json = JSON.parse(json); | ||||
|     } catch(e) { | ||||
|       err = new Error('response not parsable:' + resp.body); | ||||
|       err.result = resp.body; | ||||
|       return PromiseA.reject(err); | ||||
|       err = err || (new Error('response not parsable: ' + resp.body)); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   // handle both Oauth2- and node-style errors
 | ||||
|   if (json.error) { | ||||
|     err = new Error(json.error && json.error.message || json.error_description || json.error); | ||||
|   if (json && json.error) { | ||||
|     err = new Error(json.error.message || json.error_description || JSON.stringify(json.error)); | ||||
|   } | ||||
| 
 | ||||
|   if (err) { | ||||
|     err.result = json; | ||||
|     return PromiseA.reject(err); | ||||
|   } | ||||
|  | ||||
| @ -9,6 +9,11 @@ var sessionsdir = path.join(oauth3dir, 'sessions'); | ||||
| var directivesdir = path.join(oauth3dir, 'directives'); | ||||
| var metadir = path.join(oauth3dir, 'meta'); | ||||
| 
 | ||||
| // We can reasonably assume the existence of the home directory, but we can't assume
 | ||||
| // that there will already be a `.oauth3` directory or anything inside of it.
 | ||||
| if (!fs.existsSync(path.join(oauth3dir, '..'))) { | ||||
|   fs.mkdirSync(path.join(oauth3dir, '..')); | ||||
| } | ||||
| if (!fs.existsSync(oauth3dir)) { | ||||
|   fs.mkdirSync(oauth3dir); | ||||
| } | ||||
| @ -62,10 +67,9 @@ module.exports = { | ||||
| 
 | ||||
| , sessions: { | ||||
|     all: function (providerUri) { | ||||
|       var dirname = path.join(oauth3dir, 'sessions'); | ||||
|       return fs.readdirAsync(dirname).then(function (nodes) { | ||||
|       return fs.readdirAsync(sessionsdir).then(function (nodes) { | ||||
|         return nodes.map(function (node) { | ||||
|           var result = require(path.join(dirname, node)); | ||||
|           var result = require(path.join(sessionsdir, node)); | ||||
|           if (result.link) { | ||||
|             return null; | ||||
|           } | ||||
| @ -86,7 +90,7 @@ module.exports = { | ||||
|           result = require(path.join(sessionsdir, providerUri + '.json')); | ||||
|           // TODO make safer
 | ||||
|           if (result.link && '/' !== result.link[0] && !/\.\./.test(result.link)) { | ||||
|             result = require(path.join(oauth3dir, 'sessions', result.link)); | ||||
|             result = require(path.join(sessionsdir, result.link)); | ||||
|           } | ||||
|         } | ||||
|       } catch(e) { | ||||
| @ -108,10 +112,9 @@ module.exports = { | ||||
|       }); | ||||
|     } | ||||
|   , clear: function () { | ||||
|       var dirname = path.join(oauth3dir, 'sessions'); | ||||
|       return fs.readdirAsync(dirname).then(function (nodes) { | ||||
|       return fs.readdirAsync(sessionsdir).then(function (nodes) { | ||||
|         return PromiseA.all(nodes.map(function (node) { | ||||
|           return fs.unlinkAsync(path.join(dirname, node)); | ||||
|           return fs.unlinkAsync(path.join(sessionsdir, node)); | ||||
|         })); | ||||
|       }); | ||||
|     } | ||||
|  | ||||
| @ -1,66 +0,0 @@ | ||||
| { | ||||
|   "provider_uri": "https://oauth3.org", | ||||
|   "client_uri": "oauth3.org", | ||||
|   "token_type": "bearer", | ||||
|   "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJqdGkiOiIzOTZjMzJlNzE1NmIzNGI1ZWY1ZWYwZWU4Zjk3Y2NiMyIsImlhdCI6MTQ5MDE5NzUyNywiaXNzIjoib2F1dGgzLm9yZyIsImF1ZCI6Im9hdXRoMy5vcmciLCJhenAiOiJvYXV0aDMub3JnIiwic3ViIjoiIiwia2lkIjoib2F1dGgzLm9yZyIsInNjcCI6IiIsImFzIjoibG9naW4iLCJncnQiOiJwYXNzd29yZCIsInNydiI6ZmFsc2UsImsiOiJvYXV0aDMub3JnIiwiYXBwIjoib2F1dGgzLm9yZyIsImFjeCI6eyJpZCI6IjE1LUxhM3JnZXBFelBCR0xITHlrdEZOT1NDZFNVOXZJdjlKc2EzTkMxYVJUc3ZmUTZ5cDJuVFFfZWxmdkhzYTEifSwiYXhzIjpbXSwidXNyIjoiYTM3YWVkYTk5ZDQ5MThhMDM0YzM0MmQ2NGNkZjRiN2VkMjM0ZGZlNSIsImFjYyI6eyJpZCI6IjUzZTUwMTk2LTE4ZTMtNGJlNi04NDcyLTQ1ZDBjNDMxZjdhZCJ9LCJhY3MiOltdLCJpZHgiOiJuZnZ1bHRETE0tT0EzVUV3dVJHTDE3RFY1UXpIbWhac005Z2xMdnFLVGJacGh1T0NqMnBEUzByRk9XSXhaRjZLIiwidG9rZW5UeXBlIjoiYmVhcmVyIiwiZXhwIjoxNDkwMTk5MzI3LCJpcCI6IjIwNy4xNzMuMTY1LjUwIn0.qQu6NdsU4oVucv4uV_jusfL2HKgnPpfwF6iVG0H-P08akDtGgDoXcyVfl6hQdpVL9DGYVwvCPPUkLT0bJztM08lWhg69dVs-2e2I2BhjClsKeLsrFDBrUMwWVqqzCNVj8WBzcULLtl_mEgZc1qwVpZvXXiu0vmrRl3gtzVRaLL0", | ||||
|   "scope": "", | ||||
|   "expires_in": 1800, | ||||
|   "refresh_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJqdGkiOiI0OGJhNjJhNTQxNGFlODQ3OWJhMzA0MGQ1Mzc5NmY3MiIsImlhdCI6MTQ5MDE5NzUyNywiaXNzIjoib2F1dGgzLm9yZyIsImF1ZCI6Im9hdXRoMy5vcmciLCJhenAiOiJvYXV0aDMub3JnIiwic3ViIjoiIiwia2lkIjoib2F1dGgzLm9yZyIsInNjcCI6IiIsImFzIjoibG9naW4iLCJncnQiOiJwYXNzd29yZCIsInNydiI6ZmFsc2UsImsiOiJvYXV0aDMub3JnIiwiYXBwIjoib2F1dGgzLm9yZyIsImFjeCI6eyJpZCI6IjE1LUxhM3JnZXBFelBCR0xITHlrdEZOT1NDZFNVOXZJdjlKc2EzTkMxYVJUc3ZmUTZ5cDJuVFFfZWxmdkhzYTEifSwiYXhzIjpbXSwidXNyIjoiYTM3YWVkYTk5ZDQ5MThhMDM0YzM0MmQ2NGNkZjRiN2VkMjM0ZGZlNSIsImFjYyI6eyJpZCI6IjUzZTUwMTk2LTE4ZTMtNGJlNi04NDcyLTQ1ZDBjNDMxZjdhZCJ9LCJhY3MiOltdLCJpZHgiOiJuZnZ1bHRETE0tT0EzVUV3dVJHTDE3RFY1UXpIbWhac005Z2xMdnFLVGJacGh1T0NqMnBEUzByRk9XSXhaRjZLIiwicmVmcmVzaCI6dHJ1ZX0.QfufVyAGit2YOy9Hs9mv4eoCuyCYb9FDT_UXGd3JaFZe6MwqxLLnq2fWkkV2jgzDAK5t0JMu2Vk91jPP2IBXMkpZSzjaEKJ3-Eokb14Mo5GIrp54ndM20gWVZc-ReQtOUtSVG28bfnOBT5ceUM6SBrTxfz1ENOfmAiWl5591roQ", | ||||
|   "token": { | ||||
|     "jti": "396c32e7156b34b5ef5ef0ee8f97ccb3", | ||||
|     "iat": 1490197527, | ||||
|     "iss": "oauth3.org", | ||||
|     "aud": "oauth3.org", | ||||
|     "azp": "oauth3.org", | ||||
|     "sub": "15-La3rgepEzPBGLHLyktFNOSCdSU9vIv9Jsa3NC1aRTsvfQ6yp2nTQ_elfvHsa1", | ||||
|     "kid": "oauth3.org", | ||||
|     "scp": "", | ||||
|     "as": "login", | ||||
|     "grt": "password", | ||||
|     "srv": false, | ||||
|     "k": "oauth3.org", | ||||
|     "app": "oauth3.org", | ||||
|     "acx": { | ||||
|       "id": "15-La3rgepEzPBGLHLyktFNOSCdSU9vIv9Jsa3NC1aRTsvfQ6yp2nTQ_elfvHsa1" | ||||
|     }, | ||||
|     "axs": [], | ||||
|     "usr": "a37aeda99d4918a034c342d64cdf4b7ed234dfe5", | ||||
|     "acc": { | ||||
|       "id": "53e50196-18e3-4be6-8472-45d0c431f7ad" | ||||
|     }, | ||||
|     "acs": [], | ||||
|     "idx": "nfvultDLM-OA3UEwuRGL17DV5QzHmhZsM9glLvqKTbZphuOCj2pDS0rFOWIxZF6K", | ||||
|     "tokenType": "bearer", | ||||
|     "exp": 1490199327, | ||||
|     "ip": "207.173.165.50", | ||||
|     "client_uri": "oauth3.org", | ||||
|     "provider_uri": "https://oauth3.org" | ||||
|   }, | ||||
|   "refresh": { | ||||
|     "jti": "48ba62a5414ae8479ba3040d53796f72", | ||||
|     "iat": 1490197527, | ||||
|     "iss": "oauth3.org", | ||||
|     "aud": "oauth3.org", | ||||
|     "azp": "oauth3.org", | ||||
|     "sub": "15-La3rgepEzPBGLHLyktFNOSCdSU9vIv9Jsa3NC1aRTsvfQ6yp2nTQ_elfvHsa1", | ||||
|     "kid": "oauth3.org", | ||||
|     "scp": "", | ||||
|     "as": "login", | ||||
|     "grt": "password", | ||||
|     "srv": false, | ||||
|     "k": "oauth3.org", | ||||
|     "app": "oauth3.org", | ||||
|     "acx": { | ||||
|       "id": "15-La3rgepEzPBGLHLyktFNOSCdSU9vIv9Jsa3NC1aRTsvfQ6yp2nTQ_elfvHsa1" | ||||
|     }, | ||||
|     "axs": [], | ||||
|     "usr": "a37aeda99d4918a034c342d64cdf4b7ed234dfe5", | ||||
|     "acc": { | ||||
|       "id": "53e50196-18e3-4be6-8472-45d0c431f7ad" | ||||
|     }, | ||||
|     "acs": [], | ||||
|     "idx": "nfvultDLM-OA3UEwuRGL17DV5QzHmhZsM9glLvqKTbZphuOCj2pDS0rFOWIxZF6K", | ||||
|     "refresh": true, | ||||
|     "provider_uri": "https://oauth3.org" | ||||
|   } | ||||
| } | ||||
| @ -1,66 +0,0 @@ | ||||
| { | ||||
|   "provider_uri": "https://oauth3.org", | ||||
|   "client_uri": "oauth3.org", | ||||
|   "token_type": "bearer", | ||||
|   "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJqdGkiOiI4MmU5MDhlMTljMmIxM2IxNmM3N2JlMTNkMTljZmEzOSIsImlhdCI6MTQ5MDEzODg2MiwiaXNzIjoib2F1dGgzLm9yZyIsImF1ZCI6Im9hdXRoMy5vcmciLCJhenAiOiJvYXV0aDMub3JnIiwic3ViIjoiIiwia2lkIjoib2F1dGgzLm9yZyIsInNjcCI6IiIsImFzIjoibG9naW4iLCJncnQiOiJwYXNzd29yZCIsInNydiI6ZmFsc2UsImsiOiJvYXV0aDMub3JnIiwiYXBwIjoib2F1dGgzLm9yZyIsImFjeCI6eyJpZCI6IjE1LUxhM3JnZXBFelBCR0xITHlrdEZOT1NDZFNVOXZJdjlKc2EzTkMxYVJUc3ZmUTZ5cDJuVFFfZWxmdkhzYTEifSwiYXhzIjpbXSwidXNyIjoiYTM3YWVkYTk5ZDQ5MThhMDM0YzM0MmQ2NGNkZjRiN2VkMjM0ZGZlNSIsImFjYyI6eyJpZCI6IjUzZTUwMTk2LTE4ZTMtNGJlNi04NDcyLTQ1ZDBjNDMxZjdhZCJ9LCJhY3MiOltdLCJpZHgiOiJuZnZ1bHRETE0tT0EzVUV3dVJHTDE3RFY1UXpIbWhac005Z2xMdnFLVGJacGh1T0NqMnBEUzByRk9XSXhaRjZLIiwidG9rZW5UeXBlIjoiYmVhcmVyIiwiZXhwIjoxNDkwMTQwNjYyLCJpcCI6IjIwNy4xNzMuMTY1LjUwIn0.luWnALBIv9TD_mGHUIddRpOnbVAhkYO-DJtEQitODQX2IEC7cIcbSrvJBKI3i_djeMj69fm-ctr6XFUU7WiEVnBsMh55WK1gkdFqFzImo67apQ5kAV8GTGGbG___kisjl12AMvL09_shU1Sp8F8cHayTZTmSbyyWbbFKT3cZCsg", | ||||
|   "scope": "", | ||||
|   "expires_in": 1800, | ||||
|   "refresh_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJqdGkiOiI1ZDBjMGMyNTkyNmZhNmMxOTM0MjlhZWRkMjJjOTEyYiIsImlhdCI6MTQ5MDEzODg2MiwiaXNzIjoib2F1dGgzLm9yZyIsImF1ZCI6Im9hdXRoMy5vcmciLCJhenAiOiJvYXV0aDMub3JnIiwic3ViIjoiIiwia2lkIjoib2F1dGgzLm9yZyIsInNjcCI6IiIsImFzIjoibG9naW4iLCJncnQiOiJwYXNzd29yZCIsInNydiI6ZmFsc2UsImsiOiJvYXV0aDMub3JnIiwiYXBwIjoib2F1dGgzLm9yZyIsImFjeCI6eyJpZCI6IjE1LUxhM3JnZXBFelBCR0xITHlrdEZOT1NDZFNVOXZJdjlKc2EzTkMxYVJUc3ZmUTZ5cDJuVFFfZWxmdkhzYTEifSwiYXhzIjpbXSwidXNyIjoiYTM3YWVkYTk5ZDQ5MThhMDM0YzM0MmQ2NGNkZjRiN2VkMjM0ZGZlNSIsImFjYyI6eyJpZCI6IjUzZTUwMTk2LTE4ZTMtNGJlNi04NDcyLTQ1ZDBjNDMxZjdhZCJ9LCJhY3MiOltdLCJpZHgiOiJuZnZ1bHRETE0tT0EzVUV3dVJHTDE3RFY1UXpIbWhac005Z2xMdnFLVGJacGh1T0NqMnBEUzByRk9XSXhaRjZLIiwicmVmcmVzaCI6dHJ1ZX0.XljVX_QXgnYw8gyIZjQdxfyDlAAwWtU-kLitibw2_3xo9muLFPCL_dAk5XnMRygyyh5B9H4p4qB2Gb5BEJKJRfAtQ6TeZadTBtxwoY7zcns9f4Nx59VNii4k_Xp3uhJ6fQp8ERvkMgBy52Sj5ag0PFnuIwk35wLdSfiikDGnwKo", | ||||
|   "token": { | ||||
|     "jti": "82e908e19c2b13b16c77be13d19cfa39", | ||||
|     "iat": 1490138862, | ||||
|     "iss": "oauth3.org", | ||||
|     "aud": "oauth3.org", | ||||
|     "azp": "oauth3.org", | ||||
|     "sub": "15-La3rgepEzPBGLHLyktFNOSCdSU9vIv9Jsa3NC1aRTsvfQ6yp2nTQ_elfvHsa1", | ||||
|     "kid": "oauth3.org", | ||||
|     "scp": "", | ||||
|     "as": "login", | ||||
|     "grt": "password", | ||||
|     "srv": false, | ||||
|     "k": "oauth3.org", | ||||
|     "app": "oauth3.org", | ||||
|     "acx": { | ||||
|       "id": "15-La3rgepEzPBGLHLyktFNOSCdSU9vIv9Jsa3NC1aRTsvfQ6yp2nTQ_elfvHsa1" | ||||
|     }, | ||||
|     "axs": [], | ||||
|     "usr": "a37aeda99d4918a034c342d64cdf4b7ed234dfe5", | ||||
|     "acc": { | ||||
|       "id": "53e50196-18e3-4be6-8472-45d0c431f7ad" | ||||
|     }, | ||||
|     "acs": [], | ||||
|     "idx": "nfvultDLM-OA3UEwuRGL17DV5QzHmhZsM9glLvqKTbZphuOCj2pDS0rFOWIxZF6K", | ||||
|     "tokenType": "bearer", | ||||
|     "exp": 1490140662, | ||||
|     "ip": "207.173.165.50", | ||||
|     "client_uri": "oauth3.org", | ||||
|     "provider_uri": "https://oauth3.org" | ||||
|   }, | ||||
|   "refresh": { | ||||
|     "jti": "5d0c0c25926fa6c193429aedd22c912b", | ||||
|     "iat": 1490138862, | ||||
|     "iss": "oauth3.org", | ||||
|     "aud": "oauth3.org", | ||||
|     "azp": "oauth3.org", | ||||
|     "sub": "15-La3rgepEzPBGLHLyktFNOSCdSU9vIv9Jsa3NC1aRTsvfQ6yp2nTQ_elfvHsa1", | ||||
|     "kid": "oauth3.org", | ||||
|     "scp": "", | ||||
|     "as": "login", | ||||
|     "grt": "password", | ||||
|     "srv": false, | ||||
|     "k": "oauth3.org", | ||||
|     "app": "oauth3.org", | ||||
|     "acx": { | ||||
|       "id": "15-La3rgepEzPBGLHLyktFNOSCdSU9vIv9Jsa3NC1aRTsvfQ6yp2nTQ_elfvHsa1" | ||||
|     }, | ||||
|     "axs": [], | ||||
|     "usr": "a37aeda99d4918a034c342d64cdf4b7ed234dfe5", | ||||
|     "acc": { | ||||
|       "id": "53e50196-18e3-4be6-8472-45d0c431f7ad" | ||||
|     }, | ||||
|     "acs": [], | ||||
|     "idx": "nfvultDLM-OA3UEwuRGL17DV5QzHmhZsM9glLvqKTbZphuOCj2pDS0rFOWIxZF6K", | ||||
|     "refresh": true, | ||||
|     "provider_uri": "https://oauth3.org" | ||||
|   } | ||||
| } | ||||
| @ -9,7 +9,7 @@ OAUTH3.api['tunnel.token'] = function (providerUri, opts) { | ||||
|   return OAUTH3.request({ | ||||
|     method: 'POST' | ||||
|   , url: OAUTH3.url.normalize(providerUri) | ||||
|       + '/api/org.oauth3.tunnel/accounts/' + session.token.sub + '/token' | ||||
|       + '/api/tunnel@oauth3.org/accounts/' + session.token.sub + '/token' | ||||
|   , session: session | ||||
|   , data: { | ||||
|       domains: opts.data.domains | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| { | ||||
|   "name": "oauth3.js", | ||||
|   "version": "1.0.7", | ||||
|   "version": "1.2.2", | ||||
|   "description": "The world's smallest, fastest, and most secure OAuth3 (and OAuth2) JavaScript implementation.", | ||||
|   "main": "oauth3.node.js", | ||||
|   "scripts": { | ||||
| @ -9,7 +9,7 @@ | ||||
|   }, | ||||
|   "repository": { | ||||
|     "type": "git", | ||||
|     "url": "git@git.daplie.com:OAuth3/oauth3.js.git" | ||||
|     "url": "git@git.oauth3.org:OAuth3/oauth3.js.git" | ||||
|   }, | ||||
|   "keywords": [ | ||||
|     "oauth", | ||||
| @ -35,7 +35,7 @@ | ||||
|     "bluebird": "^3.5.0", | ||||
|     "elliptic": "^6.4.0", | ||||
|     "request": "^2.81.0", | ||||
|     "terminal-forms.js": "git+https://git.daplie.com/OAuth3/terminal-forms.js.git#v1" | ||||
|     "terminal-forms.js": "git+https://git.oauth3.org/OAuth3/terminal-forms.js.git#v1" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "browserify-aes": "^1.0.6", | ||||
| @ -49,6 +49,6 @@ | ||||
|     "gulp-uglify": "^2.1.0", | ||||
|     "vinyl-source-stream": "^1.1.0" | ||||
|   }, | ||||
|   "author": "AJ ONeal <aj@daplie.com> (https://daplie.com/)", | ||||
|   "author": "AJ ONeal <coolaj86@gmail.com> (https://coolaj86.com/)", | ||||
|   "license": "(MIT OR Apache-2.0)" | ||||
| } | ||||
|  | ||||
| @ -1,560 +0,0 @@ | ||||
| ;(function (exports) { | ||||
|   'use strict'; | ||||
| 
 | ||||
|   var OAUTH3 = exports.OAUTH3; | ||||
|   var OAUTH3_CORE = exports.OAUTH3_CORE; | ||||
| 
 | ||||
|   function getDefaultAppUrl() { | ||||
|     console.warn('[deprecated] using window.location.{protocol, host, pathname} when opts.client_id should be used'); | ||||
|     return window.location.protocol | ||||
|       + '//' + window.location.host | ||||
|       + (window.location.pathname).replace(/\/?$/, '') | ||||
|       ; | ||||
|   } | ||||
| 
 | ||||
|   var browser = exports.OAUTH3_BROWSER = { | ||||
|     window: window | ||||
|   , clientUri: function (location) { | ||||
|       return OAUTH3_CORE.normalizeUri(location.host + location.pathname); | ||||
|     } | ||||
|   , discover: function (providerUri, opts) { | ||||
|       if (!providerUri) { | ||||
|         throw new Error('oauth3.discover(providerUri, opts) received providerUri as ' + providerUri); | ||||
|       } | ||||
|       var directives = OAUTH3.hooks.getDirectives(providerUri); | ||||
|       if (directives && directives.issuer) { | ||||
|         return OAUTH3.PromiseA.resolve(directives); | ||||
|       } | ||||
|       return browser._discoverHelper(providerUri, opts).then(function (directives) { | ||||
|         directives.issuer = directives.issuer || OAUTH3_CORE.normalizeUrl(providerUri); | ||||
|         console.log('discoverHelper', directives); | ||||
|         return OAUTH3.hooks.setDirectives(providerUri, directives); | ||||
|       }); | ||||
|     } | ||||
|   , _discoverHelper: function (providerUri, opts) { | ||||
|       opts = opts || {}; | ||||
|       //opts.debug = true;
 | ||||
|       providerUri = OAUTH3_CORE.normalizeUrl(providerUri); | ||||
|       if (window.location.hostname.match(providerUri)) { | ||||
|         console.warn("It looks like you're a provider checking for your own directive," | ||||
|           + " so we we're just gonna use OAUTH3.request({ method: 'GET', url: '.well-known/oauth3/directive.json' })"); | ||||
|         return OAUTH3.request({ | ||||
|           method: 'GET' | ||||
|         , url: OAUTH3.core.normalizeUrl(providerUri) + '/.well-known/oauth3/directives.json' | ||||
|         }); | ||||
|       } | ||||
| 
 | ||||
|       if (!window.location.hostname.match(opts.client_id || opts.client_uri)) { | ||||
|         console.warn("It looks like your client_id doesn't match your current window... this probably won't end well"); | ||||
|         console.warn(opts.client_id || opts.client_uri, window.location.hostname); | ||||
|       } | ||||
|       var discObj = OAUTH3_CORE.urls.discover(providerUri, { client_id: (opts.client_id || opts.client_uri || getDefaultAppUrl()), debug: opts.debug }); | ||||
| 
 | ||||
|       // TODO ability to reuse iframe instead of closing
 | ||||
|       return browser.insertIframe(discObj.url, discObj.state, opts).then(function (params) { | ||||
|         if (params.error) { | ||||
|           return OAUTH3_CORE.formatError(providerUri, params.error); | ||||
|         } | ||||
|         var directives = JSON.parse(atob(OAUTH3_CORE.utils.urlSafeBase64ToBase64(params.result || params.directives))); | ||||
|         return directives; | ||||
|       }, function (err) { | ||||
|         return OAUTH3.PromiseA.reject(err); | ||||
|       }); | ||||
|     } | ||||
| 
 | ||||
|   , discoverAuthorizationDialog: function(providerUri, opts) { | ||||
|       var discObj = OAUTH3.core.discover(providerUri, opts); | ||||
| 
 | ||||
|       // hmm... we're gonna need a broker for this since switching windows is distracting,
 | ||||
|       // popups are obnoxious, iframes are sometimes blocked, and most servers don't implement CORS
 | ||||
|       // eventually it should be the browser (and postMessage may be a viable option now), but whatever...
 | ||||
| 
 | ||||
|       // TODO allow postMessage from providerUri in addition to callback
 | ||||
|       var discWin = OAUTH3.openWindow(discObj.url, discObj.state, { reuseWindow: 'conquerer' }); | ||||
|       return discWin.then(function (params) { | ||||
|         console.log('discwin params'); | ||||
|         console.log(params); | ||||
|         // discWin.child
 | ||||
|         // TODO params should have response_type indicating json, binary, etc
 | ||||
|         var directives = JSON.parse(atob(OAUTH3.core.utils.urlSafeBase64ToBase64(params.result || params.directives))); | ||||
|         console.log('directives'); | ||||
|         console.log(directives); | ||||
| 
 | ||||
|         // Do some stuff
 | ||||
|         var authObj = OAUTH3.core.implicitGrant( | ||||
|           directives | ||||
|         , { redirect_uri: opts.redirect_uri | ||||
|           , debug: opts.debug | ||||
|           , client_id: opts.client_id || opts.client_uri | ||||
|           , client_uri: opts.client_uri || opts.client_id | ||||
|           } | ||||
|         ); | ||||
| 
 | ||||
|         if (params.debug) { | ||||
|           window.alert("DEBUG MODE: Pausing so you can look at logs and whatnot :) Fire at will!"); | ||||
|         } | ||||
| 
 | ||||
|         return new OAUTH3.PromiseA(function (resolve, reject) { | ||||
|           // TODO check if authObj.url is relative or full
 | ||||
|           discWin.child.location = OAUTH3.core.urls.resolve(providerUri, authObj.url); | ||||
| 
 | ||||
|           if (params.debug) { | ||||
|             discWin.child.focus(); | ||||
|           } | ||||
| 
 | ||||
|           window['--oauth3-callback-' + authObj.state] = function (tokens) { | ||||
|             if (tokens.error) { | ||||
|               return reject(OAUTH3.core.formatError(tokens.error)); | ||||
|             } | ||||
| 
 | ||||
|             if (params.debug || tokens.debug) { | ||||
|               if (window.confirm("DEBUG MODE: okay to close oauth3 window?")) { | ||||
|                 discWin.child.close(); | ||||
|               } | ||||
|             } | ||||
|             else { | ||||
|               discWin.child.close(); | ||||
|             } | ||||
| 
 | ||||
|             resolve(tokens); | ||||
|           }; | ||||
|         }); | ||||
|       }).then(function (tokens) { | ||||
|         return OAUTH3.hooks.refreshSession( | ||||
|           opts.session || { | ||||
|             provider_uri: providerUri | ||||
|           , client_id: opts.client_id | ||||
|           , client_uri: opts.client_uri || opts.clientUri | ||||
|           } | ||||
|         , tokens | ||||
|         ); | ||||
|       }); | ||||
|     } | ||||
| 
 | ||||
|   , frameRequest: function (url, state, opts) { | ||||
|       var promise; | ||||
| 
 | ||||
|       if (!opts.windowType) { | ||||
|         opts.windowType = 'popup'; | ||||
|       } | ||||
| 
 | ||||
|       if ('background' === opts.windowType) { | ||||
|         promise = browser.insertIframe(url, state, opts); | ||||
|       } else if ('popup' === opts.windowType) { | ||||
|         promise = browser.openWindow(url, state, opts); | ||||
|       } else if ('inline' === opts.windowType) { | ||||
|         // callback function will never execute and would need to redirect back to current page
 | ||||
|         // rather than the callback.html
 | ||||
|         url += '&original_url=' + browser.window.location.href; | ||||
|         promise = browser.window.location = url; | ||||
|       } else { | ||||
|         throw new Error("login framing method options.windowType not specified or not type yet implemented"); | ||||
|       } | ||||
| 
 | ||||
|       return promise.then(function (params) { | ||||
|         var err; | ||||
| 
 | ||||
|         if (params.error || params.error_description) { | ||||
|           err = new Error(params.error_description || "Unknown response error"); | ||||
|           err.code = params.error || "E_UKNOWN_ERROR"; | ||||
|           err.params = params; | ||||
|           return OAUTH3.PromiseA.reject(err); | ||||
|         } | ||||
| 
 | ||||
|         return params; | ||||
|       }); | ||||
|     } | ||||
| 
 | ||||
|   , insertIframe: function (url, state, opts) { | ||||
|       opts = opts || {}; | ||||
|       if (opts.debug) { | ||||
|         opts.timeout = opts.timeout || 15 * 60 * 1000; | ||||
|       } | ||||
|       var promise = new OAUTH3.PromiseA(function (resolve, reject) { | ||||
|         var tok; | ||||
|         var iframeDiv; | ||||
| 
 | ||||
|         function cleanup() { | ||||
|           delete window['--oauth3-callback-' + state]; | ||||
|           iframeDiv.remove(); | ||||
|           clearTimeout(tok); | ||||
|           tok = null; | ||||
|         } | ||||
| 
 | ||||
|         window['--oauth3-callback-' + state] = function (params) { | ||||
|           resolve(params); | ||||
|           cleanup(); | ||||
|         }; | ||||
| 
 | ||||
|         tok = setTimeout(function () { | ||||
|           var err = new Error("the iframe request did not complete within 15 seconds"); | ||||
|           err.code = "E_TIMEOUT"; | ||||
|           reject(err); | ||||
|           cleanup(); | ||||
|         }, opts.timeout || 15 * 1000); | ||||
| 
 | ||||
|         // TODO hidden / non-hidden (via directive even)
 | ||||
|         var framesrc = '<iframe class="js-oauth3-iframe" src="' + url + '" '; | ||||
|         if (opts.debug) { | ||||
|           framesrc += ' width="800px" height="800px" style="opacity: 0.8;" frameborder="1"'; | ||||
|         } | ||||
|         else { | ||||
|           framesrc += ' width="1px" height="1px" frameborder="0"'; | ||||
|         } | ||||
|         framesrc += '></iframe>'; | ||||
| 
 | ||||
|         iframeDiv = window.document.createElement('div'); | ||||
|         iframeDiv.innerHTML = framesrc; | ||||
|         window.document.body.appendChild(iframeDiv); | ||||
|       }); | ||||
| 
 | ||||
|       // TODO periodically garbage collect expired handlers from window object
 | ||||
|       return promise; | ||||
|     } | ||||
| 
 | ||||
|   , openWindow: function (url, state, opts) { | ||||
|       if (opts.debug) { | ||||
|         opts.timeout = opts.timeout || 15 * 60 * 1000; | ||||
|       } | ||||
|       var promise = new OAUTH3.PromiseA(function (resolve, reject) { | ||||
|         var tok; | ||||
| 
 | ||||
|         function cleanup() { | ||||
|           clearTimeout(tok); | ||||
|           tok = null; | ||||
|           delete window['--oauth3-callback-' + state]; | ||||
|           // this is last in case the window self-closes synchronously
 | ||||
|           // (should never happen, but that's a negotiable implementation detail)
 | ||||
|           if (!opts.reuseWindow) { | ||||
|             promise.child.close(); | ||||
|           } | ||||
|         } | ||||
| 
 | ||||
|         window['--oauth3-callback-' + state] = function (params) { | ||||
|           console.log('YOLO!!'); | ||||
|           resolve(params); | ||||
|           cleanup(); | ||||
|         }; | ||||
| 
 | ||||
|         tok = setTimeout(function () { | ||||
|           var err = new Error("the windowed request did not complete within 3 minutes"); | ||||
|           err.code = "E_TIMEOUT"; | ||||
|           reject(err); | ||||
|           cleanup(); | ||||
|         }, opts.timeout || 3 * 60 * 1000); | ||||
| 
 | ||||
|         setTimeout(function () { | ||||
|           if (!promise.child) { | ||||
|             reject("TODO: open the iframe first and discover oauth3 directives before popup"); | ||||
|             cleanup(); | ||||
|           } | ||||
|         }, 0); | ||||
|       }); | ||||
| 
 | ||||
|       // TODO allow size changes (via directive even)
 | ||||
|       promise.child = window.open( | ||||
|         url | ||||
|       , 'oauth3-login-' + (opts.reuseWindow || state) | ||||
|       , 'height=' + (opts.height || 720) + ',width=' + (opts.width || 620) | ||||
|       ); | ||||
|       // TODO periodically garbage collect expired handlers from window object
 | ||||
|       return promise; | ||||
|     } | ||||
| 
 | ||||
|     //
 | ||||
|     // Logins
 | ||||
|     //
 | ||||
|   , authn: { | ||||
|       authorizationRedirect: function (providerUri, opts) { | ||||
|         // TODO get own directives
 | ||||
| 
 | ||||
|         return OAUTH3.discover(providerUri, opts).then(function (directive) { | ||||
|           var prequest = OAUTH3_CORE.urls.authorizationRedirect( | ||||
|             directive | ||||
|           , opts | ||||
|           ); | ||||
| 
 | ||||
|           if (!prequest.state) { | ||||
|             throw new Error("[Devolper Error] [authorization redirect] prequest.state is empty"); | ||||
|           } | ||||
| 
 | ||||
|           return browser.frameRequest(prequest.url, prequest.state, opts); | ||||
|         }).then(function (tokens) { | ||||
|           return OAUTH3.hooks.refreshSession( | ||||
|             opts.session || { | ||||
|               provider_uri: providerUri | ||||
|             , client_id: opts.client_id | ||||
|             , client_uri: opts.client_uri || opts.clientUri | ||||
|             } | ||||
|           , tokens | ||||
|           ); | ||||
|         }); | ||||
|       } | ||||
|     , implicitGrant: function (providerUri, opts) { | ||||
|         // TODO let broker=true change behavior to open discover inline with frameRequest
 | ||||
|         // TODO OAuth3 provider should use the redirect URI as the appId?
 | ||||
|         return OAUTH3.discover(providerUri, opts).then(function (directive) { | ||||
|           var prequest = OAUTH3_CORE.urls.implicitGrant( | ||||
|             directive | ||||
|             // TODO OAuth3 provider should referrer / referer / origin as the appId?
 | ||||
|           , opts | ||||
|           ); | ||||
| 
 | ||||
|           if (!prequest.state) { | ||||
|             throw new Error("[Devolper Error] [implicit grant] prequest.state is empty"); | ||||
|           } | ||||
| 
 | ||||
|           return browser.frameRequest(prequest.url, prequest.state, opts); | ||||
|         }).then(function (tokens) { | ||||
|           return OAUTH3.hooks.refreshSession( | ||||
|             opts.session || { | ||||
|               provider_uri: providerUri | ||||
|             , client_id: opts.client_id | ||||
|             , client_uri: opts.client_uri || opts.clientUri | ||||
|             } | ||||
|           , tokens | ||||
|           ); | ||||
|         }); | ||||
|       } | ||||
|     , logout: function (providerUri, opts) { | ||||
|         opts = opts || {}; | ||||
| 
 | ||||
|         return OAUTH3.discover(providerUri, opts).then(function (directive) { | ||||
|           var prequest = OAUTH3_CORE.urls.logout( | ||||
|             directive | ||||
|           , opts | ||||
|           ); | ||||
|           // Oauth3.init({ logout: function () {} });
 | ||||
|           //return Oauth3.logout();
 | ||||
| 
 | ||||
|           var redirectUri = opts.redirect_uri || opts.redirectUri | ||||
|             || (window.location.protocol + '//' + (window.location.host + window.location.pathname) + 'oauth3.html') | ||||
|             ; | ||||
|           var params = { | ||||
|             // logout=true for all logins/accounts
 | ||||
|             // logout=app-scoped-login-id for a single login
 | ||||
|             action: 'logout' | ||||
|             // TODO specify specific accounts / logins to delete from session
 | ||||
|           , accounts: true | ||||
|           , logins: true | ||||
|           , redirect_uri: redirectUri | ||||
|           , state: prequest.state | ||||
|           , debug: opts.debug | ||||
|           }; | ||||
| 
 | ||||
|           if (prequest.url === params.redirect_uri) { | ||||
|             return OAUTH3.PromiseA.resolve(); | ||||
|           } | ||||
| 
 | ||||
|           prequest.url += '#' + OAUTH3_CORE.querystringify(params); | ||||
| 
 | ||||
|           return OAUTH3.insertIframe(prequest.url, prequest.state, opts); | ||||
|         }); | ||||
|       } | ||||
|     } | ||||
|   , isIframe: function isIframe () { | ||||
|       try { | ||||
|         return window.self !== window.top; | ||||
|       } catch (e) { | ||||
|         return true; | ||||
|       } | ||||
|     } | ||||
|   , parseUrl: function (url) { | ||||
|       var parser = document.createElement('a'); | ||||
|       parser.href = url; | ||||
|       return parser; | ||||
|     } | ||||
|   , isRedirectHostSafe: function (referrerUrl, redirectUrl) { | ||||
|       var src = browser.parseUrl(referrerUrl); | ||||
|       var dst = browser.parseUrl(redirectUrl); | ||||
| 
 | ||||
|       // TODO how should we handle subdomains?
 | ||||
|       // It should be safe for api.example.com to redirect to example.com
 | ||||
|       // But it may not be safe for to example.com to redirect to aj.example.com
 | ||||
|       // It is also probably not safe for sally.example.com to redirect to john.example.com
 | ||||
|       // The client should have a list of allowed URLs to choose from and perhaps a wildcard will do
 | ||||
|       //
 | ||||
|       // api.example.com.evil.com SHOULD NOT match example.com
 | ||||
|       return dst.hostname === src.hostname; | ||||
|     } | ||||
|   , checkRedirect: function (client, query) { | ||||
|       console.warn("[security] URL path checking not yet implemented"); | ||||
| 
 | ||||
|       var clientUrl = OAUTH3.core.normalizeUrl(client.url); | ||||
|       var redirectUrl = OAUTH3.core.normalizeUrl(query.redirect_uri); | ||||
| 
 | ||||
|       // General rule:
 | ||||
|       // I can callback to a shorter domain (fewer subs) or a shorter path (on the same domain)
 | ||||
|       // but not a longer (more subs) or different domain or a longer path (on the same domain)
 | ||||
| 
 | ||||
| 
 | ||||
|       // We can callback to an explicitly listed domain (TODO and path)
 | ||||
|       if (browser.isRedirectHostSafe(clientUrl, redirectUrl)) { | ||||
|         return true; | ||||
|       } | ||||
| 
 | ||||
|       return false; | ||||
|     } | ||||
|   /* | ||||
|   , redirect: function (redirect) { | ||||
|       if (parser.search) { | ||||
|         parser.search += '&'; | ||||
|       } else { | ||||
|         parser.search += '?'; | ||||
|       } | ||||
| 
 | ||||
|       parser.search += 'error=E_NO_SESSION'; | ||||
|       redirectUri = parser.href; | ||||
| 
 | ||||
|       window.location.href = redirectUri; | ||||
|     } | ||||
|   */ | ||||
| 
 | ||||
|   , hackFormSubmit: function (opts) { | ||||
|       opts = opts || {}; | ||||
|       scope.authorizationDecisionUri = DaplieApiConfig.providerUri + '/api/org.oauth3.provider/authorization_decision'; | ||||
|       scope.updateScope(); | ||||
| 
 | ||||
|       var redirectUri = scope.appQuery.redirect_uri.replace(/^https?:\/\//i, 'https://'); | ||||
|       var separator; | ||||
| 
 | ||||
|       // TODO check that we appropriately use '#' for implicit and '?' for code
 | ||||
|       // (server-side) in an OAuth2 backwards-compatible way
 | ||||
|       if ('token' === scope.appQuery.response_type) { | ||||
|         separator = '#'; | ||||
|       } | ||||
|       else if ('code' === scope.appQuery.response_type) { | ||||
|         separator = '?'; | ||||
|       } | ||||
|       else { | ||||
|         separator = '#'; | ||||
|       } | ||||
| 
 | ||||
|       if (scope.pendingScope.length && !opts.allow) { | ||||
|         redirectUri += separator + Oauth3.querystringify({ | ||||
|           error: 'access_denied' | ||||
|           , error_description: 'None of the permissions were accepted' | ||||
|           , error_uri: 'https://oauth3.org/docs/errors#access_denied' | ||||
|           , state: scope.appQuery.state | ||||
|         }); | ||||
|         window.location.href = redirectUri; | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
|       // TODO move to Oauth3? or not?
 | ||||
|       // this could be implementation-specific,
 | ||||
|       // but it may still be nice to provide it as de-facto
 | ||||
|       var url = DaplieApiConfig.apiBaseUri + '/api/org.oauth3.provider/grants/:client_id/:account_id' | ||||
|         .replace(/:client_id/g, scope.appQuery.client_id || scope.appQuery.client_uri) | ||||
|         .replace(/:account_id/g, scope.selectedAccountId) | ||||
|         ; | ||||
| 
 | ||||
|       var account = scope.sessionAccount; | ||||
|       var session = { accessToken: account.token, refreshToken: account.refreshToken }; | ||||
|       var preq = { | ||||
|         url: url | ||||
|       , method: 'POST' | ||||
|       , data: { | ||||
|           scope: updateAccepted() | ||||
|         , response_type: scope.appQuery.response_type | ||||
|         , referrer: document.referrer || document.referer || '' | ||||
|         , referer: document.referrer || document.referer || '' | ||||
|         , tenant_id: scope.appQuery.tenant_id | ||||
|         , client_id: scope.appQuery.client_id | ||||
|         , client_uri: scope.appQuery.client_uri | ||||
|         } | ||||
|       , session: session | ||||
|       }; | ||||
|       preq.clientId = preq.appId = DaplieApiConfig.appId || DaplieApiConfig.clientId; | ||||
|       preq.clientUri = preq.appUri = DaplieApiConfig.appUri || DaplieApiConfig.clientUri; | ||||
|       // TODO need a way to have middleware in Oauth3.request for TherapySession et al
 | ||||
| 
 | ||||
|       return Oauth3.request(preq).then(function (resp) { | ||||
|         var err; | ||||
|         var data = resp.data || {}; | ||||
| 
 | ||||
|         if (data.error) { | ||||
|           err = new Error(data.error.message || data.errorDescription); | ||||
|           err.message = data.error.message || data.errorDescription; | ||||
|           err.code = resp.data.error.code || resp.data.error; | ||||
|           err.uri = 'https://oauth3.org/docs/errors#' + (resp.data.error.code || resp.data.error); | ||||
|           return $q.reject(err); | ||||
|         } | ||||
| 
 | ||||
|         if (!(data.code || data.accessToken)) { | ||||
|           err = new Error("No grant code"); | ||||
|           return $q.reject(err); | ||||
|         } | ||||
| 
 | ||||
|         return data; | ||||
|       }).then(function (data) { | ||||
|         redirectUri += separator + Oauth3.querystringify({ | ||||
|           state: scope.appQuery.state | ||||
| 
 | ||||
|         , code: data.code | ||||
| 
 | ||||
|         , access_token: data.access_token | ||||
|         , expires_at: data.expires_at | ||||
|         , expires_in: data.expires_in | ||||
|         , scope: data.scope | ||||
| 
 | ||||
|         , refresh_token: data.refresh_token | ||||
|         , refresh_expires_at: data.refresh_expires_at | ||||
|         , refresh_expires_in: data.refresh_expires_in | ||||
|         }); | ||||
| 
 | ||||
|         if ('token' === scope.appQuery.response_type) { | ||||
|           window.location.href = redirectUri; | ||||
|           return; | ||||
|         } | ||||
|         else if ('code' === scope.appQuery.response_type) { | ||||
|           scope.hackFormSubmitHelper(redirectUri); | ||||
|           return; | ||||
|         } | ||||
|         else { | ||||
|           console.warn("Grant Code NOT IMPLEMENTED for '" + scope.appQuery.response_type + "'"); | ||||
|           console.warn(redirectUri); | ||||
|           throw new Error("Grant Code NOT IMPLEMENTED for '" + scope.appQuery.response_type + "'"); | ||||
|         } | ||||
|       }, function (err) { | ||||
|         redirectUri += separator + Oauth3.querystringify({ | ||||
|           error: err.code || 'server_error' | ||||
|         , error_description: err.message || "Server Error: It's not your fault" | ||||
|         , error_uri: err.uri || 'https://oauth3.org/docs/errors#server_error' | ||||
|         , state: scope.appQuery.state | ||||
|         }); | ||||
| 
 | ||||
|         console.error('Grant Code Error NOT IMPLEMENTED'); | ||||
|         console.error(err); | ||||
|         console.error(redirectUri); | ||||
|         //window.location.href = redirectUri;
 | ||||
|       }); | ||||
|     } | ||||
| 
 | ||||
|   , hackFormSubmitHelper: function (uri) { | ||||
|       // TODO de-jQuerify
 | ||||
|       //window.location.href = redirectUri;
 | ||||
|       //return;
 | ||||
| 
 | ||||
|       // the only way to do a POST that redirects the current window
 | ||||
|       window.jQuery('form.js-hack-hidden-form').attr('action', uri); | ||||
| 
 | ||||
|       // give time for the apply to take place
 | ||||
|       window.setTimeout(function () { | ||||
|         window.jQuery('form.js-hack-hidden-form').submit(); | ||||
|       }, 50); | ||||
|     } | ||||
|   }; | ||||
|   browser.requests = browser.authn; | ||||
| 
 | ||||
|   Object.keys(browser).forEach(function (key) { | ||||
|     if ('requests' === key) { | ||||
|       Object.keys(browser.requests).forEach(function (key) { | ||||
|         OAUTH3.requests[key] = browser.requests[key]; | ||||
|       }); | ||||
|       return; | ||||
|     } | ||||
|     OAUTH3[key] = browser[key]; | ||||
|   }); | ||||
| 
 | ||||
| }('undefined' !== typeof exports ? exports : window)); | ||||
| @ -1,42 +0,0 @@ | ||||
|   oauth3.discover = function (providerUri, opts) { | ||||
|     opts = opts || {}; | ||||
| 
 | ||||
|     console.log('DEBUG oauth3.discover', providerUri); | ||||
|     console.log(opts); | ||||
|     if (opts.directives) { | ||||
|       return oauth3.PromiseA.resolve(opts.directives); | ||||
|     } | ||||
| 
 | ||||
|     var promise; | ||||
|     var promise2; | ||||
|     var directives; | ||||
|     var updatedAt; | ||||
|     var fresh; | ||||
| 
 | ||||
|     providerUri = oauth3.normalizeUrl(providerUri); | ||||
|     try { | ||||
|       directives = JSON.parse(localStorage.getItem('oauth3.' + providerUri + '.directives')); | ||||
|       console.log('DEBUG oauth3.discover cache', directives); | ||||
|       updatedAt = localStorage.getItem('oauth3.' + providerUri + '.directives.updated_at'); | ||||
|       console.log('DEBUG oauth3.discover updatedAt', updatedAt); | ||||
|       updatedAt = new Date(updatedAt).valueOf(); | ||||
|       console.log('DEBUG oauth3.discover updatedAt', updatedAt); | ||||
|     } catch(e) { | ||||
|       // ignore
 | ||||
|     } | ||||
| 
 | ||||
|     fresh = (Date.now() - updatedAt) < (24 * 60 * 60 * 1000); | ||||
| 
 | ||||
|     if (directives) { | ||||
|       promise = oauth3.PromiseA.resolve(directives); | ||||
| 
 | ||||
|       if (fresh) { | ||||
|         //console.log('[local] [fresh directives]', directives);
 | ||||
|         return promise; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     promise2 = oauth3._discoverHelper(providerUri, opts); | ||||
| 
 | ||||
|     return promise || promise2; | ||||
|   }; | ||||
| @ -1,473 +0,0 @@ | ||||
| ;(function (exports) { | ||||
|   'use strict'; | ||||
| 
 | ||||
|   // NOTE: we assume that directive.provider_uri exists
 | ||||
| 
 | ||||
|   var core = {}; | ||||
|   core.urls = core; | ||||
| 
 | ||||
|   function getDefaultAppApiBase() { | ||||
|     console.warn('[deprecated] using window.location.host when opts.appApiBase should be used'); | ||||
|     return 'https://' + window.location.host; | ||||
|   } | ||||
| 
 | ||||
|   core.parsescope = function (scope) { | ||||
|     return (scope||'').split(/[+, ]/g); | ||||
|   }; | ||||
|   core.stringifyscope = function (scope) { | ||||
|     if (Array.isArray(scope)) { | ||||
|       scope = scope.join(' '); | ||||
|     } | ||||
|     return scope; | ||||
|   }; | ||||
| 
 | ||||
|   core.querystringify = function (params) { | ||||
|     var qs = []; | ||||
| 
 | ||||
|     Object.keys(params).forEach(function (key) { | ||||
|       // TODO nullify instead?
 | ||||
|       if ('undefined' === typeof params[key]) { | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
|       if ('scope' === key) { | ||||
|         params[key] = core.stringifyscope(params[key]); | ||||
|       } | ||||
| 
 | ||||
|       qs.push(encodeURIComponent(key) + '=' + encodeURIComponent(params[key])); | ||||
|     }); | ||||
| 
 | ||||
|     return qs.join('&'); | ||||
|   }; | ||||
| 
 | ||||
|   // Modified from http://stackoverflow.com/a/7826782
 | ||||
|   core.queryparse = function (search) { | ||||
|     // parse a query or a hash
 | ||||
|     if (-1 !== ['#', '?'].indexOf(search[0])) { | ||||
|       search = search.substring(1); | ||||
|     } | ||||
|     // Solve for case of search within hash
 | ||||
|     // example: #/authorization_dialog/?state=...&redirect_uri=...
 | ||||
|     var queryIndex = search.indexOf('?'); | ||||
|     if (-1 !== queryIndex) { | ||||
|       search = search.substr(queryIndex + 1); | ||||
|     } | ||||
| 
 | ||||
|     var args = search.split('&'); | ||||
|     var argsParsed = {}; | ||||
|     var i, arg, kvp, key, value; | ||||
| 
 | ||||
|     for (i = 0; i < args.length; i += 1) { | ||||
| 
 | ||||
|         arg = args[i]; | ||||
| 
 | ||||
|         if (-1 === arg.indexOf('=')) { | ||||
| 
 | ||||
|           argsParsed[decodeURIComponent(arg).trim()] = true; | ||||
| 
 | ||||
|         } | ||||
|         else { | ||||
| 
 | ||||
|           kvp = arg.split('='); | ||||
|           key = decodeURIComponent(kvp[0]).trim(); | ||||
|           value = decodeURIComponent(kvp[1]).trim(); | ||||
|           argsParsed[key] = value; | ||||
| 
 | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return argsParsed; | ||||
|   }; | ||||
| 
 | ||||
|   core.formatError = function (providerUri, params) { | ||||
|     var err = new Error(params.error_description || params.error.message || "Unknown error when discoving provider '" + providerUri + "'"); | ||||
|     err.uri = params.error_uri || params.error.uri; | ||||
|     err.code = params.error.code || params.error; | ||||
|     return err; | ||||
|   }; | ||||
|   core.normalizePath = function (path) { | ||||
|     return path.replace(/^\//, '').replace(/\/$/, ''); | ||||
|   }; | ||||
|   core.normalizeUri = function (providerUri) { | ||||
|     // tested with
 | ||||
|     //   example.com
 | ||||
|     //   example.com/
 | ||||
|     //   http://example.com
 | ||||
|     //   https://example.com/
 | ||||
|     return providerUri | ||||
|       .replace(/^(https?:\/\/)?/i, '') | ||||
|       .replace(/\/?$/, '') | ||||
|       ; | ||||
|   }; | ||||
|   core.normalizeUrl = function (providerUri) { | ||||
|     // tested with
 | ||||
|     //   example.com
 | ||||
|     //   example.com/
 | ||||
|     //   http://example.com
 | ||||
|     //   https://example.com/
 | ||||
|     return providerUri | ||||
|       .replace(/^(https?:\/\/)?/i, 'https://') | ||||
|       .replace(/\/?$/, '') | ||||
|       ; | ||||
|   }; | ||||
| 
 | ||||
|   // these might not really belong in core... not sure
 | ||||
|   // there should be node.js- and browser-specific versions probably
 | ||||
|   core.utils = { | ||||
|     urlSafeBase64ToBase64: function (b64) { | ||||
|       // URL-safe Base64 to Base64
 | ||||
|       // https://en.wikipedia.org/wiki/Base64
 | ||||
|       // https://gist.github.com/catwell/3046205
 | ||||
|       var mod = b64.length % 4; | ||||
|       if (2 === mod) { b64 += '=='; } | ||||
|       if (3 === mod) { b64 += '='; } | ||||
|       b64 = b64.replace(/-/g, '+').replace(/_/g, '/'); | ||||
|       return b64; | ||||
|     } | ||||
|   , base64ToUrlSafeBase64: function (b64) { | ||||
|       // Base64 to URL-safe Base64
 | ||||
|       b64 = b64.replace(/\+/g, '-').replace(/\//g, '_'); | ||||
|       b64 = b64.replace(/=+/g, ''); | ||||
|       return b64; | ||||
|     } | ||||
|   , randomState: function () { | ||||
|       var i; | ||||
|       var ch; | ||||
|       var str; | ||||
| 
 | ||||
|       // TODO put in different file for browser vs node
 | ||||
|       try { | ||||
|         return Array.prototype.slice.call(window.crypto.getRandomValues(new Uint8Array(16))).map(function (ch) { return (ch).toString(16); }).join(''); | ||||
|       } catch(e) { | ||||
|         // TODO use fisher-yates on 0..255 and select [0] 16 times
 | ||||
|         // [security] https://medium.com/@betable/tifu-by-using-math-random-f1c308c4fd9d#.5qx0bf95a
 | ||||
|         // https://github.com/v8/v8/blob/b0e4dce6091a8777bda80d962df76525dc6c5ea9/src/js/math.js#L135-L144
 | ||||
|         // Note: newer versions of v8 do not have this bug, but other engines may still
 | ||||
|         console.warn('[security] crypto.getRandomValues() failed, falling back to Math.random()'); | ||||
|         str = ''; | ||||
|         for (i = 0; i < 32; i += 1) { | ||||
|           ch = Math.round(Math.random() * 255).toString(16); | ||||
|           if (ch.length < 2) { ch = '0' + ch; } | ||||
|           str += ch; | ||||
|         } | ||||
|         return str; | ||||
|       } | ||||
|     } | ||||
|   }; | ||||
|   core.jwt = { | ||||
|     // decode only (no verification)
 | ||||
|     decode: function (str) { | ||||
| 
 | ||||
|       // 'abc.qrs.xyz'
 | ||||
|       // [ 'abc', 'qrs', 'xyz' ]
 | ||||
|       // [ {}, {}, 'foo' ]
 | ||||
|       // { header: {}, payload: {}, signature: }
 | ||||
|       var parts = str.split(/\./g); | ||||
|       var jsons = parts.slice(0, 2).map(function (urlsafe64) { | ||||
|         var atob = exports.atob || require('atob'); | ||||
|         var b64 = core.utils.urlSafeBase64ToBase64(urlsafe64); | ||||
|         return atob(b64); | ||||
|       }); | ||||
| 
 | ||||
|       return { | ||||
|         header: JSON.parse(jsons[0]) | ||||
|       , payload: JSON.parse(jsons[1]) | ||||
|       , signature: parts[2] // should remain url-safe base64
 | ||||
|       }; | ||||
|     } | ||||
|   , getFreshness: function (tokenMeta, staletime, now) { | ||||
|       staletime = staletime || (15 * 60); | ||||
|       now = now || Date.now(); | ||||
|       var fresh = ((parseInt(tokenMeta.exp, 10) || 0) - Math.round(now / 1000)); | ||||
| 
 | ||||
|       if (fresh >= staletime) { | ||||
|         return 'fresh'; | ||||
|       } | ||||
| 
 | ||||
|       if (fresh <= 0) { | ||||
|         return 'expired'; | ||||
|       } | ||||
| 
 | ||||
|       return 'stale'; | ||||
|     } | ||||
|     // encode-only (no signature)
 | ||||
|   , encode: function (parts) { | ||||
|       parts.header = parts.header || { alg: 'none', typ: 'jwt' }; | ||||
|       parts.signature = parts.signature || ''; | ||||
| 
 | ||||
|       var btoa = exports.btoa || require('btoa'); | ||||
|       var result = [ | ||||
|         core.utils.base64ToUrlSafeBase64(btoa(JSON.stringify(parts.header, null))) | ||||
|       , core.utils.base64ToUrlSafeBase64(btoa(JSON.stringify(parts.payload, null))) | ||||
|       , parts.signature // should already be url-safe base64
 | ||||
|       ].join('.'); | ||||
| 
 | ||||
|       return result; | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   core.urls.discover = function (providerUri, opts) { | ||||
|     if (!providerUri) { | ||||
|       throw new Error("cannot discover without providerUri"); | ||||
|     } | ||||
|     if (!opts.client_id) { | ||||
|       throw new Error("cannot discover without options.client_id"); | ||||
|     } | ||||
|     var clientId = core.normalizeUrl(opts.client_id || opts.client_uri); | ||||
|     providerUri = core.normalizeUrl(providerUri); | ||||
| 
 | ||||
|     var params = { | ||||
|       action: 'directives' | ||||
|     , state: core.utils.randomState() | ||||
|     , redirect_uri: clientId + (opts.client_callback_path || '/.well-known/oauth3/callback.html') | ||||
|     , response_type: 'rpc' | ||||
|     , _method: 'GET' | ||||
|     , _pathname: '.well-known/oauth3/directives.json' | ||||
|     , debug: opts.debug || undefined | ||||
|     }; | ||||
| 
 | ||||
|     var result = { | ||||
|       url: providerUri + '/.well-known/oauth3/#/?' + core.querystringify(params) | ||||
|     , state: params.state | ||||
|     , method: 'GET' | ||||
|     , query: params | ||||
|     }; | ||||
| 
 | ||||
|     return result; | ||||
|   }; | ||||
|   core.urls.authorizationCode = function (/*directive, scope, redirectUri, clientId*/) { | ||||
|     //
 | ||||
|     // Example Authorization Code Request
 | ||||
|     // (not for use in the browser)
 | ||||
|     //
 | ||||
|     // GET https://example.com/api/org.oauth3.provider/authorization_dialog
 | ||||
|     //  ?response_type=code
 | ||||
|     //  &scope=`encodeURIComponent('profile.login profile.email')`
 | ||||
|     //  &state=`cryptoutil.random().toString('hex')`
 | ||||
|     //  &client_id=xxxxxxxxxxx
 | ||||
|     //  &redirect_uri=`encodeURIComponent('https://myapp.com/oauth3.html')`
 | ||||
|     //
 | ||||
|     // NOTE: `redirect_uri` itself may also contain URI-encoded components
 | ||||
|     //
 | ||||
|     // NOTE: This probably shouldn't be done in the browser because the server
 | ||||
|     //   needs to initiate the state. If it is done in a browser, the browser
 | ||||
|     //   should probably request 'state' from the server beforehand
 | ||||
|     //
 | ||||
| 
 | ||||
|     throw new Error("not implemented"); | ||||
|   }; | ||||
| 
 | ||||
|   core.urls.authorizationRedirect = function (directive, opts) { | ||||
|     //console.log('[authorizationRedirect]');
 | ||||
|     //
 | ||||
|     // Example Authorization Redirect - from Browser to Consumer API
 | ||||
|     // (for generating a session securely on your own server)
 | ||||
|     //
 | ||||
|     // i.e. GET https://<<CONSUMER>>.com/api/org.oauth3.consumer/authorization_redirect/<<PROVIDER>>.com
 | ||||
|     //
 | ||||
|     // GET https://myapp.com/api/org.oauth3.consumer/authorization_redirect/`encodeURIComponent('example.com')`
 | ||||
|     //  &scope=`encodeURIComponent('profile.login profile.email')`
 | ||||
|     //
 | ||||
|     // (optional)
 | ||||
|     //  &state=`cryptoutil.random().toString('hex')`
 | ||||
|     //  &redirect_uri=`encodeURIComponent('https://myapp.com/oauth3.html')`
 | ||||
|     //
 | ||||
|     // NOTE: This is not a request sent to the provider, but rather a request sent to the
 | ||||
|     // consumer (your own API) which then sets some state and redirects.
 | ||||
|     // This will initiate the `authorization_code` request on your server
 | ||||
|     //
 | ||||
|     opts = opts || {}; | ||||
| 
 | ||||
|     var scope = opts.scope || directive.authn_scope; | ||||
|     var providerUri = directive.provider_uri; | ||||
|     var params = { | ||||
|       state: core.utils.randomState() | ||||
|     , debug: opts.debug || undefined | ||||
|     }; | ||||
|     var slimProviderUri = encodeURIComponent(providerUri.replace(/^(https?|spdy):\/\//, '')); | ||||
|     var authorizationRedirect = opts.authorizationRedirect; | ||||
| 
 | ||||
|     if (scope) { | ||||
|       params.scope = scope; | ||||
|     } | ||||
|     if (opts.redirectUri) { | ||||
|       // this is really only for debugging
 | ||||
|       params.redirect_uri = opts.redirectUri; | ||||
|     } | ||||
|     // Note: the type check is necessary because we allow 'true'
 | ||||
|     // as an automatic mechanism when it isn't necessary to specify
 | ||||
|     if ('string' !== typeof authorizationRedirect) { | ||||
|       // TODO oauth3.json for self?
 | ||||
|       authorizationRedirect = (opts.appApiBase || getDefaultAppApiBase()) | ||||
|         + '/api/org.oauth3.consumer/authorization_redirect/:provider_uri'; | ||||
|     } | ||||
|     authorizationRedirect = authorizationRedirect | ||||
|       .replace(/!(provider_uri)/, slimProviderUri) | ||||
|       .replace(/:provider_uri/, slimProviderUri) | ||||
|       .replace(/#{provider_uri}/, slimProviderUri) | ||||
|       .replace(/{{provider_uri}}/, slimProviderUri) | ||||
|       ; | ||||
| 
 | ||||
|     return { | ||||
|       url: authorizationRedirect + '?' + core.querystringify(params) | ||||
|     , method: 'GET' | ||||
|     , state: params.state    // this becomes browser_state
 | ||||
|     , params: params  // includes scope, final redirect_uri?
 | ||||
|     }; | ||||
|   }; | ||||
| 
 | ||||
|   core.urls.implicitGrant = function (directive, opts) { | ||||
|     //console.log('[implicitGrant]');
 | ||||
|     //
 | ||||
|     // Example Implicit Grant Request
 | ||||
|     // (for generating a browser-only session, not a session on your server)
 | ||||
|     //
 | ||||
|     // GET https://example.com/api/org.oauth3.provider/authorization_dialog
 | ||||
|     //  ?response_type=token
 | ||||
|     //  &scope=`encodeURIComponent('profile.login profile.email')`
 | ||||
|     //  &state=`cryptoutil.random().toString('hex')`
 | ||||
|     //  &client_id=xxxxxxxxxxx
 | ||||
|     //  &redirect_uri=`encodeURIComponent('https://myapp.com/oauth3.html')`
 | ||||
|     //
 | ||||
|     // NOTE: `redirect_uri` itself may also contain URI-encoded components
 | ||||
|     //
 | ||||
| 
 | ||||
|     opts = opts || {}; | ||||
|     var type = 'authorization_dialog'; | ||||
|     var responseType = 'token'; | ||||
| 
 | ||||
|     var redirectUri = opts.redirect_uri; | ||||
|     var scope = opts.scope || directive.authn_scope; | ||||
|     var args = directive[type]; | ||||
|     var uri = args.url; | ||||
|     var state = core.utils.randomState(); | ||||
|     var params = { | ||||
|       debug: opts.debug || undefined | ||||
|     , client_uri: opts.client_uri || opts.clientUri || undefined | ||||
|     , client_id: opts.client_id || opts.client_uri || undefined | ||||
|     }; | ||||
|     var result; | ||||
| 
 | ||||
|     params.state = state; | ||||
|     params.response_type = responseType; | ||||
|     if (scope) { | ||||
|       params.scope = core.stringifyscope(scope); | ||||
|     } | ||||
|     if (!redirectUri) { | ||||
|       // TODO consider making this optional
 | ||||
|       console.error('missing redirect_uri'); | ||||
|     } | ||||
|     params.redirect_uri = redirectUri; | ||||
| 
 | ||||
|     uri += '?' + core.querystringify(params); | ||||
| 
 | ||||
|     result = { | ||||
|       url: uri | ||||
|     , state: state | ||||
|     , method: args.method | ||||
|     , query: params | ||||
|     }; | ||||
| 
 | ||||
|     return result; | ||||
|   }; | ||||
| 
 | ||||
|   core.urls.resolve = function (base, next) { | ||||
|     if (/^https:\/\//i.test(next)) { | ||||
|       return next; | ||||
|     } | ||||
|     return core.normalizeUrl(base) + '/' + core.normalizePath(next); | ||||
|   }; | ||||
| 
 | ||||
|   core.urls.refreshToken = function (directive, opts) { | ||||
|     // grant_type=refresh_token
 | ||||
| 
 | ||||
|     // Example Refresh Token Request
 | ||||
|     // (generally for 1st or 3rd party server-side, mobile, and desktop apps)
 | ||||
|     //
 | ||||
|     // POST https://example.com/api/oauth3/access_token
 | ||||
|     //    { "grant_type": "refresh_token", "client_id": "<<id>>", "scope": "<<scope>>"
 | ||||
|     //    , "username": "<<username>>", "password": "password" }
 | ||||
|     //
 | ||||
|     opts = opts || {}; | ||||
|     var type = 'access_token'; | ||||
|     var grantType = 'refresh_token'; | ||||
| 
 | ||||
|     var scope = opts.scope || directive.authn_scope; | ||||
|     var clientSecret = opts.appSecret || opts.clientSecret; | ||||
|     var args = directive[type]; | ||||
|     var params = { | ||||
|       "grant_type": grantType | ||||
|     , "refresh_token": opts.refresh_token || opts.refreshToken || (opts.session && opts.session.refresh_token) | ||||
|     , "response_type": 'token' | ||||
|     , "client_id": opts.appId || opts.app_id || opts.client_id || opts.clientId || opts.client_id || opts.clientId | ||||
|     , "client_uri": opts.client_uri || opts.clientUri | ||||
|     //, "scope": undefined
 | ||||
|     //, "client_secret": undefined
 | ||||
|     , debug: opts.debug || undefined | ||||
|     }; | ||||
|     var uri = args.url; | ||||
|     var body; | ||||
| 
 | ||||
|     // TODO not allowed in the browser
 | ||||
|     if (clientSecret) { | ||||
|       params.client_secret = clientSecret; | ||||
|     } | ||||
| 
 | ||||
|     if (scope) { | ||||
|       params.scope = core.stringifyscope(scope); | ||||
|     } | ||||
| 
 | ||||
|     if ('GET' === args.method.toUpperCase()) { | ||||
|       uri += '?' + core.querystringify(params); | ||||
|     } else { | ||||
|       body = params; | ||||
|     } | ||||
| 
 | ||||
|     return { | ||||
|       url: uri | ||||
|     , method: args.method | ||||
|     , data: body | ||||
|     }; | ||||
|   }; | ||||
| 
 | ||||
|   core.urls.logout = function (directive, opts) { | ||||
|     opts = opts || {}; | ||||
|     var type = 'logout'; | ||||
|     var clientId = opts.appId || opts.clientId || opts.client_id; | ||||
|     var args = directive[type]; | ||||
|     var params = { | ||||
|       client_id: opts.clientUri || opts.client_uri | ||||
|     , debug: opts.debug || undefined | ||||
|     }; | ||||
|     var uri = args.url; | ||||
|     var body; | ||||
| 
 | ||||
|     if (opts.clientUri) { | ||||
|       params.client_uri = opts.clientUri; | ||||
|     } | ||||
| 
 | ||||
|     if (clientId) { | ||||
|       params.client_id = clientId; | ||||
|     } | ||||
| 
 | ||||
|     args.method = (args.method || 'GET').toUpperCase(); | ||||
|     if ('GET' === args.method) { | ||||
|       uri += '?' + core.querystringify(params); | ||||
|     } else { | ||||
|       body = params; | ||||
|     } | ||||
| 
 | ||||
|     return { | ||||
|       url: uri | ||||
|     , method: args.method || 'GET' | ||||
|     , data: body | ||||
|     }; | ||||
|   }; | ||||
| 
 | ||||
|   exports.OAUTH3 = exports.OAUTH3 || { core: core }; | ||||
|   exports.OAUTH3_CORE = core.OAUTH3_CORE = core; | ||||
| 
 | ||||
|   if ('undefined' !== typeof module) { | ||||
|     module.exports = core; | ||||
|   } | ||||
| }('undefined' !== typeof exports ? exports : window)); | ||||
| @ -1,302 +0,0 @@ | ||||
| ;(function (exports) { | ||||
|   'use strict'; | ||||
| 
 | ||||
|   var core = window.OAUTH3_CORE; | ||||
| 
 | ||||
|   // Provider-Only
 | ||||
|   core.urls.loginCode = function (directive, opts) { | ||||
|     //
 | ||||
|     // Example Resource Owner Password Request
 | ||||
|     // (generally for 1st party and direct-partner mobile apps, and webapps)
 | ||||
|     //
 | ||||
|     // POST https://api.example.com/api/org.oauth3.provider/otp
 | ||||
|     //    { "request_otp": true, "client_id": "<<id>>", "scope": "<<scope>>"
 | ||||
|     //    , "username": "<<username>>" }
 | ||||
|     //
 | ||||
|     opts = opts || {}; | ||||
|     var clientId = opts.appId || opts.clientId; | ||||
| 
 | ||||
|     var args = directive.credential_otp; | ||||
|     if (!directive.credential_otp) { | ||||
|       console.log('[debug] loginCode directive:'); | ||||
|       console.log(directive); | ||||
|     } | ||||
|     var params = { | ||||
|       "username": opts.id || opts.username | ||||
|     , "request_otp": true // opts.requestOtp || undefined
 | ||||
|     //, "jwt": opts.jwt // TODO sign a proof
 | ||||
|     , debug: opts.debug || undefined | ||||
|     }; | ||||
|     var uri = args.url; | ||||
|     var body; | ||||
|     if (opts.clientUri) { | ||||
|       params.client_uri = opts.clientUri; | ||||
|     } | ||||
|     if (opts.clientAgreeTos) { | ||||
|       params.client_agree_tos = opts.clientAgreeTos; | ||||
|     } | ||||
|     if (clientId) { | ||||
|       params.client_id = clientId; | ||||
|     } | ||||
|     if ('GET' === args.method.toUpperCase()) { | ||||
|       uri += '?' + core.querystringify(params); | ||||
|     } else { | ||||
|       body = params; | ||||
|     } | ||||
| 
 | ||||
|     return { | ||||
|       url: uri | ||||
|     , method: args.method | ||||
|     , data: body | ||||
|     }; | ||||
|   }; | ||||
| 
 | ||||
|   core.urls.resourceOwnerPassword = function (directive, opts) { | ||||
|     //
 | ||||
|     // Example Resource Owner Password Request
 | ||||
|     // (generally for 1st party and direct-partner mobile apps, and webapps)
 | ||||
|     //
 | ||||
|     // POST https://example.com/api/org.oauth3.provider/access_token
 | ||||
|     //    { "grant_type": "password", "client_id": "<<id>>", "scope": "<<scope>>"
 | ||||
|     //    , "username": "<<username>>", "password": "password" }
 | ||||
|     //
 | ||||
|     opts = opts || {}; | ||||
|     var type = 'access_token'; | ||||
|     var grantType = 'password'; | ||||
| 
 | ||||
|     if (!opts.password) { | ||||
|       if (opts.otp) { | ||||
|         // for backwards compat
 | ||||
|         opts.password = opts.otp; // 'otp:' + opts.otpUuid + ':' + opts.otp;
 | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     var scope = opts.scope || directive.authn_scope; | ||||
|     var clientId = opts.appId || opts.clientId || opts.client_id; | ||||
|     var clientAgreeTos = opts.clientAgreeTos || opts.client_agree_tos; | ||||
|     var clientUri = opts.clientUri || opts.client_uri || opts.clientUrl || opts.client_url; | ||||
|     var args = directive[type]; | ||||
|     var otpCode = opts.otp || opts.otpCode || opts.otp_code || opts.otpToken || opts.otp_token || undefined; | ||||
|     var params = { | ||||
|       "grant_type": grantType | ||||
|     , "username": opts.username | ||||
|     , "password": opts.password || otpCode || undefined | ||||
|     , "totp": opts.totp || opts.totpToken || opts.totp_token || undefined | ||||
|     , "otp": otpCode | ||||
|     , "password_type": otpCode && 'otp' | ||||
|     , "otp_code": otpCode | ||||
|     , "otp_uuid": opts.otpUuid || opts.otp_uuid || undefined | ||||
|     , "user_agent": opts.userAgent || opts.useragent || opts.user_agent || undefined // AJ's Macbook
 | ||||
|     , "jwk": (opts.rememberDevice || opts.remember_device) && opts.jwk || undefined | ||||
|     //, "public_key": opts.rememberDevice && opts.publicKey || undefined
 | ||||
|     //, "public_key_type":  opts.rememberDevice && opts.publicKeyType || undefined // RSA/ECDSA
 | ||||
|     //, "jwt": opts.jwt // TODO sign a proof with a previously loaded public_key
 | ||||
|     , debug: opts.debug || undefined | ||||
|     }; | ||||
|     var uri = args.url; | ||||
|     var body; | ||||
|     if (opts.totp) { | ||||
|       params.totp = opts.totp; | ||||
|     } | ||||
| 
 | ||||
|     if (clientId) { | ||||
|       params.clientId = clientId; | ||||
|     } | ||||
|     if (clientUri) { | ||||
|       params.clientUri = clientUri; | ||||
|       params.clientAgreeTos = clientAgreeTos; | ||||
|       if (!clientAgreeTos) { | ||||
|         throw new Error('Developer Error: missing clientAgreeTos uri'); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     if (scope) { | ||||
|       params.scope = core.stringifyscope(scope); | ||||
|     } | ||||
| 
 | ||||
|     if ('GET' === args.method.toUpperCase()) { | ||||
|       uri += '?' + core.querystringify(params); | ||||
|     } else { | ||||
|       body = params; | ||||
|     } | ||||
| 
 | ||||
|     return { | ||||
|       url: uri | ||||
|     , method: args.method | ||||
|     , data: body | ||||
|     }; | ||||
|   }; | ||||
| 
 | ||||
| 
 | ||||
|   core.urls.grants = function (directive, opts) { | ||||
|     // directive = { issuer, authorization_decision }
 | ||||
|     // opts = { response_type, scopes{ granted, requested, pending, accepted } }
 | ||||
| 
 | ||||
|     if (!opts) { | ||||
|       throw new Error("You must supply a directive and an options object."); | ||||
|     } | ||||
|     if (!opts.client_id) { | ||||
|       throw new Error("You must supply options.client_id."); | ||||
|     } | ||||
|     if (!opts.session) { | ||||
|       throw new Error("You must supply options.session."); | ||||
|     } | ||||
|     if (!opts.referrer) { | ||||
|       console.warn("You should supply options.referrer"); | ||||
|     } | ||||
|     if (!opts.method) { | ||||
|       console.warn("You must supply options.method as either 'GET', or 'POST'"); | ||||
|     } | ||||
|     if ('POST' === opts.method) { | ||||
|       if ('string' !== typeof opts.scope) { | ||||
|         console.warn("You should supply options.scope as a space-delimited string of scopes"); | ||||
|       } | ||||
|       if (-1 === ['token', 'code'].indexOf(opts.response_type)) { | ||||
|         throw new Error("You must supply options.response_type as 'token' or 'code'"); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     var url = core.urls.resolve(directive.issuer, directive.grants.url) | ||||
|       .replace(/(:azp|:client_id)/g, core.normalizeUri(opts.client_id || opts.client_uri)) | ||||
|       .replace(/(:sub|:account_id)/g, opts.session.token.sub) | ||||
|       ; | ||||
|     var data = { | ||||
|       client_id: opts.client_id | ||||
|     , client_uri: opts.client_uri | ||||
|     , referrer: opts.referrer | ||||
|     , response_type: opts.response_type | ||||
|     , scope: opts.scope | ||||
|     , tenant_id: opts.tenant_id | ||||
|     }; | ||||
|     var body; | ||||
| 
 | ||||
|     if ('GET' === opts.method) { | ||||
|       url += '?' + core.querystringify(data); | ||||
|     } | ||||
|     else { | ||||
|       body = data; | ||||
|     } | ||||
| 
 | ||||
|     return { | ||||
|       method: opts.method | ||||
|     , url: url | ||||
|     , data: body | ||||
|     , session: opts.session | ||||
|     }; | ||||
|   }; | ||||
|   core.urls.authorizationDecision = function (directive, opts) { | ||||
|     var url = core.urls.resolve(directive.issuer, directive.authorization_decision.url); | ||||
|     if (!opts) { | ||||
|       throw new Error("You must supply a directive and an options object"); | ||||
|     } | ||||
|     console.info(url); | ||||
|     throw new Error("NOT IMPLEMENTED authorization_decision"); | ||||
|   }; | ||||
|   core.authz = core.authz || {}; | ||||
|   core.authz.scopes = function (session, clientParams) { | ||||
|     // OAuth3.requests.grants(providerUri, {});         // return list of grants
 | ||||
|     // OAuth3.checkGrants(providerUri, {});             //
 | ||||
|     var clientUri = OAUTH3.core.normalizeUri(clientParams.client_uri || window.document.referrer); | ||||
|     var scope = clientParams.scope || ''; | ||||
|     var clientObj = clientParams; | ||||
| 
 | ||||
|     if (!scope) { | ||||
|       scope = 'oauth3_authn'; | ||||
|     } | ||||
| 
 | ||||
|     //$('.js-user-avatar').attr('src', userAvatar);
 | ||||
| 
 | ||||
|     /* | ||||
|     console.log('grants options'); | ||||
|     console.log(loc.hash); | ||||
|     console.log(loc.search); | ||||
|     console.log(clientObj); | ||||
|     console.log(session.token); | ||||
|     console.log(window.document.referrer); | ||||
|     */ | ||||
| 
 | ||||
|     return OAUTH3.requests.grants(CONFIG.host, { | ||||
|       method: 'GET' | ||||
|     , client_id: clientUri | ||||
|     , client_uri: clientUri | ||||
|     , session: session | ||||
|     }).then(function (grantResults) { | ||||
|       var grants; | ||||
|       var grantedScopes; | ||||
|       var grantedScopesMap; | ||||
|       var pendingScopes; | ||||
|       var acceptedScopes; | ||||
|       var scopes = scope.split(/[+, ]/g); | ||||
|       var callbackUrl; | ||||
| 
 | ||||
|       console.log('previous grants:'); | ||||
|       console.log(grantResults); | ||||
| 
 | ||||
|       if (grantResults.data.error) { | ||||
|         window.alert('grantResults: ' + grantResults.data.error_description || grantResults.data.error.message); | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
|       // it doesn't matter who the referrer is as long as the destination
 | ||||
|       // is an authorized destination for the client in question
 | ||||
|       // (though it may not hurt to pass the referrer's info on to the client)
 | ||||
|       if (!OAUTH3.checkRedirect(grantResults.data.client, clientObj)) { | ||||
|         callbackUrl = 'https://oauth3.org/docs/errors#E_REDIRECT_ATTACK' | ||||
|           + '?redirect_uri=' + clientObj.redirect_uri | ||||
|           + '&allowed_urls=' + grantResults.data.client.url | ||||
|           + '&client_id=' + clientUri | ||||
|           + '&referrer_uri=' + OAUTH3.core.normalizeUri(window.document.referrer) | ||||
|           ; | ||||
|         location.href = callbackUrl; | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
|       if ('oauth3_authn' === scope) { | ||||
|         // implicit ppid grant is automatic
 | ||||
|         console.warn('[security] fix scope checking on backend so that we can do automatic grants'); | ||||
|         // TODO check user preference if implicit ppid grant is allowed
 | ||||
|         //return generateToken(session, clientObj);
 | ||||
|       } | ||||
| 
 | ||||
|       grants = (grantResults.originalData||grantResults.data).grants.filter(function (grant) { | ||||
|         if (clientUri === (grant.azp || grant.oauth_client_id || grant.oauthClientId)) { | ||||
|           return true; | ||||
|         } | ||||
|       }); | ||||
| 
 | ||||
|       grantedScopesMap = {}; | ||||
|       acceptedScopes = []; | ||||
|       pendingScopes = scopes.filter(function (requestedScope) { | ||||
|         return grants.every(function (grant) { | ||||
|           if (!grant.scope) { | ||||
|             grant.scope = 'oauth3_authn'; | ||||
|           } | ||||
|           var gscopes = grant.scope.split(/[+, ]/g); | ||||
|           gscopes.forEach(function (s) { grantedScopesMap[s] = true; }); | ||||
|           if (-1 !== gscopes.indexOf(requestedScope)) { | ||||
|             // already accepted in the past
 | ||||
|             acceptedScopes.push(requestedScope); | ||||
|           } | ||||
|           else { | ||||
|             // true, is pending
 | ||||
|             return true; | ||||
|           } | ||||
|         }); | ||||
|       }); | ||||
|       grantedScopes = Object.keys(grantedScopesMap); | ||||
| 
 | ||||
|       return { | ||||
|         pending: pendingScopes    // not yet accepted
 | ||||
|       , granted: grantedScopes    // all granted, ever
 | ||||
|       , requested: scopes         // all requested, now
 | ||||
|       , accepted: acceptedScopes  // granted (ever) and requested (now)
 | ||||
|       }; | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|   exports.OAUTH3_CORE_PROVIDER = core; | ||||
| 
 | ||||
|   if ('undefined' !== typeof module) { | ||||
|     module.exports = core; | ||||
|   } | ||||
| }('undefined' !== typeof exports ? exports : window)); | ||||
| @ -1,109 +0,0 @@ | ||||
| (function () { | ||||
|   'use strict'; | ||||
| 
 | ||||
|   // I did try to shim jQuery's deferred, but it's just too clunky.
 | ||||
|   // Here I use es6-promise which lacks asynchrity, but it's the smallest Promise implementation.
 | ||||
|   // Only Opera Mini and MSIE (even on 11) will use this shim, so no biggie;
 | ||||
| 
 | ||||
|   var oauth3 = window.OAUTH3; | ||||
|   var count = 0; | ||||
| 
 | ||||
|   function inject() { | ||||
|     count += 1; | ||||
| 
 | ||||
|     if (count >= 100) { | ||||
|       throw new Error("you forgot to include rsvp.js, methinks"); | ||||
|     } | ||||
| 
 | ||||
|     /* | ||||
|      * | ||||
|         [window.Promise, window.ES6Promise, window.RSVP.Promise].forEach(function (PromiseA) { | ||||
|           var x = 1; new PromiseA(function (resolve, reject) { console.log('x', 1 === x); resolve(); }); x = 2; void null; | ||||
|           var y = 1; PromiseA.resolve().then(function () { console.log('y', 2 === x); }); y = 2; void null; | ||||
|         }); | ||||
|      */ | ||||
|     var PromiseA = /*(window.RSVP && window.RSVP.Promise) || window.ES6Promise || */window.Promise; | ||||
|     if ('undefined' !== typeof PromiseA) { | ||||
|       oauth3.providePromise(PromiseA).then(function () { | ||||
|         // ignore
 | ||||
|         window.jqOauth3 = oauth3; | ||||
|       }, function (err) { | ||||
|         console.error(err); | ||||
|         console.error("Bad Promise Implementation!"); | ||||
|       }); | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     // because MSIE can't tell when a script is loaded
 | ||||
|     setTimeout(inject, 100); | ||||
|   } | ||||
| 
 | ||||
|   if ('undefined' === typeof Promise) { | ||||
|     // support Opera Mini and MSIE 11+ (which doesn't support <!-- [if IE]> detection)
 | ||||
|     /* jshint ignore: start */ | ||||
|     document.write('<script src="bower_components/es6-promise/promise.min.js"></script>'); | ||||
|     /* jshint ignore: end */ | ||||
| 
 | ||||
|     /* | ||||
|     // I would have used this, but it turns out that
 | ||||
|     // MSIE can't tell when a script has loaded
 | ||||
|     var js = document.createElement("script"); | ||||
|     js.setAttribute("src", "bower_components/es6-promise/promise.js"); | ||||
|     js.setAttribute("type", "text/javascript"); | ||||
|     document.getElementsByTagName("head")[0].appendChild(js); | ||||
|     */ | ||||
|   } | ||||
| 
 | ||||
|   inject(); | ||||
| 
 | ||||
|   function Request(opts) { | ||||
|     if (!opts.method) { | ||||
|       throw new Error("Developer Error: you must set method as one of 'GET', 'POST', 'DELETE', etc"); | ||||
|     } | ||||
| 
 | ||||
|     var req = { | ||||
|       url: opts.url | ||||
|       // Noted: jQuery 1.9 finally added 'method' as an alias of 'type'
 | ||||
|     , method: opts.method | ||||
|       // leaving type for backwards compat
 | ||||
|     , type: opts.method | ||||
|     , headers: opts.headers || {} | ||||
|     }; | ||||
| 
 | ||||
|     // don't allow accidetal querystring via 'data'
 | ||||
|     if (opts.data && !/get|delete/i.test(opts.method)) { | ||||
|       req.data = opts.data; | ||||
|       if (opts.data && 'object' === typeof opts.data) { | ||||
|         req.data = JSON.stringify(req.data); | ||||
|         req.headers['Content-Type'] = 'application/json; charset=utf-8'; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     // I don't trust jQuery promises...
 | ||||
|     return new oauth3.PromiseA(function (resolve, reject) { | ||||
|       $.ajax(req).then(function (data, textStatus, jqXhr) { | ||||
|         var resp = {}; | ||||
| 
 | ||||
|         Object.keys(jqXhr).forEach(function (key) { | ||||
|           // particularly we have to get rid of .then
 | ||||
|           if ('function' !== typeof jqXhr[key]) { | ||||
|             resp[key] = jqXhr[key]; | ||||
|           } | ||||
|         }); | ||||
| 
 | ||||
|         resp.data = data; | ||||
|         resp.status = textStatus; | ||||
|         resp.request = jqXhr; | ||||
|         resolve(resp); | ||||
|       }, function (jqXhr, textStatus, errorThrown) { | ||||
|         errorThrown.request = jqXhr; | ||||
|         errorThrown.response = jqXhr; | ||||
|         errorThrown.status = textStatus; | ||||
|         reject(errorThrown); | ||||
|       }); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   oauth3.provideRequest(Request); | ||||
| }()); | ||||
| @ -1,445 +0,0 @@ | ||||
| /* global Promise */ | ||||
| (function (exports) { | ||||
|   'use strict'; | ||||
| 
 | ||||
|   var oauth3 = {}; | ||||
| 
 | ||||
|   var core = exports.OAUTH3_CORE || require('./oauth3.core.js'); | ||||
| 
 | ||||
|   oauth3.requests = {}; | ||||
| 
 | ||||
|   if ('undefined' !== typeof Promise) { | ||||
|     oauth3.PromiseA = Promise; | ||||
|   } else { | ||||
|     console.warn("[oauth3.js] Remember to call oauth3.providePromise(Promise) with a proper Promise implementation"); | ||||
|   } | ||||
| 
 | ||||
|   oauth3.providePromise = function (PromiseA) { | ||||
|     oauth3.PromiseA = PromiseA; | ||||
|     if (oauth3._testPromise) { | ||||
|       return oauth3._testPromise(PromiseA).then(function () { | ||||
|         oauth3.PromiseA = PromiseA; | ||||
|       }); | ||||
|     } | ||||
| 
 | ||||
|     oauth3.PromiseA = PromiseA; | ||||
|     return PromiseA.resolve(); | ||||
|   }; | ||||
| 
 | ||||
|   // TODO move recase out
 | ||||
|   /* | ||||
|   oauth3._recaseRequest = function (recase, req) { | ||||
|     // convert JavaScript camelCase to oauth3/ruby snake_case
 | ||||
|     if (req.data && 'object' === typeof req.data) { | ||||
|       req.originalData = req.data; | ||||
|       req.data = recase.snakeCopy(req.data); | ||||
|     } | ||||
| 
 | ||||
|     return req; | ||||
|   }; | ||||
|   oauth3._recaseResponse = function (recase, resp) { | ||||
|     // convert oauth3/ruby snake_case to JavaScript camelCase
 | ||||
|     if (resp.data && 'object' === typeof resp.data) { | ||||
|       resp.originalData = resp.data; | ||||
|       resp.data = recase.camelCopy(resp.data); | ||||
|     } | ||||
|     return resp; | ||||
|   }; | ||||
|   */ | ||||
| 
 | ||||
|   oauth3.hooks = { | ||||
|     checkSession: function (preq, opts) { | ||||
|       if (!preq.session) { | ||||
|         console.warn('[oauth3.hooks.checkSession] no session'); | ||||
|         return oauth3.PromiseA.resolve(null); | ||||
|       } | ||||
|       var freshness = oauth3.core.jwt.getFreshness(preq.session.token, opts.staletime); | ||||
|       console.info('[oauth3.hooks.checkSession] freshness', freshness, preq.session); | ||||
| 
 | ||||
|       switch (freshness) { | ||||
|         case 'stale': | ||||
|           return oauth3.hooks.sessionStale(preq.session); | ||||
|         case 'expired': | ||||
|           return oauth3.hooks.sessionExpired(preq.session).then(function (newSession) { | ||||
|             preq.session = newSession; | ||||
|             return newSession; | ||||
|           }); | ||||
|         //case 'fresh':
 | ||||
|         default: | ||||
|           return oauth3.PromiseA.resolve(preq.session); | ||||
|       } | ||||
|     } | ||||
|   , sessionStale: function (staleSession) { | ||||
|       console.info('[oauth3.hooks.sessionStale] called'); | ||||
|       if (oauth3.hooks._stalePromise) { | ||||
|         return oauth3.PromiseA.resolve(staleSession); | ||||
|       } | ||||
| 
 | ||||
|       oauth3.hooks._stalePromise = oauth3.requests.refreshToken( | ||||
|         staleSession.provider_uri | ||||
|       , { client_uri: staleSession.client_uri | ||||
|         , session: staleSession | ||||
|         , debug: staleSession.debug | ||||
|         } | ||||
|       ).then(function (newSession) { | ||||
|         oauth3.hooks._stalePromise = null; | ||||
|         return newSession; // oauth3.hooks.refreshSession(staleSession, newSession);
 | ||||
|       }, function () { | ||||
|         oauth3.hooks._stalePromise = null; | ||||
|       }); | ||||
| 
 | ||||
|       return oauth3.PromiseA.resolve(staleSession); | ||||
|     } | ||||
|   , sessionExpired: function (expiredSession) { | ||||
|       console.info('[oauth3.hooks.sessionExpired] called'); | ||||
|       return oauth3.requests.refreshToken( | ||||
|         expiredSession.provider_uri | ||||
|       , { client_uri: expiredSession.client_uri | ||||
|         , session: expiredSession | ||||
|         , debug: expiredSession.debug | ||||
|         } | ||||
|       ).then(function (newSession) { | ||||
|         return newSession; // oauth3.hooks.refreshSession(expiredSession, newSession);
 | ||||
|       }); | ||||
|     } | ||||
|   , refreshSession: function (oldSession, newSession) { | ||||
|       var providerUri = oldSession.provider_uri; | ||||
|       var clientUri = oldSession.client_uri; | ||||
| 
 | ||||
|       console.info('[oauth3.hooks.refreshSession] oldSession', JSON.parse(JSON.stringify(oldSession))); | ||||
|       console.info('[oauth3.hooks.refreshSession] newSession', newSession); | ||||
|       // shim for account create which does not return new refresh_token
 | ||||
|       newSession.refresh_token = newSession.refresh_token || oldSession.refresh_token; | ||||
|       Object.keys(oldSession).forEach(function (key) { | ||||
|         oldSession[key] = undefined; | ||||
|       }); | ||||
|       Object.keys(newSession).forEach(function (key) { | ||||
|         oldSession[key] = newSession[key]; | ||||
|       }); | ||||
| 
 | ||||
|       // info about the session of this API call
 | ||||
|       oldSession.provider_uri = providerUri;  // aud
 | ||||
|       oldSession.client_uri = clientUri;      // azp
 | ||||
| 
 | ||||
|       // info about the newly-discovered token
 | ||||
|       oldSession.token = oldSession.meta = core.jwt.decode(oldSession.access_token).payload; | ||||
| 
 | ||||
|       oldSession.token.sub = oldSession.token.sub | ||||
|         || (oldSession.token.acx && oldSession.token.acx.id) | ||||
|         || (oldSession.token.axs && oldSession.token.axs.length && oldSession.token.axs[0].appScopedId) | ||||
|         ; | ||||
|       oldSession.token.client_uri = clientUri; | ||||
|       oldSession.token.provider_uri = providerUri; | ||||
| 
 | ||||
|       if (!oldSession.token.sub) { | ||||
|         // TODO this is broken hard
 | ||||
|         console.warn('TODO implementation for OAUTH3.hooks.accounts.create (GUI, CLI, or API)'); | ||||
|       } | ||||
| 
 | ||||
|       if (oldSession.refresh_token) { | ||||
|         oldSession.refresh = core.jwt.decode(oldSession.refresh_token).payload; | ||||
|         oldSession.refresh.sub = oldSession.refresh.sub | ||||
|           || (oldSession.refresh.acx && oldSession.refresh.acx.id) | ||||
|           || (oldSession.refresh.axs && oldSession.refresh.axs.length && oldSession.refresh.axs[0].appScopedId) | ||||
|           ; | ||||
|         oldSession.refresh.provider_uri = providerUri; | ||||
|       } | ||||
| 
 | ||||
|       console.info('[oauth3.hooks.refreshSession] refreshedSession', oldSession); | ||||
| 
 | ||||
|       // set for a set of audiences
 | ||||
|       return oauth3.PromiseA.resolve(oauth3.hooks.setSession(providerUri, oldSession)); | ||||
|     } | ||||
|   , setSession: function (providerUri, newSession) { | ||||
|       if (!providerUri) { | ||||
|         console.error(new Error('no providerUri').stack); | ||||
|       } | ||||
|       providerUri = oauth3.core.normalizeUri(providerUri); | ||||
|       console.warn('[ERROR] Please implement OAUTH3.hooks.setSession = function (providerUri, newSession) { return newSession; }'); | ||||
|       console.warn(newSession); | ||||
|       if (!oauth3.hooks._sessions) { oauth3.hooks._sessions = {}; } | ||||
|       oauth3.hooks._sessions[providerUri] = newSession; | ||||
|       return newSession; | ||||
|     } | ||||
|   , getSession: function (providerUri) { | ||||
|       providerUri = oauth3.core.normalizeUri(providerUri); | ||||
|       console.warn('[ERROR] Please implement OAUTH3.hooks.getSession = function (providerUri) { return savedSession; }'); | ||||
|       if (!oauth3.hooks._sessions) { oauth3.hooks._sessions = {}; } | ||||
|       return oauth3.hooks._sessions[providerUri]; | ||||
|     } | ||||
|   , setDirectives: function (providerUri, directives) { | ||||
|       providerUri = oauth3.core.normalizeUri(providerUri); | ||||
|       console.warn('[oauth3.hooks.setDirectives] PLEASE IMPLEMENT -- Your Fault'); | ||||
|       console.warn(directives); | ||||
|       if (!oauth3.hooks._directives) { oauth3.hooks._directives = {}; } | ||||
|       window.localStorage.setItem('directives-' + providerUri, JSON.stringify(directives)); | ||||
|       oauth3.hooks._directives[providerUri] = directives; | ||||
|       return directives; | ||||
|     } | ||||
|   , getDirectives: function (providerUri) { | ||||
|       providerUri = oauth3.core.normalizeUri(providerUri); | ||||
|       console.warn('[oauth3.hooks.getDirectives] PLEASE IMPLEMENT -- Your Fault'); | ||||
|       if (!oauth3.hooks._directives) { oauth3.hooks._directives = {}; } | ||||
|       return JSON.parse(window.localStorage.getItem('directives-' + providerUri) || '{}'); | ||||
|       //return oauth3.hooks._directives[providerUri];
 | ||||
|     } | ||||
| 
 | ||||
|     // Provider Only
 | ||||
|   , setGrants: function (clientUri, newGrants) { | ||||
|       clientUri = oauth3.core.normalizeUri(clientUri); | ||||
|       console.warn('[oauth3.hooks.setGrants] PLEASE IMPLEMENT -- Your Fault'); | ||||
|       console.warn(newGrants); | ||||
|       if (!oauth3.hooks._grants) { oauth3.hooks._grants = {}; } | ||||
|       console.log('clientUri, newGrants'); | ||||
|       console.log(clientUri, newGrants); | ||||
|       oauth3.hooks._grants[clientUri] = newGrants; | ||||
|       return newGrants; | ||||
|     } | ||||
|   , getGrants: function (clientUri) { | ||||
|       clientUri = oauth3.core.normalizeUri(clientUri); | ||||
|       console.warn('[oauth3.hooks.getGrants] PLEASE IMPLEMENT -- Your Fault'); | ||||
|       if (!oauth3.hooks._grants) { oauth3.hooks._grants = {}; } | ||||
|       console.log('clientUri, existingGrants'); | ||||
|       console.log(clientUri, oauth3.hooks._grants[clientUri]); | ||||
|       return oauth3.hooks._grants[clientUri]; | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   // TODO simplify (nix recase)
 | ||||
|   oauth3.provideRequest = function (rawRequest, opts) { | ||||
|     opts = opts || {}; | ||||
|     //var Recase = exports.Recase || require('recase');
 | ||||
|     // TODO make insensitive to providing exceptions
 | ||||
|     //var recase = Recase.create({ exceptions: {} });
 | ||||
| 
 | ||||
|     function lintAndRequest(preq) { | ||||
|       function goGetHer() { | ||||
|         if (preq.session) { | ||||
|           // TODO check session.token.aud against preq.url to make sure they match
 | ||||
|           console.warn("[security] session audience checking has not been implemented yet (it's up to you to check)"); | ||||
|           preq.headers = preq.headers || {}; | ||||
|           preq.headers.Authorization = 'Bearer ' + preq.session.access_token; | ||||
|         } | ||||
| 
 | ||||
|         if (!oauth3._lintRequest) { | ||||
|           return rawRequest(preq); | ||||
|         } | ||||
|         return oauth3._lintRequest(preq, opts).then(function (preq) { | ||||
|           return rawRequest(preq); | ||||
|         }); | ||||
|       } | ||||
| 
 | ||||
|       if (!preq.session) { | ||||
|         return goGetHer(); | ||||
|       } | ||||
| 
 | ||||
|       console.warn('lintAndRequest checkSession', preq); | ||||
|       return oauth3.hooks.checkSession(preq, opts).then(goGetHer); | ||||
|     } | ||||
| 
 | ||||
|     if (opts.rawCase) { | ||||
|       oauth3.request = lintAndRequest; | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     // Wrap oauth3 api calls in snake_case / camelCase conversion
 | ||||
|     oauth3.request = function (req, opts) { | ||||
|       //console.log('[D] [oauth3 req.url]', req.url);
 | ||||
|       opts = opts || {}; | ||||
| 
 | ||||
|       if (opts.rawCase) { | ||||
|         return lintAndRequest(req, opts); | ||||
|       } | ||||
| 
 | ||||
|       //req = oauth3._recaseRequest(recase, req);
 | ||||
|       return lintAndRequest(req, opts).then(function (res) { | ||||
|         //return oauth3._recaseResponse(recase, res);
 | ||||
|         return res; | ||||
|       }); | ||||
|     }; | ||||
| 
 | ||||
|     /* | ||||
|     return oauth3._testRequest(request).then(function () { | ||||
|       oauth3.request = request; | ||||
|     }); | ||||
|     */ | ||||
|   }; | ||||
| 
 | ||||
|   // TODO merge with regular token access point and new response_type=federated ?
 | ||||
|   oauth3.requests.clientToken = function (providerUri, opts) { | ||||
|     return oauth3.discover(providerUri, opts).then(function (directive) { | ||||
|       return oauth3.request(core.urls.grants(directive, opts)).then(function (grantsResult) { | ||||
|         return grantsResult.originalData || grantsResult.data; | ||||
|       }); | ||||
|     }); | ||||
|   }; | ||||
|   oauth3.requests.grants = function (providerUri, opts) { | ||||
|     return oauth3.discover(providerUri, { | ||||
|       client_id: providerUri | ||||
|     , debug: opts.debug | ||||
|     }).then(function (directive) { | ||||
|       return oauth3.request(core.urls.grants(directive, opts)).then(function (grantsResult) { | ||||
|         if ('POST' === opts.method) { | ||||
|           // TODO this is clientToken
 | ||||
|           return grantsResult.originalData || grantsResult.data; | ||||
|         } | ||||
| 
 | ||||
|         var grants = grantsResult.originalData || grantsResult.data; | ||||
|         // TODO
 | ||||
|         if (grants.error) { | ||||
|           return oauth3.PromiseA.reject(oauth3.core.formatError(grants.error)); | ||||
|         } | ||||
| 
 | ||||
|         console.warn('requests.grants', grants); | ||||
| 
 | ||||
|         oauth3.hooks.setGrants(opts.client_id + '-client', grants.client); | ||||
|         grants.grants.forEach(function (grant) { | ||||
|           var clientId = grant.client_id || grant.oauth_client_id || grant.oauthClientId; | ||||
|           // TODO should save as an array
 | ||||
|           oauth3.hooks.setGrants(clientId, [ grant ]); | ||||
|         }); | ||||
| 
 | ||||
|         return { | ||||
|           client: oauth3.hooks.getGrants(opts.client_id + '-client') | ||||
|         , grants: oauth3.hooks.getGrants(opts.client_id) || [] | ||||
|         }; | ||||
|       }); | ||||
|     }); | ||||
|   }; | ||||
|   oauth3.requests.loginCode = function (providerUri, opts) { | ||||
|     return oauth3.discover(providerUri, opts).then(function (directive) { | ||||
|       var prequest = core.urls.loginCode(directive, opts); | ||||
| 
 | ||||
|       return oauth3.request(prequest).then(function (res) { | ||||
|         // result = { uuid, expires_at }
 | ||||
|         return { | ||||
|           otpUuid: res.data.uuid | ||||
|         , otpExpires: res.data.expires_at | ||||
|         }; | ||||
|       }); | ||||
|     }); | ||||
|   }; | ||||
|   oauth3.loginCode = oauth3.requests.loginCode; | ||||
| 
 | ||||
|   oauth3.requests.resourceOwnerPassword = function (providerUri, opts) { | ||||
|     //var scope = opts.scope;
 | ||||
|     //var appId = opts.appId;
 | ||||
|     return oauth3.discover(providerUri, opts).then(function (directive) { | ||||
|       var prequest = core.urls.resourceOwnerPassword(directive, opts); | ||||
| 
 | ||||
|       return oauth3.request(prequest).then(function (req) { | ||||
|         var data = (req.originalData || req.data); | ||||
|         data.provider_uri = providerUri; | ||||
|         if (data.error) { | ||||
|           return oauth3.PromiseA.reject(oauth3.core.formatError(providerUri, data.error)); | ||||
|         } | ||||
|         return oauth3.hooks.refreshSession( | ||||
|           opts.session || { provider_uri: providerUri, client_uri: opts.client_uri || opts.clientUri } | ||||
|         , data | ||||
|         ); | ||||
|       }); | ||||
|     }); | ||||
|   }; | ||||
|   oauth3.resourceOwnerPassword = oauth3.requests.resourceOwnerPassword; | ||||
| 
 | ||||
|   oauth3.requests.refreshToken = function (providerUri, opts) { | ||||
|     console.info('[oauth3.requests.refreshToken] called', providerUri, opts); | ||||
|     return oauth3.discover(providerUri, opts).then(function (directive) { | ||||
|       var prequest = core.urls.refreshToken(directive, opts); | ||||
| 
 | ||||
|       return oauth3.request(prequest).then(function (req) { | ||||
|         var data = (req.originalData || req.data); | ||||
|         data.provider_uri = providerUri; | ||||
|         if (data.error) { | ||||
|           return oauth3.PromiseA.reject(oauth3.core.formatError(providerUri, data)); | ||||
|         } | ||||
|         return oauth3.hooks.refreshSession(opts, data); | ||||
|       }); | ||||
|     }); | ||||
|   }; | ||||
|   oauth3.refreshToken = oauth3.requests.refreshToken; | ||||
| 
 | ||||
|   // TODO It'll be very interesting to see if we can do some browser popup stuff from the CLI
 | ||||
|   oauth3.requests._error_description = 'Not Implemented: Please override by including <script src="oauth3.browser.js"></script>'; | ||||
|   oauth3.requests.authorizationRedirect = function (/*providerUri, opts*/) { | ||||
|     throw new Error(oauth3.requests._error_description); | ||||
|   }; | ||||
|   oauth3.requests.implicitGrant = function (/*providerUri, opts*/) { | ||||
|     throw new Error(oauth3.requests._error_description); | ||||
|   }; | ||||
|   oauth3.requests.logout = function (/*providerUri, opts*/) { | ||||
|     throw new Error(oauth3.requests._error_description); | ||||
|   }; | ||||
| 
 | ||||
|   oauth3.login = function (providerUri, opts) { | ||||
|     // Four styles of login:
 | ||||
|     //   * background (hidden iframe)
 | ||||
|     //   * iframe (visible iframe, needs border color and width x height params)
 | ||||
|     //   * popup (needs width x height and positioning? params)
 | ||||
|     //   * window (params?)
 | ||||
| 
 | ||||
|     // Two strategies
 | ||||
|     //  * authorization_redirect (to server authorization code)
 | ||||
|     //  * implicit_grant (default, browser-only)
 | ||||
|     // If both are selected, implicit happens first and then the other happens in background
 | ||||
| 
 | ||||
|     var promise; | ||||
| 
 | ||||
|     if (opts.username || opts.password) { | ||||
|       /* jshint ignore:start */ | ||||
|       // ingore "confusing use of !"
 | ||||
|       if (!opts.username !== !(opts.password || opts.otp)) { | ||||
|         throw new Error("you did not specify both username and password"); | ||||
|       } | ||||
|       /* jshint ignore:end */ | ||||
| 
 | ||||
|       return oauth3.requests.resourceOwnerPassword(providerUri, opts).then(function (resp) { | ||||
|         if (!resp || !resp.data) { | ||||
|           var err = new Error("bad response"); | ||||
|           err.response = resp; | ||||
|           err.data = resp && resp.data || undefined; | ||||
|           return oauth3.PromiseA.reject(err); | ||||
|         } | ||||
|         return resp.data; | ||||
|       }); | ||||
|     } | ||||
| 
 | ||||
|     // TODO support dual-strategy login
 | ||||
|     // by default, always get implicitGrant (for client)
 | ||||
|     // and optionally do authorizationCode (for server session)
 | ||||
|     if ('background' === opts.type || opts.background) { | ||||
|       opts.type = 'background'; | ||||
|       opts.background = true; | ||||
|     } | ||||
|     else { | ||||
|       opts.type = 'popup'; | ||||
|       opts.popup = true; | ||||
|     } | ||||
|     if (opts.authorizationRedirect) { | ||||
|       promise = oauth3.requests.authorizationRedirect(providerUri, opts); | ||||
|     } | ||||
|     else { | ||||
|       promise = oauth3.requests.implicitGrant(providerUri, opts); | ||||
|     } | ||||
| 
 | ||||
|     return promise; | ||||
|   }; | ||||
| 
 | ||||
|   oauth3.backgroundLogin = function (providerUri, opts) { | ||||
|     opts = opts || {}; | ||||
|     opts.type = 'background'; | ||||
|     return oauth3.login(providerUri, opts); | ||||
|   }; | ||||
| 
 | ||||
|   oauth3.core = core; | ||||
|   oauth3.querystringify = core.querystringify; | ||||
|   oauth3.scopestringify = core.stringifyscope; | ||||
|   oauth3.stringifyscope = core.stringifyscope; | ||||
| 
 | ||||
|   exports.OAUTH3 = oauth3.oauth3 = oauth3.OAUTH3 = oauth3; | ||||
|   exports.oauth3 = exports.OAUTH3; | ||||
| 
 | ||||
|   if ('undefined' !== typeof module) { | ||||
|     module.exports = oauth3; | ||||
|   } | ||||
| }('undefined' !== typeof exports ? exports : window)); | ||||
| @ -1,158 +0,0 @@ | ||||
| 
 | ||||
|   // TODO move to a test / lint suite?
 | ||||
|   oauth3._lintPromise = function (PromiseA) { | ||||
|     var promise; | ||||
|     var x = 1; | ||||
| 
 | ||||
|     // tests that this promise has all of the necessary api
 | ||||
|     promise = new PromiseA(function (resolve, reject) { | ||||
|       //console.log('x [2]', x);
 | ||||
|       if (x !== 1) { | ||||
|         throw new Error("bad promise, create not Synchronous [0]"); | ||||
|       } | ||||
| 
 | ||||
|       PromiseA.resolve().then(function () { | ||||
|         var promise2; | ||||
| 
 | ||||
|         //console.log('x resolve', x);
 | ||||
|         if (x !== 2) { | ||||
|           throw new Error("bad promise, resolve not Asynchronous [1]"); | ||||
|         } | ||||
| 
 | ||||
|         promise2 = PromiseA.reject().then(reject, function () { | ||||
|           //console.log('x reject', x);
 | ||||
|           if (x !== 4) { | ||||
|             throw new Error("bad promise, reject not Asynchronous [2]"); | ||||
|           } | ||||
| 
 | ||||
|           if ('undefined' === typeof angular) { | ||||
|             throw new Error("[NOT AN ERROR] Dear angular users: ignore this error-handling test"); | ||||
|           } else { | ||||
|             return PromiseA.reject(new Error("[NOT AN ERROR] ignore this error-handling test")); | ||||
|           } | ||||
|         }); | ||||
| 
 | ||||
|         x = 4; | ||||
| 
 | ||||
|         return promise2; | ||||
|       }).catch(function (e) { | ||||
|         if (e.message.match('NOT AN ERROR')) { | ||||
|           resolve({ success: true }); | ||||
|         } else { | ||||
|           reject(e); | ||||
|         } | ||||
|       }); | ||||
| 
 | ||||
|       x = 3; | ||||
|     }); | ||||
| 
 | ||||
|     x = 2; | ||||
|     return promise; | ||||
|   }; | ||||
| 
 | ||||
|   oauth3._lintDirectives = function (providerUri, directives) { | ||||
|     var params = { directives: directives }; | ||||
|     console.log('DEBUG oauth3._discoverHelper', directives); | ||||
|     var err; | ||||
|     if (!params.directives) { | ||||
|       err = new Error(params.error_description || "Unknown error when discoving provider '" + providerUri + "'"); | ||||
|       err.code = params.error || "E_UNKNOWN_ERROR"; | ||||
|       return OAUTH3.PromiseA.reject(err); | ||||
|     } | ||||
| 
 | ||||
|     try { | ||||
|       directives = JSON.parse(atob(params.directives)); | ||||
|       console.log('DEBUG oauth3._discoverHelper directives', directives); | ||||
|     } catch(e) { | ||||
|       err = new Error(params.error_description || "could not parse directives for provider '" + providerUri + "'"); | ||||
|       err.code = params.error || "E_PARSE_DIRECTIVE"; | ||||
|       return OAUTH3.PromiseA.reject(err); | ||||
|     } | ||||
|     if ( | ||||
|         (directives.authorization_dialog && directives.authorization_dialog.url) | ||||
|       || (directives.access_token && directives.access_token.url) | ||||
|     ) { | ||||
|       // TODO lint directives
 | ||||
|       // TODO self-reference in directive for providerUri?
 | ||||
|       directives.provider_uri = providerUri; | ||||
|       localStorage.setItem('oauth3.' + providerUri + '.directives', JSON.stringify(directives)); | ||||
|       localStorage.setItem('oauth3.' + providerUri + '.directives.updated_at', new Date().toISOString()); | ||||
| 
 | ||||
|       return OAUTH3.PromiseA.resolve(directives); | ||||
|     } else { | ||||
|       // ignore
 | ||||
|       console.error("the directives provided by '" + providerUri + "' were invalid."); | ||||
|       params.error = params.error || "E_INVALID_DIRECTIVE"; | ||||
|       params.error_description = params.error_description | ||||
|         || "directives did not include authorization_dialog.url"; | ||||
|       err = new Error(params.error_description || "Unknown error when discoving provider '" + providerUri + "'"); | ||||
|       err.code = params.error; | ||||
|       return OAUTH3.PromiseA.reject(err); | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   core.tokenState = function (session) { | ||||
|     var fresh; | ||||
|     fresh = (Date.now() / 1000) >= (parseInt(session._accessTokenData.exp) || 0); | ||||
|     if (!fresh) { | ||||
|       console.log("[os] isn't fresh", session._accessTokenData.exp); | ||||
|     } | ||||
|   }; | ||||
|   oauth3._lintRequest = function (preq, opts) { | ||||
|     var providerUri; | ||||
| 
 | ||||
|     console.log('[os] request meta opts', opts); | ||||
| 
 | ||||
|     // check that the JWT is not expired
 | ||||
|     // TODO check that this request applies to the aud and azp
 | ||||
|     if (!(preq.session && preq.session.accessToken)) { | ||||
|       console.log('[os] no session/accessTokenData'); | ||||
|       return oauth3.PromiseA.resolve(preq); | ||||
|     } | ||||
| 
 | ||||
|     preq.headers = preq.headers || {}; | ||||
|     preq.headers.Authorization = 'Bearer ' + preq.session.accessToken; | ||||
| 
 | ||||
|     if (!preq.session._accessTokenData) { | ||||
|       console.log('[os] no _accessTokenData'); | ||||
|       preq.session._accessTokenData = core.jwt.decode(preq.session.accessToken).payload; | ||||
|     } | ||||
| 
 | ||||
|     if (!preq.url.match(preq.session._accessTokenData.aud)) { | ||||
|       console.log("[os] doesn't match audience", preq.session._accessTokenData.aud); | ||||
|       return oauth3.PromiseA.resolve(preq); | ||||
|     } | ||||
| 
 | ||||
|     switch (core.tokenState(session)) { | ||||
|       case 'fresh': | ||||
|         return oauth3.PromiseA.resolve(preq); | ||||
|       case 'stale': | ||||
|       case 'useless': | ||||
|         break; | ||||
|     } | ||||
| 
 | ||||
|     if (!preq.session.refreshToken) { | ||||
|       console.log("[os] can't refresh", preq.session); | ||||
|       return oauth3.PromiseA.resolve(preq); | ||||
|     } | ||||
| 
 | ||||
|     opts.refreshToken = preq.session.refreshToken; | ||||
|     console.log('[oauth3.js] refreshToken attempt'); | ||||
| 
 | ||||
|     // TODO include directive?
 | ||||
|     providerUri = preq.session.providerUri || preq.session._accessTokenData.iss; | ||||
|     //opts.
 | ||||
|     return oauth3.refreshToken(providerUri, opts).then(function (res) { | ||||
|       console.log('[oauth3.js] refreshToken result:', res); | ||||
| 
 | ||||
|       if (!res.data.accessToken) { | ||||
|         return preq; | ||||
|       } | ||||
| 
 | ||||
|       // TODO fire session update event
 | ||||
|       res.data.providerUri = preq.session.providerUri; | ||||
|       preq.session = res.data; | ||||
|       preq.headers.Authorization = 'Bearer ' + preq.session.accessToken; | ||||
|       return preq; | ||||
|     }); | ||||
|   }; | ||||
| @ -1,97 +0,0 @@ | ||||
| ;(function (exports) { | ||||
|   'use strict'; | ||||
| 
 | ||||
|   var OAUTH3 = window.OAUTH3 || require('./oauth3.js'); | ||||
| 
 | ||||
|   OAUTH3.authz = OAUTH3.authz || {}; | ||||
|   OAUTH3.authz.scopes = function (providerUri, session, clientParams) { | ||||
|     // OAuth3.requests.grants(providerUri, {});         // return list of grants
 | ||||
|     // OAuth3.checkGrants(providerUri, {});             //
 | ||||
|     var clientUri = OAUTH3.core.normalizeUri(clientParams.client_id || clientParams.client_uri); | ||||
|     var scope = clientParams.scope || ''; | ||||
|     var clientObj = clientParams; | ||||
| 
 | ||||
|     if (!scope) { | ||||
|       scope = 'oauth3_authn'; | ||||
|     } | ||||
| 
 | ||||
|     return OAUTH3.requests.grants(providerUri, { | ||||
|       method: 'GET' | ||||
|     , client_id: clientUri | ||||
|     , client_uri: clientUri | ||||
|     , session: session | ||||
|     }).then(function (grants) { | ||||
|       var myGrants; | ||||
|       var grantedScopes; | ||||
|       var grantedScopesMap; | ||||
|       var pendingScopes; | ||||
|       var acceptedScopes; | ||||
|       var acceptedScopesMap; | ||||
|       var scopes = OAUTH3.core.parsescope(scope); | ||||
|       var callbackUrl; | ||||
| 
 | ||||
|       console.log('previous grants:'); | ||||
|       console.log(grants); | ||||
| 
 | ||||
|       // it doesn't matter who the referrer is as long as the destination
 | ||||
|       // is an authorized destination for the client in question
 | ||||
|       // (though it may not hurt to pass the referrer's info on to the client)
 | ||||
|       if (!OAUTH3.checkRedirect(grants.client, clientObj)) { | ||||
|         callbackUrl = 'https://oauth3.org/docs/errors#E_REDIRECT_ATTACK' | ||||
|           + '?redirect_uri=' + clientObj.redirect_uri | ||||
|           + '&allowed_urls=' + grants.client.url | ||||
|           + '&client_id=' + clientUri | ||||
|           + '&referrer_uri=' + OAUTH3.core.normalizeUri(window.document.referrer) | ||||
|           ; | ||||
|         location.href = callbackUrl; | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
|       console.warn("What are grants? Baby don't hurt me. Don't hurt me. No more."); | ||||
|       console.warn(grants); | ||||
| 
 | ||||
|       myGrants = grants.grants.filter(function (grant) { | ||||
|         if (clientUri === (grant.azp || grant.oauth_client_id || grant.oauthClientId)) { | ||||
|           return true; | ||||
|         } | ||||
|       }); | ||||
| 
 | ||||
|       grantedScopesMap = {}; | ||||
|       acceptedScopesMap = {}; | ||||
|       pendingScopes = scopes.filter(function (requestedScope) { | ||||
|         return myGrants.every(function (grant) { | ||||
|           if (!grant.scope) { | ||||
|             grant.scope = 'oauth3_authn'; | ||||
|           } | ||||
|           var gscopes = grant.scope.split(/[+, ]/g); | ||||
|           gscopes.forEach(function (s) { grantedScopesMap[s] = true; }); | ||||
|           if (-1 !== gscopes.indexOf(requestedScope)) { | ||||
|             // already accepted in the past
 | ||||
|             acceptedScopesMap[requestedScope] = true; | ||||
|           } | ||||
|           else { | ||||
|             // true, is pending
 | ||||
|             return true; | ||||
|           } | ||||
|         }); | ||||
|       }); | ||||
|       grantedScopes = Object.keys(grantedScopesMap); | ||||
|       acceptedScopes = Object.keys(acceptedScopesMap); | ||||
| 
 | ||||
|       return { | ||||
|         pending: pendingScopes    // not yet accepted
 | ||||
|       , granted: grantedScopes    // all granted, ever
 | ||||
|       , requested: scopes         // all requested, now
 | ||||
|       , accepted: acceptedScopes  // granted (ever) and requested (now)
 | ||||
|       , client: grants.client | ||||
|       , grants: grants.grants | ||||
|       }; | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|   exports.OAUTH3_PROVIDER = OAUTH3; | ||||
| 
 | ||||
|   if ('undefined' !== typeof module) { | ||||
|     module.exports = OAUTH3; | ||||
|   } | ||||
| }('undefined' !== typeof exports ? exports : window)); | ||||
| @ -1,24 +0,0 @@ | ||||
|     var separator; | ||||
| 
 | ||||
|     // TODO check that we appropriately use '#' for implicit and '?' for code
 | ||||
|     // (server-side) in an OAuth2 backwards-compatible way
 | ||||
|     if ('token' === scope.appQuery.response_type) { | ||||
|       separator = '#'; | ||||
|     } | ||||
|     else if ('code' === scope.appQuery.response_type) { | ||||
|       separator = '?'; | ||||
|     } | ||||
|     else { | ||||
|       separator = '#'; | ||||
|     } | ||||
| 
 | ||||
|     if (scope.pendingScope.length && !opts.allow) { | ||||
|       redirectUri += separator + Oauth3.querystringify({ | ||||
|         error: 'access_denied' | ||||
|         , error_description: 'None of the permissions were accepted' | ||||
|         , error_uri: 'https://oauth3.org/docs/errors#access_denied' | ||||
|         , state: scope.appQuery.state | ||||
|       }); | ||||
|       $window.location.href = redirectUri; | ||||
|       return; | ||||
|     } | ||||
							
								
								
									
										1
									
								
								well-known
									
									
									
									
									
										Symbolic link
									
								
							
							
						
						
									
										1
									
								
								well-known
									
									
									
									
									
										Symbolic link
									
								
							| @ -0,0 +1 @@ | ||||
| _apis | ||||
| @ -1,12 +0,0 @@ | ||||
| { "terms": [ "oauth3.org/tos/draft" ] | ||||
| , "authorization_dialog": { "url": "#/authorization_dialog" } | ||||
| , "access_token": { "method": "POST", "url": "api/org.oauth3.provider/access_token" } | ||||
| , "otp": { "method": "POST" , "url": "api/org.oauth3.provider/otp" } | ||||
| , "credential_otp": { "method": "POST" , "url": "api/org.oauth3.provider/otp" } | ||||
| , "credential_meta": { "url": "api/org.oauth3.provider/logins/meta/:type/:id" } | ||||
| , "credential_create": { "method": "POST" , "url": "api/org.oauth3.provider/logins" } | ||||
| , "grants": { "method": "GET", "url": "api/org.oauth3.provider/grants/:azp/:sub" } | ||||
| , "authorization_decision": { "method": "POST", "url": "api/org.oauth3.provider/authorization_decision" } | ||||
| , "callback": { "method": "GET", "url": ".well-known/oauth3/callback.html#/" } | ||||
| , "logout": { "method": "GET", "url": "#/logout/" } | ||||
| } | ||||
| @ -1,71 +0,0 @@ | ||||
| <!DOCTYPE html> | ||||
| <html> | ||||
|   <head> | ||||
|     <style> | ||||
|       body { | ||||
|         background-color: #ffcccc; | ||||
|       } | ||||
|     </style> | ||||
|   </head> | ||||
|   <body> | ||||
|   OAuth3 RPC | ||||
| 
 | ||||
|   <script src="/assets/org.oauth3/oauth3.core.js"></script> | ||||
|   <script> | ||||
|     ;(function () { | ||||
|     'use strict'; | ||||
| 
 | ||||
|     // Taken from oauth3.core.js | ||||
| 
 | ||||
|     // TODO what about search within hash? | ||||
|     var prefix = "(" + window.location.hostname + ") [.well-known/oauth3/]"; | ||||
|     var params = OAUTH3.query.parse(window.location.hash || window.location.search); | ||||
|     if (params.debug) { | ||||
|       console.warn(prefix, "DEBUG MODE ENABLED. Automatic redirects disabled."); | ||||
|     } | ||||
| 
 | ||||
|     console.log(prefix, 'hash||search:'); | ||||
|     console.log(window.location.hash || window.location.search); | ||||
| 
 | ||||
|     console.log(prefix, 'params:'); | ||||
|     console.log(params); | ||||
| 
 | ||||
|     OAUTH3.request({ url: 'directives.json' }).then(function (resp) { | ||||
|       var urlsafe64 = OAUTH3._base64.encodeUrlSafe(JSON.stringify(resp.data, null, 0)); | ||||
|       var redirect; | ||||
| 
 | ||||
|       console.log(prefix, 'directives'); | ||||
|       console.log(resp); | ||||
| 
 | ||||
|       console.log(prefix, 'base64'); | ||||
|       console.log(urlsafe64); | ||||
| 
 | ||||
|       // TODO try postMessage back to redirect_uri domain right here | ||||
|       // window.postMessage(); | ||||
| 
 | ||||
|       // TODO make sure it's https NOT http | ||||
|       // NOTE: this can be only up to 2,083 characters | ||||
|       console.log(prefix, 'params.redirect_uri:', params.redirect_uri); | ||||
|       redirect = params.redirect_uri + '?' + OAUTH3.query.stringify({ | ||||
|         state: params.state | ||||
|       , directives: urlsafe64 | ||||
|       , debug: params.debug || undefined | ||||
|       }) | ||||
| 
 | ||||
|       console.log(prefix, 'redirect'); | ||||
|       console.log(redirect); | ||||
|       if (!params.debug) { | ||||
|         window.location = redirect; | ||||
|       } else { | ||||
|         // yes, we're violating the security lint with purpose | ||||
|         document.body.innerHTML += window.location.host + window.location.pathname | ||||
|           + '<br/><br/>You\'ve passed the \'debug\' parameter so we\'re pausing' | ||||
|           + ' to let you look at logs or whatever it is that you intended to do.' | ||||
|           + '<br/><br/>Continue with redirect: <a href="' + redirect + '">' + redirect + '</' + 'a>'; | ||||
|       } | ||||
|     }); | ||||
| 
 | ||||
|     }()); | ||||
|   </script> | ||||
|   </body> | ||||
| </html> | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user