Compare commits
	
		
			92 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 | ||
|  | 6b7fd877ec | ||
|  | 4d80074046 | ||
|  | b60e9b8fce | ||
|  | c250ab07f4 | ||
|  | 623d94e045 | ||
|  | 516eda4ea6 | ||
|  | 5d42f3e2cc | ||
|  | 39b8e19bae | ||
|  | e42079d856 | ||
|  | 197c0fdcb2 | ||
|  | 84a574e31b | ||
|  | 39c18ab184 | ||
|  | 5a5488f504 | ||
|  | 28dbf9ab23 | ||
|  | 1ca6f0a324 | ||
|  | c38554a9dd | 
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -1,3 +1,5 @@ | |||||||
|  | prefactor | ||||||
|  | .well-known | ||||||
| node_modules/ | node_modules/ | ||||||
| DS_Store | DS_Store | ||||||
| .vscode | .vscode | ||||||
| @ -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. | ||||||
							
								
								
									
										78
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										78
									
								
								README.md
									
									
									
									
									
								
							| @ -1,6 +1,12 @@ | |||||||
| oauth3.js | 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 | 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!) | (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/` | 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/` | 2. Inside of the folder `example.com/` a folder called `assets/` | ||||||
| 3. Inside of the folder `example.com/assets` a folder called `org.oauth3/` | 3. Inside of the folder `example.com/assets` a folder called `oauth3.org/` | ||||||
| 4. Download [oauth3.js-v1.zip](https://git.daplie.com/OAuth3/oauth3.js/repository/archive.zip?ref=v1) | 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. | 5. Double-click to unzip the folder. | ||||||
| 6. Copy the file `oauth3.core.js` into the folder `example.com/assets/org.oauth3/` | 6. Copy the file `oauth3.core.js` into the folder `example.com/assets/oauth3.org/` | ||||||
| 7. Copy the folder `well-known` into the folder `example.com/` | 7. Copy the folder `_apis` 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/oauth3.org/oauth3.core.js"></script>` to your `index.html` | ||||||
| 9. Add `<script src="assets/org.oauth3/oauth3.core.js"></script>` to your `index.html` |  | ||||||
| 9. Add `<script src="app.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: | 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" |   <script src="https://code.jquery.com/jquery-3.1.1.js" | ||||||
|     integrity="sha256-16cdPddA6VdVInumRGo6IbivbERE8p7CQR3HzTBuELA=" |     integrity="sha256-16cdPddA6VdVInumRGo6IbivbERE8p7CQR3HzTBuELA=" | ||||||
|     crossorigin="anonymous"></script> |     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> |   <script src="app.js"></script> | ||||||
| </body> | </body> | ||||||
| </html> | </html> | ||||||
| @ -53,13 +58,13 @@ If you have no idea what you're doing | |||||||
| `app.js`: | `app.js`: | ||||||
| ```js | ```js | ||||||
| var OAUTH3 = window.OAUTH3; | 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 | // 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 | // 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 |   // example https://oauth3.org | ||||||
|   return oauth3.setIdentityProvider(providerUri); |   return oauth3.setIdentityProvider(providerUri); | ||||||
| } | } | ||||||
| @ -81,11 +86,13 @@ function onClickLogin() { | |||||||
|     console.info('Secure PPID (aka subject):', session.token.sub); |     console.info('Secure PPID (aka subject):', session.token.sub); | ||||||
| 
 | 
 | ||||||
|     return oauth3.request({ |     return oauth3.request({ | ||||||
|       url: 'https://oauth3.org/api/issuer@oauth3.org/inspect_token' |       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 |     , session: session | ||||||
|     }).then(function (resp) { |     }).then(function (resp) { | ||||||
| 
 | 
 | ||||||
|       console.info("Inspect Token:"); |       console.info("Signing Public Key JWK:"); | ||||||
|       console.log(resp.data); |       console.log(resp.data); | ||||||
| 
 | 
 | ||||||
|     }); |     }); | ||||||
| @ -138,13 +145,13 @@ it might look like this: | |||||||
| example.com | example.com | ||||||
| │ | │ | ||||||
| │ | │ | ||||||
| ├── .well-known (hidden) | ├── _apis | ||||||
| │   └── oauth3 | │   └── oauth3.org | ||||||
| │       ├── callback.html | │       ├── callback.html | ||||||
| │       ├── directives.json | │       ├── directives.json | ||||||
| │       └── index.html | │       └── index.html | ||||||
| ├── assets | ├── assets | ||||||
| │   └── org.oauth3 | │   └── oauth3.org | ||||||
| │       └── oauth3.core.js | │       └── oauth3.core.js | ||||||
| │ | │ | ||||||
| │ | │ | ||||||
| @ -165,17 +172,17 @@ Installation (if you know what you're doing) | |||||||
| pushd /path/to/your/web/app | pushd /path/to/your/web/app | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| # clone the project as assets/org.oauth3 | # clone the project as assets/oauth3.org | ||||||
| mkdir -p assets | mkdir -p assets | ||||||
| git clone git@git.daplie.com:OAuth3/oauth3.js.git assets/org.oauth3 | git clone git@git.oauth3.org:OAuth3/oauth3.js.git assets/oauth3.org | ||||||
| pushd assets/org.oauth3 | pushd assets/oauth3.org | ||||||
| git checkout v1 | git checkout v1 | ||||||
| popd | popd | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| # symlink `.well-known/oauth3` to `assets/org.oauth3/.well-known/oauth3` | # symlink `_apis/oauth3.org` to `assets/oauth3.org/_apis/oauth3.org` | ||||||
| mkdir -p .well-known | mkdir -p _apis | ||||||
| ln -sf  ../assets/org.oauth3/.well-known/oauth3 .well-known/oauth3 | ln -sf  ../assets/oauth3.org/_apis/oauth3 _apis/oauth3.org | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| **Advanced Installation with `bower`** | **Advanced Installation with `bower`** | ||||||
| @ -185,17 +192,17 @@ ln -sf  ../assets/org.oauth3/.well-known/oauth3 .well-known/oauth3 | |||||||
| bower install oauth3 | bower install oauth3 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| # create a `.well-known` folder and an `assets` folder | # create a `_apis` folder and an `assets` folder | ||||||
| mkdir -p .well-known assets | mkdir -p _apis assets | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| # symlink `.well-known/oauth3` to `bower_components/oauth3/.well-known/oauth3` | # symlink `_apis/oauth3.org` to `bower_components/oauth3.org/_apis/oauth3.org` | ||||||
| ln -sf  ../bower_components/oauth3/.well-known/oauth3 .well-known/oauth3 | ln -sf  ../bower_components/oauth3.org/_apis/oauth3.org _apis/oauth3.org | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| # symlink `assets/org.oauth3` to `bower_components/oauth3` | # symlink `assets/oauth3.org` to `bower_components/oauth3.org` | ||||||
| ln -sf  ../bower_components/oauth3/.well-known/oauth3 .well-known/oauth3 | ln -sf  ../bower_components/oauth3.org/_apis/oauth3.org _apis/oauth3.org | ||||||
| ln -sf  ../bower_components/oauth3 assets/org.oauth3 | ln -sf  ../bower_components/oauth3.org assets/oauth3.org | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| Usage | Usage | ||||||
| @ -204,7 +211,7 @@ Usage | |||||||
| Update your HTML to include the the following script tag: | Update your HTML to include the the following script tag: | ||||||
| 
 | 
 | ||||||
| ```html | ```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: | You can create a very simple demo application like this: | ||||||
| @ -283,7 +290,7 @@ You're all set. Nothing else is needed. | |||||||
| We've created an `Oauth3` service just for you: | We've created an `Oauth3` service just for you: | ||||||
| 
 | 
 | ||||||
| ```html | ```html | ||||||
| <script src="assets/org.oauth3/oauth3.ng.js"></script> | <script src="assets/oauth3.org/oauth3.ng.js"></script> | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| ```js | ```js | ||||||
| @ -316,7 +323,7 @@ promise = oauth3.init(opts);                        // set and fetch your own si | |||||||
| // promises your site's config                      // opts = { location, session, issuer, audience } | // promises your site's config                      // opts = { location, session, issuer, audience } | ||||||
| 
 | 
 | ||||||
| promise = oauth3.setIdentityProvider(url);          // changes the Identity Provider URI (the site you're logging into), | 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 .well-known/oauth3), | // 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 |                                                     // and caches it in internal state as the default | ||||||
| 
 | 
 | ||||||
| promise = oauth3.setResourceProvider(url);          // changes the Resource Provider URI (the site you're getting stuff from) | promise = oauth3.setResourceProvider(url);          // changes the Resource Provider URI (the site you're getting stuff from) | ||||||
| @ -333,12 +340,11 @@ promise = oauth3.request({ url, method, data });    // make an (authorized) arbi | |||||||
|                                                     // (contacts, photos, whatever) |                                                     // (contacts, photos, whatever) | ||||||
| 
 | 
 | ||||||
| promise = oauth3.api(apiname, opts);                // make an (authorized) well-known api call to an audience | promise = oauth3.api(apiname, opts);                // make an (authorized) well-known api call to an audience | ||||||
|                                                     // See https://labs.daplie.com/docs/ for API schemas |                                                     // Ex: oauth3.api('dns.list', { sld: 'example', tld: 'com' }); | ||||||
|                                                     // Ex: oauth3.api('dns.list', { sld: 'daplie', tld: 'com' }); |  | ||||||
| 
 | 
 | ||||||
| // TODO | // TODO | ||||||
| api = await oauth3.package(audience, schemaname);   // make an (authorized) well-known api call to an audience | api = await oauth3.package(audience, schemaname);   // make an (authorized) well-known api call to an audience | ||||||
|                                                     // Ex: api = await oauth3.package('domains.daplie.com', 'dns@oauth3.org'); |                                                     // Ex: api = await oauth3.package('domains.example.com', 'dns@oauth3.org'); | ||||||
|                                                     //     api.list({ sld: 'mydomain', tld: 'com' }); |                                                     //     api.list({ sld: 'mydomain', tld: 'com' }); | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @ -347,6 +353,10 @@ promise = oauth3.logout();                          // opens logout window for t | |||||||
| oauth3.session();                                   // returns the current session, if any | oauth3.session();                                   // returns the current session, if any | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
|  | <!-- TODO | ||||||
|  | Track down the old https://labs.daplie.com/docs/ for API schemas | ||||||
|  | -- | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| Real API | Real API | ||||||
| ---------- | ---------- | ||||||
| @ -488,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) | 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 | 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. | 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 | 
							
								
								
									
										
											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" | ||||||
|  |     } | ||||||
							
								
								
									
										129
									
								
								bin/cli.js
									
									
									
									
									
								
							
							
						
						
									
										129
									
								
								bin/cli.js
									
									
									
									
									
								
							| @ -10,8 +10,8 @@ function parseArgs(argv, opts) { | |||||||
|   var args = Array.prototype.slice.call(argv); |   var args = Array.prototype.slice.call(argv); | ||||||
|   var sep = /[:\.\-]/; |   var sep = /[:\.\-]/; | ||||||
| 
 | 
 | ||||||
|   args.shift(); // node
 |   args.shift(); // 'node' is the first parameter
 | ||||||
|   args.shift(); // oauth3.js
 |   args.shift(); // 'oauth3.js' will be the
 | ||||||
| 
 | 
 | ||||||
|   var command = args.shift() || 'help'; |   var command = args.shift() || 'help'; | ||||||
|   var cmdpair = command.split(sep); |   var cmdpair = command.split(sep); | ||||||
| @ -20,12 +20,12 @@ function parseArgs(argv, opts) { | |||||||
|   var COMMAND = 'COMMAND'; |   var COMMAND = 'COMMAND'; | ||||||
|   var maxCmdLen = COMMAND.length; |   var maxCmdLen = COMMAND.length; | ||||||
|   var maxPairLen = 0; |   var maxPairLen = 0; | ||||||
|   var cmds; |  | ||||||
|   var arg1 = args[0]; |   var arg1 = args[0]; | ||||||
| 
 | 
 | ||||||
|   // build commands list
 |   // build top-level commands (tlcs) list
 | ||||||
|  |   // also count the word-width (for the space needed to print the commands)
 | ||||||
|   var pairsMap = {}; |   var pairsMap = {}; | ||||||
|   cmds = opts.commands.filter(function (desc) { |   var tlcs = opts.commands.filter(function (desc) { | ||||||
|     var pair = desc[0].split(/\s+/)[0]; |     var pair = desc[0].split(/\s+/)[0]; | ||||||
|     var psub = pair.split(sep)[0]; |     var psub = pair.split(sep)[0]; | ||||||
|     pairsMap[pair] = true; |     pairsMap[pair] = true; | ||||||
| @ -36,13 +36,7 @@ function parseArgs(argv, opts) { | |||||||
|     } |     } | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|   if (-1 === Object.keys(pairsMap).indexOf(cmd)) { |   // right pad (for making the printed lines longer)
 | ||||||
|     console.log('fail', cmd); |  | ||||||
|     arg1 = cmd; |  | ||||||
|     cmd = 'help'; |  | ||||||
|     help(); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   function rpad(str, len) { |   function rpad(str, len) { | ||||||
|     while (str.length < len) { |     while (str.length < len) { | ||||||
|       str += ' '; |       str += ' '; | ||||||
| @ -50,45 +44,124 @@ function parseArgs(argv, opts) { | |||||||
|     return str; |     return str; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   // oauth3.js help
 | ||||||
|  |   // oauth3.js help <command>
 | ||||||
|  |   // oauth3.js help <command:sub> (alias of `oauth3.js <command:sub> --help')
 | ||||||
|   function help() { |   function help() { | ||||||
|     var status = 0; |     var status = 0; | ||||||
| 
 | 
 | ||||||
|     function helpMain() { |     function printCmd(desc) { | ||||||
|       console.log(''); |  | ||||||
|       console.log('Here are all the top-level commands:'); |  | ||||||
|       console.log(''); |  | ||||||
| 
 |  | ||||||
|       console.log('\t' + defaults.main + ' ' + rpad(COMMAND, maxCmdLen), ' # description'); |  | ||||||
|       console.log('\t' + '------------------------------'); |  | ||||||
|       cmds.forEach(function (desc) { |  | ||||||
|       var pcmd = rpad(desc[0].split(/\s+/)[0], maxCmdLen); |       var pcmd = rpad(desc[0].split(/\s+/)[0], maxCmdLen); | ||||||
|       var pdesc = desc[1]; |       var pdesc = desc[1]; | ||||||
|         console.log('\t' + defaults.main + ' ' + pcmd, ' # ' + pdesc); |       console.info('\t' + defaults.main + ' ' + pcmd, ' # ' + pdesc); | ||||||
|       }); |     } | ||||||
|       console.log(''); | 
 | ||||||
|  |     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)) { |     if (arg1 && -1 === Object.keys(pairsMap).indexOf(arg1)) { | ||||||
|       status = 1; |       status = 1; | ||||||
|       console.log(''); |       console.info(''); | ||||||
|       console.log(defaults.main + ": Unknown command '" + arg1 + "'"); |       console.info(defaults.main + ": Unknown command '" + arg1 + "'"); | ||||||
|  |       console.info(''); | ||||||
|  |       console.info("Try '" + defaults.main + " help'"); | ||||||
|  |       console.info(''); | ||||||
|       arg1 = null; |       arg1 = null; | ||||||
|  |       return; | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     // the case of "oauth3 help --something"
 | ||||||
|     if (!arg1 || '-' === arg1[0]) { |     if (!arg1 || '-' === arg1[0]) { | ||||||
|       helpMain(); |       helpMain(); | ||||||
|       process.exit(status); |       process.exit(status); | ||||||
|  |       return; | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     // the case of "oauth3 help help"
 | ||||||
|     if ('help' === arg1) { |     if ('help' === arg1) { | ||||||
|       helpMain(); |       helpMain(); | ||||||
|       console.log("no more help available for 'help'"); |       console.info("no more help available for 'help'"); | ||||||
|       process.exit(status); |       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')) { |   if (-1 !== [ 'help', '-h', '--help' ].indexOf(command) || -1 !== args.indexOf('-h') || -1 !== args.indexOf('--help')) { | ||||||
|     help(); |     help(); | ||||||
|     return; |     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, { | parseArgs(process.argv, { | ||||||
| @ -131,8 +204,8 @@ parseArgs(process.argv, { | |||||||
| 
 | 
 | ||||||
|     // authn / authz
 |     // authn / authz
 | ||||||
|   , [ 'devices', 'manages devices for your account(s)' ] |   , [ '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/org.oauth3.tunnel/checkip)'.replace(/\b:provider\b/, defaults.provider) ] |   , [ '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/org.oauth3.tunnel/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:attach', "attach a device to a domain's DNS record" ] | ||||||
|   , [ 'devices:detach', "detach an account from 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:select', '(re)claim the specified device as this device (i.e. you re-installed your OS or deleted your ~/.oauth3)' ] | ||||||
|  | |||||||
| @ -26,7 +26,7 @@ | |||||||
|     "sign" |     "sign" | ||||||
|   ], |   ], | ||||||
|   "license": "MIT", |   "license": "MIT", | ||||||
|   "homepage": "https://git.daplie.com/OAuth3/oauth3.js", |   "homepage": "https://git.oauth3.org/OAuth3/oauth3.js", | ||||||
|   "ignore": [ |   "ignore": [ | ||||||
|     "**/.*", |     "**/.*", | ||||||
|     "browserify", |     "browserify", | ||||||
|  | |||||||
| @ -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); | ||||||
|  |         }); | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | }()); | ||||||
							
								
								
									
										595
									
								
								oauth3.core.js
									
									
									
									
									
								
							
							
						
						
									
										595
									
								
								oauth3.core.js
									
									
									
									
									
								
							| @ -78,7 +78,7 @@ | |||||||
|   , uri: { |   , uri: { | ||||||
|       normalize: function (uri) { |       normalize: function (uri) { | ||||||
|         if ('string' !== typeof uri) { |         if ('string' !== typeof uri) { | ||||||
|           console.error((new Error('stack')).stack); |           throw new Error("attempted to normalize non-string URI: "+JSON.stringify(uri)); | ||||||
|         } |         } | ||||||
|         // tested with
 |         // tested with
 | ||||||
|         //   example.com
 |         //   example.com
 | ||||||
| @ -94,7 +94,7 @@ | |||||||
|   , url: { |   , url: { | ||||||
|       normalize: function (url) { |       normalize: function (url) { | ||||||
|         if ('string' !== typeof url) { |         if ('string' !== typeof url) { | ||||||
|           console.error((new Error('stack')).stack); |           throw new Error("attempted to normalize non-string URL: "+JSON.stringify(url)); | ||||||
|         } |         } | ||||||
|         // tested with
 |         // tested with
 | ||||||
|         //   example.com
 |         //   example.com
 | ||||||
| @ -168,9 +168,12 @@ | |||||||
|       } |       } | ||||||
|     } |     } | ||||||
|   , scope: { |   , scope: { | ||||||
|       stringify: function (scope) { |       parse: function (scope) { | ||||||
|  |         return (scope||'').toString().split(/[+, ]+/g); | ||||||
|  |       } | ||||||
|  |     , stringify: function (scope) { | ||||||
|         if (Array.isArray(scope)) { |         if (Array.isArray(scope)) { | ||||||
|           scope = scope.join(' '); |           scope = scope.join(','); | ||||||
|         } |         } | ||||||
|         return scope; |         return scope; | ||||||
|       } |       } | ||||||
| @ -204,78 +207,140 @@ | |||||||
|     } |     } | ||||||
|   , jwt: { |   , jwt: { | ||||||
|       // decode only (no verification)
 |       // decode only (no verification)
 | ||||||
|       decode: function (str) { |       decode: function (token, opts) { | ||||||
| 
 | 
 | ||||||
|         // 'abc.qrs.xyz'
 |         // 'abc.qrs.xyz'
 | ||||||
|         // [ 'abc', 'qrs', 'xyz' ]
 |         // [ 'abc', 'qrs', 'xyz' ]
 | ||||||
|         // [ {}, {}, 'foo' ]
 |         // {}
 | ||||||
|         // { header: {}, payload: {}, signature: '' }
 |         var parts = token.split(/\./g); | ||||||
|         var parts = str.split(/\./g); |         var err; | ||||||
|         var jsons = parts.slice(0, 2).map(function (urlsafe64) { |         if (parts.length !== 3) { | ||||||
|           return JSON.parse(OAUTH3._base64.decodeUrlSafe(urlsafe64)); |           err = new Error("Invalid JWT: required 3 '.' separated components not "+parts.length); | ||||||
|         }); |           err.code = 'E_INVALID_JWT'; | ||||||
| 
 |           throw err; | ||||||
|         return { header: jsons[0], payload: jsons[1] }; |  | ||||||
|         } |         } | ||||||
|     , verify: function (jwk, token) { | 
 | ||||||
|  |         if (!opts || !opts.complete) { | ||||||
|  |           return JSON.parse(OAUTH3._base64.decodeUrlSafe(parts[1])); | ||||||
|  |         } | ||||||
|  |         return { | ||||||
|  |           header:  JSON.parse(OAUTH3._base64.decodeUrlSafe(parts[0])) | ||||||
|  |         , payload: JSON.parse(OAUTH3._base64.decodeUrlSafe(parts[1])) | ||||||
|  |         }; | ||||||
|  |       } | ||||||
|  |     , verify: function (token, jwk) { | ||||||
|  |         if (!OAUTH3.crypto) { | ||||||
|  |           return OAUTH3.PromiseA.reject(new Error("OAuth3 crypto library unavailable")); | ||||||
|  |         } | ||||||
|  |         jwk = jwk.publicKey || jwk; | ||||||
|  | 
 | ||||||
|         var parts = token.split(/\./g); |         var parts = token.split(/\./g); | ||||||
|         var data = OAUTH3._binStr.binStrToBuffer(parts.slice(0, 2).join('.')); |         var data = OAUTH3._binStr.binStrToBuffer(parts.slice(0, 2).join('.')); | ||||||
|         var signature = OAUTH3._base64.urlSafeToBuffer(parts[2]); |         var signature = OAUTH3._base64.urlSafeToBuffer(parts[2]); | ||||||
| 
 | 
 | ||||||
|         return OAUTH3.crypto.core.verify(jwk, data, signature); |         return OAUTH3.crypto.core.verify(jwk, data, signature).then(function () { | ||||||
|  |           return OAUTH3.jwt.decode(token); | ||||||
|  |         }); | ||||||
|       } |       } | ||||||
|     , freshness: function (tokenMeta, staletime, _now) { |     , sign: function (payload, jwk) { | ||||||
|         staletime = staletime || (15 * 60); |         if (!OAUTH3.crypto) { | ||||||
|         var now = _now || Date.now(); |           return OAUTH3.PromiseA.reject(new Error("OAuth3 crypto library unavailable")); | ||||||
|         var fresh = ((parseInt(tokenMeta.exp, 10) || 0) - Math.round(now / 1000)); |         } | ||||||
|  |         jwk = jwk.private_key || jwk.privateKey || jwk; | ||||||
| 
 | 
 | ||||||
|         if (fresh >= staletime) { |         var prom; | ||||||
|  |         if (jwk.kid) { | ||||||
|  |           prom = OAUTH3.PromiseA.resolve(jwk.kid); | ||||||
|  |         } else { | ||||||
|  |           prom = OAUTH3.crypto.thumbprintJwk(jwk); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return prom.then(function (kid) { | ||||||
|  |           // Currently the crypto part of the OAuth3 library only supports ES256
 | ||||||
|  |           var header = {type: 'JWT', alg: 'ES256', kid: kid}; | ||||||
|  |           var input = [ | ||||||
|  |             OAUTH3._base64.encodeUrlSafe(JSON.stringify(header, null)) | ||||||
|  |           , OAUTH3._base64.encodeUrlSafe(JSON.stringify(payload, null)) | ||||||
|  |           ].join('.'); | ||||||
|  | 
 | ||||||
|  |           return OAUTH3.crypto.core.sign(jwk, OAUTH3._binStr.binStrToBuffer(input)) | ||||||
|  |             .then(OAUTH3._base64.bufferToUrlSafe) | ||||||
|  |             .then(function (signature) { | ||||||
|  |               return input + '.' + signature; | ||||||
|  |             }); | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|  |     , freshness: function (tokenMeta, staletime, now) { | ||||||
|  |         // If the token doesn't expire then it's always fresh.
 | ||||||
|  |         if (!tokenMeta.exp) { | ||||||
|           return 'fresh'; |           return 'fresh'; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if (fresh <= 0) { |         staletime = staletime || (15 * 60); | ||||||
|           return 'expired'; |         now = now || Date.now(); | ||||||
|  |         // This particular number used to check if time is in milliseconds or seconds will work
 | ||||||
|  |         // for any date between the years 1973 and 5138.
 | ||||||
|  |         if (now > 1e11) { | ||||||
|  |           now = Math.round(now / 1000); | ||||||
|         } |         } | ||||||
| 
 |         var exp = parseInt(tokenMeta.exp, 10) || 0; | ||||||
|  |         if (exp < now) { | ||||||
|  |           return 'expired'; | ||||||
|  |         } else if (exp < now + staletime) { | ||||||
|           return 'stale'; |           return 'stale'; | ||||||
|  |         } else { | ||||||
|  |           return 'fresh'; | ||||||
|  |         } | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|   , urls: { |   , urls: { | ||||||
|       discover: function (providerUri, opts) { |       rpc: function (providerUri, opts) { | ||||||
|         if (!providerUri) { |         if (!providerUri) { | ||||||
|           throw new Error("cannot discover without providerUri"); |           throw new Error("cannot run rpc without providerUri"); | ||||||
|         } |         } | ||||||
|         if (!opts.client_id) { |         if (!opts.client_id) { | ||||||
|           throw new Error("cannot discover without options.client_id"); |           throw new Error("cannot run rpc without options.client_id"); | ||||||
|         } |         } | ||||||
|         var clientId = OAUTH3.url.normalize(opts.client_id || opts.client_uri); |         var clientId = OAUTH3.url.normalize(opts.client_id || opts.client_uri); | ||||||
|         providerUri = OAUTH3.url.normalize(providerUri); |         providerUri = OAUTH3.url.normalize(providerUri); | ||||||
| 
 | 
 | ||||||
|         var params = { |         var params = { | ||||||
|           action: 'directives' |           state: opts.state || OAUTH3.utils.randomState() | ||||||
|         , state: opts.state || OAUTH3.utils.randomState() |  | ||||||
|         , redirect_uri: clientId + (opts.client_callback_path || '/.well-known/oauth3/callback.html#/') |         , redirect_uri: clientId + (opts.client_callback_path || '/.well-known/oauth3/callback.html#/') | ||||||
|         , response_type: 'rpc' |         , response_type: 'rpc' | ||||||
|         , _method: 'GET' |         , _method: 'GET' | ||||||
|         , _pathname: '.well-known/oauth3/directives.json' |         , _scheme: opts._scheme | ||||||
|  |         , _pathname: opts._pathname | ||||||
|         , debug: opts.debug || undefined |         , debug: opts.debug || undefined | ||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
|         var result = { |         var toRequest = { | ||||||
|           url: providerUri + '/.well-known/oauth3/#/?' + OAUTH3.query.stringify(params) |           url: providerUri + '/.well-known/oauth3/#/?' + OAUTH3.query.stringify(params) | ||||||
|         , state: params.state |         , state: params.state | ||||||
|         , method: 'GET' |         , method: 'GET' | ||||||
|         , query: params |         , query: params | ||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
|         return result; |         return toRequest; | ||||||
|  |       } | ||||||
|  |     , broker: function (providerUri, opts) { | ||||||
|  |         opts._scheme = "localstorage:"; | ||||||
|  |         opts._pathname = "issuer"; | ||||||
|  |         return OAUTH3.urls.rpc(providerUri, opts); | ||||||
|  |       } | ||||||
|  |     , discover: function (providerUri, opts) { | ||||||
|  |         return OAUTH3.urls.directives(providerUri, opts); | ||||||
|  |       } | ||||||
|  |     , directives: function (providerUri, opts) { | ||||||
|  |         opts._pathname = ".well-known/oauth3/scopes.json"; | ||||||
|  |         return OAUTH3.urls.rpc(providerUri, opts); | ||||||
|       } |       } | ||||||
|     , implicitGrant: function (directive, opts) { |     , implicitGrant: function (directive, opts) { | ||||||
|         //
 |         //
 | ||||||
|         // Example Implicit Grant Request
 |         // Example Implicit Grant Request
 | ||||||
|         // (for generating a browser-only session, not a session on your server)
 |         // (for generating a browser-only session, not a session on your server)
 | ||||||
|         //
 |         //
 | ||||||
|         // GET https://example.com/api/org.oauth3.provider/authorization_dialog
 |         // GET https://example.com/api/issuer@oauth3.org/authorization_dialog
 | ||||||
|         //  ?response_type=token
 |         //  ?response_type=token
 | ||||||
|         //  &scope=`encodeURIComponent('profile.login profile.email')`
 |         //  &scope=`encodeURIComponent('profile.login profile.email')`
 | ||||||
|         //  &state=`cryptoutil.random().toString('hex')`
 |         //  &state=`cryptoutil.random().toString('hex')`
 | ||||||
| @ -341,29 +406,36 @@ | |||||||
|         //    , "username": "<<username>>", "password": "password" }
 |         //    , "username": "<<username>>", "password": "password" }
 | ||||||
|         //
 |         //
 | ||||||
|         opts = opts || {}; |         opts = opts || {}; | ||||||
|         var type = 'access_token'; |         var refresh_token = opts.refresh_token || (opts.session && opts.session.refresh_token); | ||||||
|         var grantType = 'refresh_token'; |         var err; | ||||||
|  |         if (!refresh_token) { | ||||||
|  |           err = new Error('refreshing a token requires a refresh token'); | ||||||
|  |           err.code = 'E_NO_TOKEN'; | ||||||
|  |           throw err; | ||||||
|  |         } | ||||||
|  |         if (OAUTH3.jwt.freshness(OAUTH3.jwt.decode(refresh_token)) === 'expired') { | ||||||
|  |           err = new Error('refresh token has also expired, login required again'); | ||||||
|  |           err.code = 'E_EXPIRED_TOKEN'; | ||||||
|  |           throw err; | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|         var scope = opts.scope || directive.authn_scope; |         var scope = opts.scope || directive.authn_scope; | ||||||
|         var clientSecret = opts.client_secret; |         var args = directive.access_token; | ||||||
|         var args = directive[type]; |  | ||||||
|         var params = { |         var params = { | ||||||
|           "grant_type": grantType |           "grant_type": 'refresh_token' | ||||||
|         , "refresh_token": opts.refresh_token || (opts.session && opts.session.refresh_token) |         , "refresh_token": refresh_token | ||||||
|         , "response_type": 'token' |         , "response_type": 'token' | ||||||
|         , "client_id": opts.client_id || opts.client_uri |         , "client_id": opts.client_id || opts.client_uri | ||||||
|         , "client_uri": opts.client_uri |         , "client_uri": opts.client_uri | ||||||
|         //, "scope": undefined
 |  | ||||||
|         //, "client_secret": undefined
 |  | ||||||
|         , debug: opts.debug || undefined |         , debug: opts.debug || undefined | ||||||
|         }; |         }; | ||||||
|         var uri = args.url; |         var uri = args.url; | ||||||
|         var body; |         var body; | ||||||
| 
 | 
 | ||||||
|         if (clientSecret) { |         if (opts.client_secret) { | ||||||
|           // TODO not allowed in the browser
 |           // TODO not allowed in the browser
 | ||||||
|           console.warn("if this is a browser, you must not use client_secret"); |           console.warn("if this is a browser, you must not use client_secret"); | ||||||
|           params.client_secret = clientSecret; |           params.client_secret = opts.client_secret; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if (scope) { |         if (scope) { | ||||||
| @ -430,42 +502,52 @@ | |||||||
|       } |       } | ||||||
|     } |     } | ||||||
|   , hooks: { |   , hooks: { | ||||||
|       directives: { |       _checkStorage: function (grpName, funcName) { | ||||||
|         get: function (providerUri) { |         if (!OAUTH3._hooks) { | ||||||
|           providerUri = OAUTH3.uri.normalize(providerUri); |           OAUTH3._hooks = {}; | ||||||
|           return OAUTH3.PromiseA.resolve(OAUTH3.hooks.directives._getCached(providerUri) |  | ||||||
|             || OAUTH3.hooks.directives._get(providerUri)) |  | ||||||
|             .then(function (directives) { |  | ||||||
|               // or do .then(this._set) to keep DRY?
 |  | ||||||
|             OAUTH3.hooks.directives._cache[providerUri] = directives; |  | ||||||
|             return directives; |  | ||||||
|           }); |  | ||||||
|         } |         } | ||||||
|       , _getCached: function (providerUri) { |         if (!OAUTH3._hooks[grpName]) { | ||||||
|           providerUri = OAUTH3.uri.normalize(providerUri); |           console.log('using default storage for '+grpName+', set OAUTH3._hooks.'+grpName+' for custom storage'); | ||||||
|           if (!OAUTH3.hooks.directives._cache) { OAUTH3.hooks.directives._cache = {}; } |           OAUTH3._hooks[grpName] = OAUTH3._defaultStorage[grpName]; | ||||||
|           return OAUTH3.hooks.directives._cache[providerUri]; |         } | ||||||
|  |         if (!OAUTH3._hooks[grpName][funcName]) { | ||||||
|  |           throw new Error("'"+funcName+"' is not defined for custom "+grpName+" storage"); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     , directives: { | ||||||
|  |         get: function (providerUri) { | ||||||
|  |           OAUTH3.hooks._checkStorage('directives', 'get'); | ||||||
|  | 
 | ||||||
|  |           if (!providerUri) { | ||||||
|  |             throw new Error("providerUri is not set"); | ||||||
|  |           } | ||||||
|  |           return OAUTH3.PromiseA.resolve(OAUTH3._hooks.directives.get(OAUTH3.uri.normalize(providerUri))); | ||||||
|         } |         } | ||||||
|       , set: function (providerUri, directives) { |       , set: function (providerUri, directives) { | ||||||
|           providerUri = OAUTH3.uri.normalize(providerUri); |           OAUTH3.hooks._checkStorage('directives', 'set'); | ||||||
|           if (!OAUTH3.hooks.directives._cache) { OAUTH3.hooks.directives._cache = {}; } | 
 | ||||||
|           OAUTH3.hooks.directives._cache[providerUri] = directives; |           if (!providerUri) { | ||||||
|           return OAUTH3.PromiseA.resolve(OAUTH3.hooks.directives._set(providerUri, directives)); |             throw new Error("providerUri is not set"); | ||||||
|           } |           } | ||||||
|       , _get: function (providerUri) { |           return OAUTH3.PromiseA.resolve(OAUTH3._hooks.directives.set(OAUTH3.uri.normalize(providerUri), directives)); | ||||||
|           if (!OAUTH3._hooks || !OAUTH3._hooks.directives || !OAUTH3._hooks.directives.get) { |  | ||||||
|             console.warn('[Warn] Please implement OAUTH3._hooks.directives.get = function (providerUri) { return PromiseA<directives>; }'); |  | ||||||
|             return JSON.parse(window.localStorage.getItem('directives-' + providerUri) || '{}'); |  | ||||||
|         } |         } | ||||||
|           return OAUTH3._hooks.directives.get(providerUri); |       , all: function () { | ||||||
|  |           OAUTH3.hooks._checkStorage('directives', 'all'); | ||||||
|  | 
 | ||||||
|  |           return OAUTH3.PromiseA.resolve(OAUTH3._hooks.directives.all()); | ||||||
|         } |         } | ||||||
|       , _set: function (providerUri, directives) { |       , clear: function () { | ||||||
|           if (!OAUTH3._hooks || !OAUTH3._hooks.directives || !OAUTH3._hooks.directives.set) { |           OAUTH3.hooks._checkStorage('directives', 'clear'); | ||||||
|             console.warn('[Warn] Please implement OAUTH3._hooks.directives.set = function (providerUri, directives) { return PromiseA<directives>; }'); | 
 | ||||||
|             window.localStorage.setItem('directives-' + providerUri, JSON.stringify(directives)); |           return OAUTH3.PromiseA.resolve(OAUTH3._hooks.directives.clear()); | ||||||
|             return directives; |  | ||||||
|         } |         } | ||||||
|           return OAUTH3._hooks.directives.set(providerUri, directives); |       } | ||||||
|  |     , scopes: { | ||||||
|  |         get: function(providerUri) { | ||||||
|  |           //TODO: retrieve cached scopes
 | ||||||
|  |         } | ||||||
|  |       , set: function(providerUri, scopes) { | ||||||
|  |           //TODO: cache scopes
 | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|     , session: { |     , session: { | ||||||
| @ -473,7 +555,7 @@ | |||||||
|           var providerUri = oldSession.provider_uri; |           var providerUri = oldSession.provider_uri; | ||||||
|           var clientUri = oldSession.client_uri; |           var clientUri = oldSession.client_uri; | ||||||
| 
 | 
 | ||||||
|           Object.keys(['access_token', 'token', 'client_uri', 'refresh', 'refresh_token', 'expires_in', 'provider_uri', 'scope', 'token_type']).forEach(function (key) { |           ['access_token', 'token', 'client_uri', 'refresh', 'refresh_token', 'expires_in', 'provider_uri', 'scope', 'token_type'].forEach(function (key) { | ||||||
|             oldSession[key] = undefined; |             oldSession[key] = undefined; | ||||||
|           }); |           }); | ||||||
|           Object.keys(newSession).forEach(function (key) { |           Object.keys(newSession).forEach(function (key) { | ||||||
| @ -485,7 +567,7 @@ | |||||||
|           oldSession.client_uri = clientUri;      // azp
 |           oldSession.client_uri = clientUri;      // azp
 | ||||||
| 
 | 
 | ||||||
|           // info about the newly-discovered token
 |           // info about the newly-discovered token
 | ||||||
|           oldSession.token = OAUTH3.jwt.decode(oldSession.access_token).payload; |           oldSession.token = OAUTH3.jwt.decode(oldSession.access_token); | ||||||
| 
 | 
 | ||||||
|           oldSession.token.sub = oldSession.token.sub |           oldSession.token.sub = oldSession.token.sub | ||||||
|             || (oldSession.token.acx||{}).id |             || (oldSession.token.acx||{}).id | ||||||
| @ -496,7 +578,7 @@ | |||||||
|           oldSession.token.provider_uri = providerUri; |           oldSession.token.provider_uri = providerUri; | ||||||
| 
 | 
 | ||||||
|           if (oldSession.refresh_token) { |           if (oldSession.refresh_token) { | ||||||
|             oldSession.refresh = OAUTH3.jwt.decode(oldSession.refresh_token).payload; |             oldSession.refresh = OAUTH3.jwt.decode(oldSession.refresh_token); | ||||||
|             oldSession.refresh.sub = oldSession.refresh.sub |             oldSession.refresh.sub = oldSession.refresh.sub | ||||||
|               || (oldSession.refresh.acx||{}).id |               || (oldSession.refresh.acx||{}).id | ||||||
|               || ((oldSession.refresh.axs||[])[0]||{}).appScopedId |               || ((oldSession.refresh.axs||[])[0]||{}).appScopedId | ||||||
| @ -506,7 +588,7 @@ | |||||||
|           } |           } | ||||||
| 
 | 
 | ||||||
|           // set for a set of audiences
 |           // set for a set of audiences
 | ||||||
|           return OAUTH3.PromiseA.resolve(OAUTH3.hooks.session.set(providerUri, oldSession)); |           return OAUTH3.hooks.session.set(providerUri, oldSession); | ||||||
|         } |         } | ||||||
|       , check: function (preq, opts) { |       , check: function (preq, opts) { | ||||||
|           opts = opts || {}; |           opts = opts || {}; | ||||||
| @ -559,68 +641,66 @@ | |||||||
|             return newSession; // oauth3.hooks.refreshSession(expiredSession, newSession);
 |             return newSession; // oauth3.hooks.refreshSession(expiredSession, newSession);
 | ||||||
|           }); |           }); | ||||||
|         } |         } | ||||||
|       , _getCached: function (providerUri, id) { |  | ||||||
|           providerUri = OAUTH3.uri.normalize(providerUri); |  | ||||||
|           if (!OAUTH3.hooks.session._cache) { OAUTH3.hooks.session._cache = {}; } |  | ||||||
|           if (id) { |  | ||||||
|             return OAUTH3.hooks.session._cache[providerUri + id]; |  | ||||||
|           } |  | ||||||
|           return OAUTH3.hooks.session._cache[providerUri]; |  | ||||||
|         } |  | ||||||
|       , set: function (providerUri, newSession, id) { |       , set: function (providerUri, newSession, id) { | ||||||
|  |           OAUTH3.hooks._checkStorage('sessions', 'set'); | ||||||
|  | 
 | ||||||
|           if (!providerUri) { |           if (!providerUri) { | ||||||
|             console.error(new Error('no providerUri').stack); |             console.error(new Error('no providerUri').stack); | ||||||
|             throw new Error("providerUri is not set"); |             throw new Error("providerUri is not set"); | ||||||
|           } |           } | ||||||
|           providerUri = OAUTH3.uri.normalize(providerUri); |           providerUri = OAUTH3.uri.normalize(providerUri); | ||||||
|           if (!OAUTH3.hooks.session._cache) { OAUTH3.hooks.session._cache = {}; } |           return OAUTH3.PromiseA.resolve(OAUTH3._hooks.sessions.set(providerUri, newSession, id)); | ||||||
|           OAUTH3.hooks.session._cache[providerUri + (id || newSession.id || newSession.token.id || '')] = newSession; |  | ||||||
|           if (!id) { |  | ||||||
|             OAUTH3.hooks.session._cache[providerUri] = newSession; |  | ||||||
|           } |  | ||||||
|           return OAUTH3.PromiseA.resolve(OAUTH3.hooks.session._set(providerUri, newSession)); |  | ||||||
|         } |         } | ||||||
|       , get: function (providerUri, id) { |       , get: function (providerUri, id) { | ||||||
|           providerUri = OAUTH3.uri.normalize(providerUri); |           OAUTH3.hooks._checkStorage('sessions', 'get'); | ||||||
|  | 
 | ||||||
|           if (!providerUri) { |           if (!providerUri) { | ||||||
|             throw new Error("providerUri is not set"); |             throw new Error("providerUri is not set"); | ||||||
|           } |           } | ||||||
| 
 |           providerUri = OAUTH3.uri.normalize(providerUri); | ||||||
|           return OAUTH3.PromiseA.resolve( |           return OAUTH3.PromiseA.resolve(OAUTH3._hooks.sessions.get(providerUri, id)); | ||||||
|             OAUTH3.hooks.session._getCached(providerUri, id) || OAUTH3.hooks.session._get(providerUri, id) |  | ||||||
|           ).then(function (session) { |  | ||||||
|             var s = session || { token: {} }; |  | ||||||
|             OAUTH3.hooks.session._cache[providerUri + (id || s.id || s.token.id || '')] = session; |  | ||||||
|             if (!id) { |  | ||||||
|               OAUTH3.hooks.session._cache[providerUri] = session; |  | ||||||
|         } |         } | ||||||
|             return session; |       , all: function (providerUri) { | ||||||
|  |           OAUTH3.hooks._checkStorage('sessions', 'all'); | ||||||
|  | 
 | ||||||
|  |           if (providerUri) { | ||||||
|  |             providerUri = OAUTH3.uri.normalize(providerUri); | ||||||
|  |           } | ||||||
|  |           return OAUTH3.PromiseA.resolve(OAUTH3._hooks.sessions.all(providerUri)); | ||||||
|  |         } | ||||||
|  |       , clear: function (providerUri) { | ||||||
|  |           OAUTH3.hooks._checkStorage('sessions', 'clear'); | ||||||
|  | 
 | ||||||
|  |           if (providerUri) { | ||||||
|  |             providerUri = OAUTH3.uri.normalize(providerUri); | ||||||
|  |           } | ||||||
|  |           return OAUTH3.PromiseA.resolve(OAUTH3._hooks.sessions.clear(providerUri)); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   , discoverScopes: function (providerUri, opts) { | ||||||
|  |       return OAUTH.scopes(providerUri, opts); | ||||||
|  |     } | ||||||
|  |   , scopes: function (providerUri, opts) { | ||||||
|  |       if (!providerUri) { | ||||||
|  |         throw new Error('oauth3.discoverScopes(providerUri, opts) received providerUri as :', providerUri); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       opts = opts || {}; | ||||||
|  |       opts._pathname = ".well-known/oauth3/scopes.json"; | ||||||
|  | 
 | ||||||
|  |       //TODO: add caching
 | ||||||
|  | 
 | ||||||
|  |       return OAUTH3._rpcHelper(providerUri, opts).then(function(scopes) { | ||||||
|  |         return scopes; | ||||||
|       }); |       }); | ||||||
|     } |     } | ||||||
|       , _get: function (providerUri, id) { |  | ||||||
|           if (!OAUTH3._hooks || !OAUTH3._hooks.sessions || !OAUTH3._hooks.sessions.all) { |  | ||||||
|             console.warn('[Warn] Please implement OAUTH3._hooks.sessions.all = function ([providerUri]) { return PromiseA<sessions>; }'); |  | ||||||
|           } |  | ||||||
|           if (!OAUTH3._hooks || !OAUTH3._hooks.sessions || !OAUTH3._hooks.sessions.get) { |  | ||||||
|             console.warn('[Warn] Please implement OAUTH3._hooks.sessions.get = function (providerUri[, id]) { return PromiseA<session>; }'); |  | ||||||
|             return JSON.parse(window.sessionStorage.getItem('session-' + providerUri + (id || '')) || 'null'); |  | ||||||
|           } |  | ||||||
|           return OAUTH3._hooks.sessions.get(providerUri, id); |  | ||||||
|         } |  | ||||||
|       , _set: function (providerUri, newSession, id) { |  | ||||||
|           if (!OAUTH3._hooks || !OAUTH3._hooks.sessions || !OAUTH3._hooks.sessions.set) { |  | ||||||
|             console.warn('[Warn] Please implement OAUTH3._hooks.sessions.set = function (providerUri, newSession[, id]) { return PromiseA<newSession>; }'); |  | ||||||
|             window.sessionStorage.setItem('session-' + providerUri, JSON.stringify(newSession)); |  | ||||||
|             window.sessionStorage.setItem('session-' + providerUri + (id || newSession.id || newSession.token.id || ''), JSON.stringify(newSession)); |  | ||||||
|             return newSession; |  | ||||||
|           } |  | ||||||
|           return OAUTH3._hooks.sessions.set(providerUri, newSession, id); |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|   , discover: function (providerUri, opts) { |   , discover: function (providerUri, opts) { | ||||||
|  |       return OAUTH3.directives(providerUri, opts); | ||||||
|  |     } | ||||||
|  |   , directives: function (providerUri, opts) { | ||||||
|       if (!providerUri) { |       if (!providerUri) { | ||||||
|         throw new Error('oauth3.discover(providerUri, opts) received providerUri as ' + providerUri); |         throw new Error('oauth3.discover(providerUri, opts) received providerUri as :', providerUri); | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       return OAUTH3.hooks.directives.get(providerUri).then(function (directives) { |       return OAUTH3.hooks.directives.get(providerUri).then(function (directives) { | ||||||
| @ -628,7 +708,8 @@ | |||||||
|           return directives; |           return directives; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return OAUTH3._discoverHelper(providerUri, opts).then(function (directives) { |         opts._pathname = ".well-known/oauth3/directives.json"; | ||||||
|  |         return OAUTH3._rpcHelper(providerUri, opts).then(function (directives) { | ||||||
|           directives.azp = directives.azp || OAUTH3.url.normalize(providerUri); |           directives.azp = directives.azp || OAUTH3.url.normalize(providerUri); | ||||||
|           directives.issuer = directives.issuer || OAUTH3.url.normalize(providerUri); |           directives.issuer = directives.issuer || OAUTH3.url.normalize(providerUri); | ||||||
|           directives.api = OAUTH3.url.normalize((directives.api||':hostname').replace(/:hostname/, OAUTH3.uri.normalize(directives.issuer) || OAUTH3.uri.normalize(providerUri))); |           directives.api = OAUTH3.url.normalize((directives.api||':hostname').replace(/:hostname/, OAUTH3.uri.normalize(directives.issuer) || OAUTH3.uri.normalize(providerUri))); | ||||||
| @ -637,8 +718,8 @@ | |||||||
|         }); |         }); | ||||||
|       }); |       }); | ||||||
|     } |     } | ||||||
|   , _discoverHelper: function(providerUri, opts) { |   , _rpcHelper: function(providerUri, opts) { | ||||||
|       return OAUTH3._browser.discover(providerUri, opts); |       return OAUTH3._browser.rpc(providerUri, opts); | ||||||
|     } |     } | ||||||
|   , request: function (preq, opts) { |   , request: function (preq, opts) { | ||||||
|       function fetch() { |       function fetch() { | ||||||
| @ -666,6 +747,21 @@ | |||||||
|       */ |       */ | ||||||
|       return OAUTH3._browser.request(preq, opts); |       return OAUTH3._browser.request(preq, opts); | ||||||
|     } |     } | ||||||
|  |   , issuer: function (opts) { | ||||||
|  |       if (!opts) { opts = {}; } | ||||||
|  | 
 | ||||||
|  |       // TODO this will default to browserlogin.org
 | ||||||
|  |       var broker = opts.broker || 'https://new.oauth3.org'; | ||||||
|  |       //var broker = opts.broker || 'https://broker.oauth3.org';
 | ||||||
|  | 
 | ||||||
|  |       opts._rpc = "broker"; | ||||||
|  |       opts._scheme = "localstorage:"; | ||||||
|  |       opts._pathname = "issuer"; | ||||||
|  | 
 | ||||||
|  |       return OAUTH3._rpcHelper(broker, opts).then(function(issuer) { | ||||||
|  |         return issuer; | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|   , implicitGrant: function (directives, opts) { |   , implicitGrant: function (directives, opts) { | ||||||
|       var promise; |       var promise; | ||||||
|       var providerUri = directives.azp || directives.issuer || directives; |       var providerUri = directives.azp || directives.issuer || directives; | ||||||
| @ -677,7 +773,9 @@ | |||||||
|       } |       } | ||||||
|       else { |       else { | ||||||
|         // Discovery must take place before calling implicitGrant
 |         // Discovery must take place before calling implicitGrant
 | ||||||
|         promise = OAUTH3._implicitGrant(OAUTH3.hooks.directives._getCached(providerUri), opts); |         promise = OAUTH3.hooks.directives.get(providerUri).then(function (directives) { | ||||||
|  |           return OAUTH3._implicitGrant(directives, opts); | ||||||
|  |         }); | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       return promise.then(function (tokens) { |       return promise.then(function (tokens) { | ||||||
| @ -774,14 +872,23 @@ | |||||||
|         }); |         }); | ||||||
|       }); |       }); | ||||||
|     } |     } | ||||||
|   , logout: function(providerUri, opts) { |   , logout: function(issuerUri, opts) { | ||||||
|       return OAUTH3._logoutHelper(OAUTH3.hooks.directives._getCached(providerUri), opts); |       var directives; | ||||||
|  |       if ('string' !== typeof issuerUri) { | ||||||
|  |         directives = issuerUri; | ||||||
|  |         return OAUTH3._logoutHelper(directives, opts); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       return OAUTH3.hooks.directives.get(issuerUri).then(function (directives) { | ||||||
|  |         return OAUTH3._logoutHelper(directives, opts); | ||||||
|  |       }); | ||||||
|     } |     } | ||||||
|   , _logoutHelper: function(directives, opts) { |   , _logoutHelper: function(directives, opts) { | ||||||
|  |       var issuerUri = directives.issuer_uri || directives.provider_uri; | ||||||
|       var logoutReq = OAUTH3.urls.logout( |       var logoutReq = OAUTH3.urls.logout( | ||||||
|         directives |         directives | ||||||
|       , { client_id: (opts.client_id || opts.client_uri || OAUTH3.clientUri(OAUTH3._browser.window.location)) |       , { client_id: (opts.client_id || opts.client_uri || OAUTH3.clientUri(OAUTH3._browser.window.location)) | ||||||
|         , windowType: 'popup' // we'll figure out background later
 |         , windowType: 'popup' // TODO: figure out background later
 | ||||||
|         , broker: opts.broker |         , broker: opts.broker | ||||||
|         //, state: opts._state
 |         //, state: opts._state
 | ||||||
|         , debug: opts.debug |         , debug: opts.debug | ||||||
| @ -800,10 +907,10 @@ | |||||||
| 
 | 
 | ||||||
|         if (params.error) { |         if (params.error) { | ||||||
|           // TODO directives.audience
 |           // TODO directives.audience
 | ||||||
|           return OAUTH3.PromiseA.reject(OAUTH3.error.parse(directives.issuer /*providerUri*/, params)); |           return OAUTH3.PromiseA.reject(OAUTH3.error.parse(directives.issuer /*issuerUri*/, params)); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         OAUTH3.hooks.session._cache = {}; |         OAUTH3.hooks.session.clear(issuerUri); | ||||||
|         return params; |         return params; | ||||||
|       }); |       }); | ||||||
|     } |     } | ||||||
| @ -814,40 +921,50 @@ | |||||||
|     //
 |     //
 | ||||||
|   , _browser: { |   , _browser: { | ||||||
|       window: 'undefined' !== typeof window ? window : null |       window: 'undefined' !== typeof window ? window : null | ||||||
|       // TODO we don't need to include this if we're using jQuery or angular
 |     , rpc: function(providerUri, opts) { | ||||||
|     , discover: function(providerUri, opts) { |  | ||||||
|         opts = opts || {}; |         opts = opts || {}; | ||||||
|         providerUri = OAUTH3.url.normalize(providerUri); |         providerUri = OAUTH3.url.normalize(providerUri); | ||||||
| 
 | 
 | ||||||
|  |         // TODO SECURITY should we whitelist our own self?
 | ||||||
|         if (OAUTH3.uri.normalize(providerUri).replace(/\/.*/, '') === OAUTH3.uri.normalize(OAUTH3._browser.window.location.hostname)) { |         if (OAUTH3.uri.normalize(providerUri).replace(/\/.*/, '') === OAUTH3.uri.normalize(OAUTH3._browser.window.location.hostname)) { | ||||||
|           console.warn("It looks like you're a provider checking for your own directive," |           console.warn("It looks like you're a provider trying to run rpc on yourself," | ||||||
|             + " so we we're just gonna use" |             + " so we we're just gonna use" | ||||||
|             + " OAUTH3.request({ method: 'GET', url: '.well-known/oauth3/directive.json' })"); |             + " OAUTH3.request({ method: 'GET', url: " | ||||||
|  |             + "'" + opts._pathname + "' })"); | ||||||
|  | 
 | ||||||
|  |           if (/localstorage/i.test(opts._scheme)) { | ||||||
|  |             return OAUTH3.PromiseA.resolve(localStorage.getItem(opts._pathname)); | ||||||
|  |           } | ||||||
|  |           else { | ||||||
|             return OAUTH3.request({ |             return OAUTH3.request({ | ||||||
|               method: 'GET' |               method: 'GET' | ||||||
|           , url: OAUTH3.url.normalize(providerUri) + '/.well-known/oauth3/directives.json' |             , url: OAUTH3.url.normalize(providerUri) + '/' + opts._pathname // '/.well-known/oauth3/' + discoverFile
 | ||||||
|             }).then(function (resp) { |             }).then(function (resp) { | ||||||
|               return resp.data; |               return resp.data; | ||||||
|             }); |             }); | ||||||
|           } |           } | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|         if (!(opts.client_id || opts.client_uri).match(OAUTH3._browser.window.location.hostname)) { |         if (!(opts.client_id || opts.client_uri || '').match(OAUTH3._browser.window.location.hostname)) { | ||||||
|           console.warn("It looks like your client_id doesn't match your current window..." |           console.warn("It looks like your client_id doesn't match your current window..." | ||||||
|             + " this probably won't end well"); |             + " this probably won't end well"); | ||||||
|           console.warn(opts.client_id || opts.client_uri, OAUTH3._browser.window.location.hostname); |           console.warn(opts.client_id || opts.client_uri, OAUTH3._browser.window.location.hostname); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         var discReq = OAUTH3.urls.discover( |         var discReq = OAUTH3.urls[opts._rpc || 'rpc']( | ||||||
|           providerUri |           providerUri | ||||||
|         , { client_id: (opts.client_id || opts.client_uri || OAUTH3.clientUri(OAUTH3._browser.window.location)) |         , { client_id: (opts.client_id || opts.client_uri || OAUTH3.clientUri(OAUTH3._browser.window.location)) | ||||||
|           , windowType: opts.broker && opts.windowType || 'background' |           , windowType: opts.broker && opts.windowType || 'background' | ||||||
|           , broker: opts.broker |           , broker: opts.broker | ||||||
|           , state: opts._state || undefined |           , state: opts._state || undefined | ||||||
|           , debug: opts.debug |           , debug: opts.debug | ||||||
|  |           , _scheme: opts._scheme | ||||||
|  |           , _pathname: opts._pathname | ||||||
|  |           , _method: opts._method | ||||||
|           } |           } | ||||||
|         ); |         ); | ||||||
|         opts._state = discReq.state; |         opts._state = discReq.state; | ||||||
|         //var discReq = OAUTH3.urls.discover(providerUri, opts);
 |         //var discReq = OAUTH3.urls.rpc(providerUri, opts);
 | ||||||
| 
 | 
 | ||||||
|         // hmm... we're gonna need a broker for this since switching windows is distracting,
 |         // 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
 |         // popups are obnoxious, iframes are sometimes blocked, and most servers don't implement CORS
 | ||||||
| @ -857,6 +974,7 @@ | |||||||
|         // TODO allow node to open a desktop browser window
 |         // TODO allow node to open a desktop browser window
 | ||||||
|         opts._windowType = opts.windowType; |         opts._windowType = opts.windowType; | ||||||
|         opts.windowType = opts.windowType || 'background'; |         opts.windowType = opts.windowType || 'background'; | ||||||
|  |         return OAUTH3._browser.testPixel(providerUri).then(function () { | ||||||
|           return OAUTH3._browser.frameRequest( |           return OAUTH3._browser.frameRequest( | ||||||
|             OAUTH3.url.resolve(providerUri, discReq.url) |             OAUTH3.url.resolve(providerUri, discReq.url) | ||||||
|           , discReq.state |           , discReq.state | ||||||
| @ -875,9 +993,17 @@ | |||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             // TODO params should have response_type indicating json, binary, etc
 |             // TODO params should have response_type indicating json, binary, etc
 | ||||||
|           var directives = JSON.parse(OAUTH3._base64.decodeUrlSafe(params.result || params.directives)); |             var result; | ||||||
|  |             try { | ||||||
|  |               result = JSON.parse(OAUTH3._base64.decodeUrlSafe(params.data || params.result || params.directives)); | ||||||
|  |             } catch(e) { | ||||||
|  |               result = params.data || params.result; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             console.log('result:', result); | ||||||
|             // caller will call OAUTH3.hooks.directives.set(providerUri, directives);
 |             // caller will call OAUTH3.hooks.directives.set(providerUri, directives);
 | ||||||
|           return directives; |             return result; | ||||||
|  |           }); | ||||||
|         }); |         }); | ||||||
|       } |       } | ||||||
|     , request: function (preq, _sys) { |     , request: function (preq, _sys) { | ||||||
| @ -910,9 +1036,12 @@ | |||||||
| 
 | 
 | ||||||
|             if (data.error) { |             if (data.error) { | ||||||
|               err = new Error(data.error.message || data.error_description || JSON.stringify(data.error)); |               err = new Error(data.error.message || data.error_description || JSON.stringify(data.error)); | ||||||
|  |               Object.assign(err, data.error); | ||||||
|             } |             } | ||||||
|             if (err) { |             if (err) { | ||||||
|               err.result = data; |               err._request = xhr; | ||||||
|  |               err.status = xhr.status; | ||||||
|  |               err.data = data; | ||||||
|               reject(err); |               reject(err); | ||||||
|               return; |               return; | ||||||
|             } |             } | ||||||
| @ -924,6 +1053,11 @@ | |||||||
|             , status: xhr.status |             , status: xhr.status | ||||||
|             }); |             }); | ||||||
|           }; |           }; | ||||||
|  |           xhr.ontimeout = function () { | ||||||
|  |             var err = new Error('ETIMEDOUT'); | ||||||
|  |             err.code = 'ETIMEDOUT'; | ||||||
|  |             reject(err); | ||||||
|  |           }; | ||||||
| 
 | 
 | ||||||
|           if (preq.progress) { |           if (preq.progress) { | ||||||
|             xhr.upload.onprogress = function (ev) { |             xhr.upload.onprogress = function (ev) { | ||||||
| @ -941,6 +1075,9 @@ | |||||||
|           // For assets.example.com/assets
 |           // For assets.example.com/assets
 | ||||||
|           xhr.withCredentials = true; |           xhr.withCredentials = true; | ||||||
| 
 | 
 | ||||||
|  |           if (preq.timeout) { | ||||||
|  |             xhr.timeout = preq.timeout; | ||||||
|  |           } | ||||||
|           if (preq.data) { |           if (preq.data) { | ||||||
|             headers['Content-Type'] = 'application/json'; // TODO XXX TODO utf8
 |             headers['Content-Type'] = 'application/json'; // TODO XXX TODO utf8
 | ||||||
|           } |           } | ||||||
| @ -965,6 +1102,28 @@ | |||||||
|           } |           } | ||||||
|         }); |         }); | ||||||
|       } |       } | ||||||
|  |     , testPixel: function (targetUri) { | ||||||
|  |         var url = OAUTH3.url.resolve(OAUTH3.url.normalize(targetUri), '.well-known/oauth3/clear.gif'); | ||||||
|  |         return new OAUTH3.PromiseA(function (resolve, reject) { | ||||||
|  |           var img = document.createElement('img'); | ||||||
|  |           img.addEventListener('load', function () { | ||||||
|  |             resolve(); | ||||||
|  |           }); | ||||||
|  |           img.addEventListener('error', function () { | ||||||
|  |             var err = new Error("OAuth3 support not detected: '" + url + "' not found"); | ||||||
|  |             err.code = 'E_NOT_SUPPORTED'; | ||||||
|  |             reject(err); | ||||||
|  |           }); | ||||||
|  |           // works with CSP
 | ||||||
|  |           img.style.position = 'absolute'; | ||||||
|  |           img.style.left = '-2px'; | ||||||
|  |           img.style.bottom = '-2px'; | ||||||
|  |           img.className = 'js-oauth3-discover'; | ||||||
|  |           img.src = url; | ||||||
|  |           document.body.appendChild(img); | ||||||
|  |           console.log('img', img); | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|     , frameRequest: function (url, state, opts) { |     , frameRequest: function (url, state, opts) { | ||||||
|         opts = opts || {}; |         opts = opts || {}; | ||||||
|         var previousFrame = OAUTH3._browser._frames[state]; |         var previousFrame = OAUTH3._browser._frames[state]; | ||||||
| @ -975,11 +1134,10 @@ | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         var timeout = opts.timeout; |         var timeout = opts.timeout; | ||||||
|         if (opts.debug) { |         if ('background' === windowType) { | ||||||
|           timeout = timeout || 3 * 60 * 1000; |           if (!timeout) { | ||||||
|  |             timeout = 7 * 1000; | ||||||
|           } |           } | ||||||
|         else { |  | ||||||
|           timeout = timeout || ('background' === windowType ? 15 * 1000 : 3 * 60 * 1000); |  | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return new OAUTH3.PromiseA(function (resolve, reject) { |         return new OAUTH3.PromiseA(function (resolve, reject) { | ||||||
| @ -1001,6 +1159,7 @@ | |||||||
|             cleanup(); |             cleanup(); | ||||||
|           }; |           }; | ||||||
| 
 | 
 | ||||||
|  |           if (timeout) { | ||||||
|             tok = setTimeout(function () { |             tok = setTimeout(function () { | ||||||
|               var err = new Error( |               var err = new Error( | ||||||
|                 "the '" + windowType + "' request did not complete within " + Math.round(timeout / 1000) + "s" |                 "the '" + windowType + "' request did not complete within " + Math.round(timeout / 1000) + "s" | ||||||
| @ -1009,6 +1168,7 @@ | |||||||
|               reject(err); |               reject(err); | ||||||
|               cleanup(); |               cleanup(); | ||||||
|             }, timeout); |             }, timeout); | ||||||
|  |           } | ||||||
| 
 | 
 | ||||||
|           setTimeout(function () { |           setTimeout(function () { | ||||||
|             if (!OAUTH3._browser._frames[state]) { |             if (!OAUTH3._browser._frames[state]) { | ||||||
| @ -1131,10 +1291,103 @@ | |||||||
|   }; |   }; | ||||||
|   OAUTH3.login = OAUTH3.implicitGrant; |   OAUTH3.login = OAUTH3.implicitGrant; | ||||||
| 
 | 
 | ||||||
|  |   OAUTH3._defaultStorage = { | ||||||
|  |     _getStorageKeys: function (prefix, storage) { | ||||||
|  |       storage = storage || window.localStorage; | ||||||
|  |       var matching = []; | ||||||
|  |       var ind, key; | ||||||
|  |       for (ind = 0; ind < storage.length; ind++) { | ||||||
|  |         key = storage.key(ind); | ||||||
|  |         if (key.indexOf(prefix || '') === 0) { | ||||||
|  |           matching.push(key); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       return matching; | ||||||
|  |     } | ||||||
|  |   , directives: { | ||||||
|  |       prefix: 'directives-' | ||||||
|  |     , get: function (providerUri) { | ||||||
|  |         var result = JSON.parse(window.localStorage.getItem(this.prefix + providerUri) || '{}'); | ||||||
|  |         return OAUTH3.PromiseA.resolve(result); | ||||||
|  |       } | ||||||
|  |     , set: function (providerUri, directives) { | ||||||
|  |         window.localStorage.setItem(this.prefix + providerUri, JSON.stringify(directives)); | ||||||
|  |         return this.get(providerUri); | ||||||
|  |       } | ||||||
|  |     , all: function () { | ||||||
|  |         var prefix = this.prefix; | ||||||
|  |         var result = {}; | ||||||
|  |         OAUTH3._defaultStorage._getStorageKeys(prefix).forEach(function (key) { | ||||||
|  |           result[key.replace(prefix, '')] = JSON.parse(window.localStorage.getItem(key) || '{}'); | ||||||
|  |         }); | ||||||
|  |         return OAUTH3.PromiseA.resolve(result); | ||||||
|  |       } | ||||||
|  |     , clear: function () { | ||||||
|  |         OAUTH3._defaultStorage._getStorageKeys(this.prefix).forEach(function (key) { | ||||||
|  |           window.localStorage.removeItem(key); | ||||||
|  |         }); | ||||||
|  |         return OAUTH3.PromiseA.resolve(); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   , sessions: { | ||||||
|  |       prefix: 'session-' | ||||||
|  |     , get: function (providerUri, id) { | ||||||
|  |         var result; | ||||||
|  |         if (id) { | ||||||
|  |           result = JSON.parse(window.sessionStorage.getItem(this.prefix + providerUri+id) || 'null'); | ||||||
|  |         } else { | ||||||
|  |           result = JSON.parse(window.sessionStorage.getItem(this.prefix + providerUri) || 'null'); | ||||||
|  |         } | ||||||
|  |         return OAUTH3.PromiseA.resolve(result); | ||||||
|  |       } | ||||||
|  |     , set: function (providerUri, newSession, id) { | ||||||
|  |         var str = JSON.stringify(newSession); | ||||||
|  |         window.sessionStorage.setItem(this.prefix + providerUri, str); | ||||||
|  |         id = id || newSession.id || newSession.token.sub || newSession.token.id; | ||||||
|  |         if (id) { | ||||||
|  |           window.sessionStorage.setItem(this.prefix + providerUri + id, str); | ||||||
|  |         } | ||||||
|  |         return this.get(providerUri, id); | ||||||
|  |       } | ||||||
|  |     , all: function (providerUri) { | ||||||
|  |         var prefix = this.prefix + (providerUri || ''); | ||||||
|  |         var result = {}; | ||||||
|  |         OAUTH3._defaultStorage._getStorageKeys(prefix, window.sessionStorage).forEach(function (key) { | ||||||
|  |           result[key.replace(prefix, '')] = JSON.parse(window.sessionStorage.getItem(key) || 'null'); | ||||||
|  |         }); | ||||||
|  |         return OAUTH3.PromiseA.resolve(result); | ||||||
|  |       } | ||||||
|  |     , clear: function (providerUri) { | ||||||
|  |         var prefix = this.prefix + (providerUri || ''); | ||||||
|  |         OAUTH3._defaultStorage._getStorageKeys(prefix, window.sessionStorage).forEach(function (key) { | ||||||
|  |           window.sessionStorage.removeItem(key); | ||||||
|  |         }); | ||||||
|  |         return OAUTH3.PromiseA.resolve(); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|   // TODO get rid of these
 |   // TODO get rid of these
 | ||||||
|   OAUTH3.utils = { |   OAUTH3.utils = { | ||||||
|     clientUri: OAUTH3.clientUri |     clientUri: OAUTH3.clientUri | ||||||
|   , query: OAUTH3.query |   , query: OAUTH3.query | ||||||
|  |   , parseSubject: function (sub) { | ||||||
|  |       var parts = sub.split('@'); | ||||||
|  |       var issuer; | ||||||
|  |       var subject; | ||||||
|  | 
 | ||||||
|  |       if (/@/.test(sub)) { | ||||||
|  |         // The username may have a single @, the provider may not
 | ||||||
|  |         // user@thing.com@whatever.com -> user@thing.com, whatever.com
 | ||||||
|  |         issuer = parts.pop(); | ||||||
|  |         subject = parts.join('@'); | ||||||
|  |       } else { | ||||||
|  |         //subject = '';
 | ||||||
|  |         issuer = parts.join('@'); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       return { subject: subject, issuer: issuer }; | ||||||
|  |     } | ||||||
|   , scope: OAUTH3.scope |   , scope: OAUTH3.scope | ||||||
|   , uri: OAUTH3.uri |   , uri: OAUTH3.uri | ||||||
|   , url: OAUTH3.url |   , url: OAUTH3.url | ||||||
| @ -1162,7 +1415,7 @@ | |||||||
|     , _resourceProviderUri: null |     , _resourceProviderUri: null | ||||||
|     , _identityProviderDirectives: null |     , _identityProviderDirectives: null | ||||||
|     , _resourceProviderDirectives: null |     , _resourceProviderDirectives: null | ||||||
|     //, _resourceProviderMap: null // map between xyz.com and org.oauth3.domains
 |     //, _resourceProviderMap: null // map between xyz.com and domains@oauth3.org
 | ||||||
|     , _init: function (location, opts) { |     , _init: function (location, opts) { | ||||||
|         var me = this; |         var me = this; | ||||||
|         if (!opts) { |         if (!opts) { | ||||||
| @ -1198,7 +1451,7 @@ | |||||||
|           } |           } | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|     , _initClient: function (location/*, opts*/) { |     , _initClient: function () { | ||||||
|         var me = this; |         var me = this; | ||||||
|         return OAUTH3.discover(me._clientUri, { client_id: me._clientUri }).then(function (clientDirectives) { |         return OAUTH3.discover(me._clientUri, { client_id: me._clientUri }).then(function (clientDirectives) { | ||||||
|           me._clientDirectives = clientDirectives; |           me._clientDirectives = clientDirectives; | ||||||
| @ -1231,7 +1484,7 @@ | |||||||
|         var me = this; |         var me = this; | ||||||
|         return me._initClient().then(function () { |         return me._initClient().then(function () { | ||||||
|           return me.setIdentityProvider(providerUri).then(function () { |           return me.setIdentityProvider(providerUri).then(function () { | ||||||
|             // TODO how to say "Use xyz.com for org.oauth3.domains, but abc.com for org.oauth3.dns"?
 |             // TODO how to say "Use xyz.com for domains@oauth3.org, but abc.com for dns@oauth3.org"?
 | ||||||
|             return me.setResourceProvider(providerUri); |             return me.setResourceProvider(providerUri); | ||||||
|           }); |           }); | ||||||
|         }); |         }); | ||||||
| @ -1263,9 +1516,10 @@ | |||||||
|       } |       } | ||||||
|     , login: function (opts) { |     , login: function (opts) { | ||||||
|         var me = this; |         var me = this; | ||||||
|         if (me.session()) { |         return OAUTH3.hooks.session.get(me._identityProviderUri).then(function (session) { | ||||||
|  |           if (session) { | ||||||
|             me._session = true; |             me._session = true; | ||||||
|           return OAUTH3.PromiseA.resolve(me.session()); |             return session; | ||||||
|           } |           } | ||||||
| 
 | 
 | ||||||
|           opts = opts || {}; |           opts = opts || {}; | ||||||
| @ -1275,10 +1529,11 @@ | |||||||
|             me._session = true; |             me._session = true; | ||||||
|             return session; |             return session; | ||||||
|           }); |           }); | ||||||
|  |         }); | ||||||
|       } |       } | ||||||
|     , session: function (session, id) { |     , session: function (session, id) { | ||||||
|         if (!session) { |         if (!session) { | ||||||
|           return JSON.parse(JSON.stringify(OAUTH3.hooks.session._getCached(this._identityProviderUri) || null)); |           return OAUTH3.hooks.session.get(this._identityProviderUri); | ||||||
|         } |         } | ||||||
|         return OAUTH3.hooks.session.set(this._identityProviderUri, session, id); |         return OAUTH3.hooks.session.set(this._identityProviderUri, session, id); | ||||||
|       } |       } | ||||||
| @ -1287,29 +1542,39 @@ | |||||||
|         preq.client_uri = this._clientUri; |         preq.client_uri = this._clientUri; | ||||||
|         preq.client_id = this._clientUri; |         preq.client_id = this._clientUri; | ||||||
|         preq.method = preq.method || 'GET'; |         preq.method = preq.method || 'GET'; | ||||||
|         if (this._session) { |  | ||||||
|           preq.session = preq.session || this.session(); // OAUTH3.hooks.session._getCached(this._identityProviderUri);
 |  | ||||||
|         } |  | ||||||
|         // TODO maybe use a baseUrl from the directives file?
 |         // TODO maybe use a baseUrl from the directives file?
 | ||||||
|         preq.url = OAUTH3.url.resolve(this._resourceProviderUri, preq.url); |         preq.url = OAUTH3.url.resolve(this._resourceProviderUri, preq.url); | ||||||
| 
 | 
 | ||||||
|  |         if (preq.session || !this._session) { | ||||||
|           return OAUTH3.request(preq, opts); |           return OAUTH3.request(preq, opts); | ||||||
|         } |         } | ||||||
|     , logout: function (opts) { |  | ||||||
|         this._session = false; |  | ||||||
|         opts = opts || {}; |  | ||||||
|         opts.client_uri = this._clientUri; |  | ||||||
|         opts.client_id = this._clientUri; |  | ||||||
|         opts.session = OAUTH3.hooks.session._getCached(this._identityProviderUri); |  | ||||||
| 
 | 
 | ||||||
|         return OAUTH3.logout(this._identityProviderUri, opts); |         return this.session().then(function (session) { | ||||||
|  |           preq.session = session; | ||||||
|  |           return OAUTH3.request(preq, opts); | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|  |     , logout: function (opts) { | ||||||
|  |         var me = this; | ||||||
|  |         me._session = false; | ||||||
|  |         opts = opts || {}; | ||||||
|  |         return OAUTH3.hooks.session.get(me._identityProviderUri).then(function (session) { | ||||||
|  |           opts.client_uri = me._clientUri; | ||||||
|  |           opts.client_id = me._clientUri; | ||||||
|  |           opts.session = session; | ||||||
|  | 
 | ||||||
|  |           return OAUTH3.logout(me._identityProviderUri, opts); | ||||||
|  |         }); | ||||||
|       } |       } | ||||||
|     , api: function (api, opts) { |     , api: function (api, opts) { | ||||||
|  |         var me = this; | ||||||
|         opts = opts || {}; |         opts = opts || {}; | ||||||
|  |         return OAUTH3.hooks.session.get(me._identityProviderUri).then(function (session) { | ||||||
|           opts.api = api; |           opts.api = api; | ||||||
|         opts.session = OAUTH3.hooks.session._getCached(this._identityProviderUri); |           opts.session = session; | ||||||
| 
 | 
 | ||||||
|         return OAUTH3.api(this._resourceProviderDirectives.api, opts); |           return OAUTH3.api(me._resourceProviderDirectives.api, opts); | ||||||
|  |         }); | ||||||
|       } |       } | ||||||
|     , pkg: function (pkgname) { |     , pkg: function (pkgname) { | ||||||
|         var me = this; |         var me = this; | ||||||
| @ -1323,17 +1588,19 @@ | |||||||
|           return OAUTH3.PromiseA.reject(new Error("No Package for '" + pkgname + "'")); |           return OAUTH3.PromiseA.reject(new Error("No Package for '" + pkgname + "'")); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |         return OAUTH3.hooks.session.get(issuer).then(function (session) { | ||||||
|           pkg = OAUTH3._pkgs[pkgname]; |           pkg = OAUTH3._pkgs[pkgname]; | ||||||
|           Object.keys(pkg).forEach(function (key) { |           Object.keys(pkg).forEach(function (key) { | ||||||
|             result[key] = function (opts) { |             result[key] = function (opts) { | ||||||
|               opts = opts || {}; |               opts = opts || {}; | ||||||
|             opts.session = OAUTH3.hooks.session._getCached(issuer); |               opts.session = session; | ||||||
|               opts.audience = audience; |               opts.audience = audience; | ||||||
|               return pkg[key](opts); |               return pkg[key](opts); | ||||||
|             }; |             }; | ||||||
|           }); |           }); | ||||||
| 
 | 
 | ||||||
|           return result; |           return result; | ||||||
|  |         }); | ||||||
|       } |       } | ||||||
|     }; |     }; | ||||||
|     result.setIssuer = result.setIdentityProvider; |     result.setIssuer = result.setIdentityProvider; | ||||||
|  | |||||||
							
								
								
									
										150
									
								
								oauth3.crypto.js
									
									
									
									
									
								
							
							
						
						
									
										150
									
								
								oauth3.crypto.js
									
									
									
									
									
								
							| @ -8,6 +8,9 @@ | |||||||
|     OAUTH3.crypto.core = require('./oauth3.node.crypto'); |     OAUTH3.crypto.core = require('./oauth3.node.crypto'); | ||||||
|   } catch (error) { |   } catch (error) { | ||||||
|     OAUTH3.crypto.core = {}; |     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
 |     // 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.
 |     // it directly to the core object instead of the webCrypto object.
 | ||||||
| @ -17,10 +20,31 @@ | |||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     var webCrypto = {}; |     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) { |     webCrypto.sha256 = function (buf) { | ||||||
|       return OAUTH3._browser.window.crypto.subtle.digest({name: 'SHA-256'}, buf); |       return OAUTH3._browser.window.crypto.subtle.digest({name: 'SHA-256'}, buf); | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|  |     OAUTH3.crypto.core.pbkdf2 = deferCryptoCall("pbkdf2"); | ||||||
|     webCrypto.pbkdf2 = function (password, salt) { |     webCrypto.pbkdf2 = function (password, salt) { | ||||||
|       return OAUTH3._browser.window.crypto.subtle.importKey('raw', OAUTH3._binStr.binStrToBuffer(password), {name: 'PBKDF2'}, false, ['deriveKey']) |       return OAUTH3._browser.window.crypto.subtle.importKey('raw', OAUTH3._binStr.binStrToBuffer(password), {name: 'PBKDF2'}, false, ['deriveKey']) | ||||||
|         .then(function (key) { |         .then(function (key) { | ||||||
| @ -32,12 +56,15 @@ | |||||||
|         }); |         }); | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|  |     OAUTH3.crypto.core.encrypt = deferCryptoCall("encrypt"); | ||||||
|     webCrypto.encrypt = function (rawKey, iv, data) { |     webCrypto.encrypt = function (rawKey, iv, data) { | ||||||
|       return OAUTH3._browser.window.crypto.subtle.importKey('raw', rawKey, {name: 'AES-GCM'}, false, ['encrypt']) |       return OAUTH3._browser.window.crypto.subtle.importKey('raw', rawKey, {name: 'AES-GCM'}, false, ['encrypt']) | ||||||
|         .then(function (key) { |         .then(function (key) { | ||||||
|           return OAUTH3._browser.window.crypto.subtle.encrypt({name: 'AES-GCM', iv: iv}, key, data); |           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) { |     webCrypto.decrypt = function (rawKey, iv, data) { | ||||||
|       return OAUTH3._browser.window.crypto.subtle.importKey('raw', rawKey, {name: 'AES-GCM'}, false, ['decrypt']) |       return OAUTH3._browser.window.crypto.subtle.importKey('raw', rawKey, {name: 'AES-GCM'}, false, ['decrypt']) | ||||||
|         .then(function (key) { |         .then(function (key) { | ||||||
| @ -45,6 +72,7 @@ | |||||||
|         }); |         }); | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|  |     OAUTH3.crypto.core.genEcdsaKeyPair = deferCryptoCall("genEcdsaKeyPair"); | ||||||
|     webCrypto.genEcdsaKeyPair = function () { |     webCrypto.genEcdsaKeyPair = function () { | ||||||
|       return OAUTH3._browser.window.crypto.subtle.generateKey({name: 'ECDSA', namedCurve: 'P-256'}, true, ['sign', 'verify']) |       return OAUTH3._browser.window.crypto.subtle.generateKey({name: 'ECDSA', namedCurve: 'P-256'}, true, ['sign', 'verify']) | ||||||
|         .then(function (keyPair) { |         .then(function (keyPair) { | ||||||
| @ -57,6 +85,7 @@ | |||||||
|         }); |         }); | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|  |     OAUTH3.crypto.core.sign = deferCryptoCall("sign"); | ||||||
|     webCrypto.sign = function (jwk, msg) { |     webCrypto.sign = function (jwk, msg) { | ||||||
|       return OAUTH3._browser.window.crypto.subtle.importKey('jwk', jwk, {name: 'ECDSA', namedCurve: jwk.crv}, false, ['sign']) |       return OAUTH3._browser.window.crypto.subtle.importKey('jwk', jwk, {name: 'ECDSA', namedCurve: jwk.crv}, false, ['sign']) | ||||||
|         .then(function (key) { |         .then(function (key) { | ||||||
| @ -66,6 +95,8 @@ | |||||||
|           return new Uint8Array(sig); |           return new Uint8Array(sig); | ||||||
|         }); |         }); | ||||||
|     }; |     }; | ||||||
|  |      | ||||||
|  |     OAUTH3.crypto.core.verify = deferCryptoCall("verify"); | ||||||
|     webCrypto.verify = function (jwk, msg, signature) { |     webCrypto.verify = function (jwk, msg, signature) { | ||||||
|       // If the JWK has properties that should only exist on the private key or is missing
 |       // 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.
 |       // "verify" in the key_ops, importing in as a public key won't work.
 | ||||||
| @ -82,6 +113,7 @@ | |||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     function checkWebCrypto() { |     function checkWebCrypto() { | ||||||
|  |       /* global OAUTH3_crypto_fallback */ | ||||||
|       var loadFallback = function() { |       var loadFallback = function() { | ||||||
|         var prom; |         var prom; | ||||||
|         loadFallback = function () { return prom; }; |         loadFallback = function () { return prom; }; | ||||||
| @ -96,25 +128,25 @@ | |||||||
|               resolve(); |               resolve(); | ||||||
|             } |             } | ||||||
|           }; |           }; | ||||||
|           script.src = '/assets/org.oauth3/oauth3.crypto.fallback.js'; |           script.src = '/assets/oauth3.org/oauth3.crypto.fallback.js'; | ||||||
|           body.appendChild(script); |           body.appendChild(script); | ||||||
|         }); |         }); | ||||||
|         return prom; |         return prom; | ||||||
|       }; |       }; | ||||||
|       function checkException(name, func) { |       function checkException(name, func) { | ||||||
|         new OAUTH3.PromiseA(function (resolve) { resolve(func()); }) |         return OAUTH3.PromiseA.resolve().then(func) | ||||||
|           .then(function () { |           .then(function () { | ||||||
|             OAUTH3.crypto.core[name] = webCrypto[name]; |             OAUTH3.crypto.core[name] = webCrypto[name]; | ||||||
|           }) |           }, function (err) { | ||||||
|           .catch(function (err) { |  | ||||||
|             console.warn('error with WebCrypto', name, '- using fallback', 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]; |               OAUTH3.crypto.core[name] = OAUTH3_crypto_fallback[name]; | ||||||
|             }); |             }); | ||||||
|           }); |           }); | ||||||
|       } |       } | ||||||
|       function checkResult(name, expected, func) { |       function checkResult(name, expected, func) { | ||||||
|         checkException(name, function () { |          | ||||||
|  |         finishBeforeReady.push(checkException(name, function () { | ||||||
|           return func() |           return func() | ||||||
|             .then(function (result) { |             .then(function (result) { | ||||||
|               if (typeof expected === typeof result) { |               if (typeof expected === typeof result) { | ||||||
| @ -127,7 +159,7 @@ | |||||||
|                 throw new Error("result ("+result+") doesn't match expectation ("+expected+")"); |                 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]); |       var zeroBuf = new Uint8Array([0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]); | ||||||
| @ -159,11 +191,19 @@ | |||||||
|         return webCrypto.verify(jwk, dataBuf, sig); |         return webCrypto.verify(jwk, dataBuf, sig); | ||||||
|       }); |       }); | ||||||
|       // The results of these functions are less predictable, so we can't check their return value.
 |       // 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(); |         return webCrypto.genEcdsaKeyPair(); | ||||||
|       }); |       })); | ||||||
|       checkException('sign', function () { |       finishBeforeReady.push(checkException('sign', function () { | ||||||
|         return webCrypto.sign(jwk, dataBuf); |         return webCrypto.sign(jwk, dataBuf); | ||||||
|  |       })); | ||||||
|  |        | ||||||
|  |       OAUTH3.PromiseA.all(finishBeforeReady) | ||||||
|  |         .then(function(results) { | ||||||
|  |           OAUTH3.crypto.core.ready = true; | ||||||
|  |           deferedCalls.forEach(function(request) { | ||||||
|  |             request(); | ||||||
|  |           }); | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|     checkWebCrypto(); |     checkWebCrypto(); | ||||||
| @ -195,100 +235,60 @@ | |||||||
|       .then(OAUTH3._base64.bufferToUrlSafe); |       .then(OAUTH3._base64.bufferToUrlSafe); | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   OAUTH3.crypto._createKey = function (ppid) { |   OAUTH3.crypto.createKeyPair = function () { | ||||||
|     var saltProm = OAUTH3.crypto.core.randomBytes(16); |     // TODO: maybe support other types of key pairs, not just ECDSA P-256
 | ||||||
|     var kekProm = saltProm.then(function (salt) { |     return OAUTH3.crypto.core.genEcdsaKeyPair().then(function (keyPair) { | ||||||
|       return OAUTH3.crypto.core.pbkdf2(ppid, salt); |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|     var ecdsaProm = OAUTH3.crypto.core.genEcdsaKeyPair() |  | ||||||
|     .then(function (keyPair) { |  | ||||||
|       return OAUTH3.crypto.thumbprintJwk(keyPair.publicKey).then(function (kid) { |       return OAUTH3.crypto.thumbprintJwk(keyPair.publicKey).then(function (kid) { | ||||||
|         keyPair.privateKey.alg = keyPair.publicKey.alg = 'ES256'; |         keyPair.privateKey.alg = keyPair.publicKey.alg = 'ES256'; | ||||||
|         keyPair.privateKey.kid = keyPair.publicKey.kid = kid; |         keyPair.privateKey.kid = keyPair.publicKey.kid = kid; | ||||||
|         return keyPair; |         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([ |     return OAUTH3.PromiseA.all([ | ||||||
|       kekProm |       kekProm | ||||||
|     , ecdsaProm |  | ||||||
|     , saltProm |     , saltProm | ||||||
|     , OAUTH3.crypto.core.randomBytes(16) |  | ||||||
|     , OAUTH3.crypto.core.randomBytes(12) |     , OAUTH3.crypto.core.randomBytes(12) | ||||||
|     , OAUTH3.crypto.core.randomBytes(12) |   , ]).then(function (results) { | ||||||
|     ]).then(function (results) { |  | ||||||
|       var kek        = results[0]; |       var kek        = results[0]; | ||||||
|       var keyPair    = results[1]; |       var salt       = results[1]; | ||||||
|       var salt       = results[2]; |       var ecdsaIv    = results[2]; | ||||||
|       var userSecret = results[3]; |  | ||||||
|       var ecdsaIv    = results[4]; |  | ||||||
|       var secretIv   = results[5]; |  | ||||||
| 
 | 
 | ||||||
|       return OAUTH3.PromiseA.all([ |       var privKeyBuf = OAUTH3._binStr.binStrToBuffer(JSON.stringify(keyPair.privateKey)); | ||||||
|         OAUTH3.crypto.core.encrypt(kek, ecdsaIv, OAUTH3._binStr.binStrToBuffer(JSON.stringify(keyPair.privateKey))) |       return OAUTH3.crypto.core.encrypt(kek, ecdsaIv, privKeyBuf).then(function (encrypted) { | ||||||
|       , OAUTH3.crypto.core.encrypt(kek, secretIv, userSecret) |  | ||||||
|       ]) |  | ||||||
|       .then(function (encrypted) { |  | ||||||
|         return { |         return { | ||||||
|           publicKey:  keyPair.publicKey |           publicKey:  keyPair.publicKey | ||||||
|         , privateKey: OAUTH3._base64.bufferToUrlSafe(encrypted[0]) |         , privateKey: OAUTH3._base64.bufferToUrlSafe(encrypted) | ||||||
|         , userSecret: OAUTH3._base64.bufferToUrlSafe(encrypted[1]) |  | ||||||
|         , salt:       OAUTH3._base64.bufferToUrlSafe(salt) |         , salt:       OAUTH3._base64.bufferToUrlSafe(salt) | ||||||
|         , ecdsaIv:    OAUTH3._base64.bufferToUrlSafe(ecdsaIv) |         , 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 salt   = OAUTH3._base64.urlSafeToBuffer(storedObj.salt); | ||||||
|     var encJwk = OAUTH3._base64.urlSafeToBuffer(storedObj.privateKey); |     var encJwk = OAUTH3._base64.urlSafeToBuffer(storedObj.privateKey); | ||||||
|     var iv     = OAUTH3._base64.urlSafeToBuffer(storedObj.ecdsaIv); |     var iv     = OAUTH3._base64.urlSafeToBuffer(storedObj.ecdsaIv); | ||||||
| 
 | 
 | ||||||
|     return OAUTH3.crypto.core.pbkdf2(ppid, salt) |     return OAUTH3.crypto.core.pbkdf2(password, salt) | ||||||
|       .then(function (key) { |       .then(function (key) { | ||||||
|         return OAUTH3.crypto.core.decrypt(key, iv, encJwk); |         return OAUTH3.crypto.core.decrypt(key, iv, encJwk); | ||||||
|       }) |       }) | ||||||
|       .then(OAUTH3._binStr.bufferToBinStr) |       .then(OAUTH3._binStr.bufferToBinStr) | ||||||
|       .then(JSON.parse); |       .then(JSON.parse) | ||||||
|   }; |       .then(function (privateKey) { | ||||||
| 
 |         return { | ||||||
|   OAUTH3.crypto._getKey = function (ppid) { |           privateKey: privateKey | ||||||
|     return OAUTH3.crypto.core.sha256(OAUTH3._binStr.binStrToBuffer(ppid)) |         , publicKey:  storedObj.publicKey | ||||||
|     .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); |  | ||||||
|       }); |  | ||||||
|     }); |  | ||||||
|   }; |  | ||||||
| 
 |  | ||||||
|   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; |  | ||||||
|         }); |  | ||||||
|       }); |       }); | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										801
									
								
								oauth3.issuer.js
									
									
									
									
									
								
							
							
						
						
									
										801
									
								
								oauth3.issuer.js
									
									
									
									
									
								
							| @ -3,39 +3,6 @@ | |||||||
| 
 | 
 | ||||||
| var OAUTH3 = exports.OAUTH3 = exports.OAUTH3 || require('./oauth3.core.js').OAUTH3; | var OAUTH3 = exports.OAUTH3 = exports.OAUTH3 || require('./oauth3.core.js').OAUTH3; | ||||||
| 
 | 
 | ||||||
| OAUTH3.query.parse = 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; |  | ||||||
| }; |  | ||||||
| OAUTH3.scope.parse = function (scope) { |  | ||||||
|   return (scope||'').split(/[, ]/g); |  | ||||||
| }; |  | ||||||
| OAUTH3.url.parse = function (url) { | OAUTH3.url.parse = function (url) { | ||||||
|   // TODO browser
 |   // TODO browser
 | ||||||
|   // Node should replace this
 |   // Node should replace this
 | ||||||
| @ -58,8 +25,16 @@ OAUTH3.url._isRedirectHostSafe = function (referrerUrl, redirectUrl) { | |||||||
| }; | }; | ||||||
| OAUTH3.url.checkRedirect = function (client, query) { | OAUTH3.url.checkRedirect = function (client, query) { | ||||||
|   console.warn("[security] URL path checking not yet implemented"); |   console.warn("[security] URL path checking not yet implemented"); | ||||||
|  |   if (!query) { | ||||||
|  |     query = client; | ||||||
|  |     client = query.client_uri; | ||||||
|  |   } | ||||||
|  |   client = client.url || client; | ||||||
| 
 | 
 | ||||||
|   var clientUrl = OAUTH3.url.normalize(client.url); |   // 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)
 | ||||||
|  |   var clientUrl = OAUTH3.url.normalize(client); | ||||||
|   var redirectUrl = OAUTH3.url.normalize(query.redirect_uri); |   var redirectUrl = OAUTH3.url.normalize(query.redirect_uri); | ||||||
| 
 | 
 | ||||||
|   // General rule:
 |   // General rule:
 | ||||||
| @ -72,6 +47,18 @@ OAUTH3.url.checkRedirect = function (client, query) { | |||||||
|     return true; |     return true; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   var callbackUrl = 'https://oauth3.org/docs/errors#E_REDIRECT_ATTACK?'+OAUTH3.query.stringify({ | ||||||
|  |     'redirect_uri': redirectUrl | ||||||
|  |   , 'allowed_urls': clientUrl | ||||||
|  |   , 'client_id':    client | ||||||
|  |   , 'referrer_uri': OAUTH3.uri.normalize(window.document.referrer) | ||||||
|  |   }); | ||||||
|  |   if (query.debug) { | ||||||
|  |     console.log('Redirect Attack'); | ||||||
|  |     console.log(query); | ||||||
|  |     window.alert("DEBUG MODE checkRedirect error encountered. Check the console."); | ||||||
|  |   } | ||||||
|  |   location.href = callbackUrl; | ||||||
|   return false; |   return false; | ||||||
| }; | }; | ||||||
| OAUTH3.url.redirect = function (clientParams, grants, tokenOrError) { | OAUTH3.url.redirect = function (clientParams, grants, tokenOrError) { | ||||||
| @ -110,13 +97,11 @@ OAUTH3.urls.resourceOwnerPassword = function (directive, opts) { | |||||||
|   // Example Resource Owner Password Request
 |   // Example Resource Owner Password Request
 | ||||||
|   // (generally for 1st party and direct-partner mobile apps, and webapps)
 |   // (generally for 1st party and direct-partner mobile apps, and webapps)
 | ||||||
|   //
 |   //
 | ||||||
|   // POST https://example.com/api/org.oauth3.provider/access_token
 |   // POST https://example.com/api/issuer@oauth3.org/access_token
 | ||||||
|   //    { "grant_type": "password", "client_id": "<<id>>", "scope": "<<scope>>"
 |   //    { "grant_type": "password", "client_id": "<<id>>", "scope": "<<scope>>"
 | ||||||
|   //    , "username": "<<username>>", "password": "password" }
 |   //    , "username": "<<username>>", "password": "password" }
 | ||||||
|   //
 |   //
 | ||||||
|   opts = opts || {}; |   opts = opts || {}; | ||||||
|   var type = 'access_token'; |  | ||||||
|   var grantType = 'password'; |  | ||||||
| 
 | 
 | ||||||
|   if (!opts.password) { |   if (!opts.password) { | ||||||
|     if (opts.otp) { |     if (opts.otp) { | ||||||
| @ -125,16 +110,13 @@ OAUTH3.urls.resourceOwnerPassword = function (directive, opts) { | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   var scope = opts.scope || directive.authn_scope; |   var args = directive.access_token; | ||||||
|   var clientAgreeTos = 'oauth3.org/tos/draft'; // opts.clientAgreeTos || opts.client_agree_tos;
 |  | ||||||
|   var clientUri = opts.client_uri; |  | ||||||
|   var args = directive[type]; |  | ||||||
|   var otpCode = opts.otp || opts.otpCode || opts.otp_code || opts.otpToken || opts.otp_token || undefined; |   var otpCode = opts.otp || opts.otpCode || opts.otp_code || opts.otpToken || opts.otp_token || undefined; | ||||||
|   // TODO require user agent
 |   // TODO require user agent
 | ||||||
|   var params = { |   var params = { | ||||||
|     client_id: opts.client_id || opts.client_uri |     client_id: opts.client_id || opts.client_uri | ||||||
|   , client_uri: opts.client_uri |   , client_uri: opts.client_uri | ||||||
|   , grant_type: grantType |   , grant_type: 'password' | ||||||
|   , username: opts.username |   , username: opts.username | ||||||
|   , password: opts.password || otpCode || undefined |   , password: opts.password || otpCode || undefined | ||||||
|   , totp: opts.totp || opts.totpToken || opts.totp_token || undefined |   , totp: opts.totp || opts.totpToken || opts.totp_token || undefined | ||||||
| @ -149,23 +131,21 @@ OAUTH3.urls.resourceOwnerPassword = function (directive, opts) { | |||||||
|   //, "jwt": opts.jwt // TODO sign a proof with a previously loaded public_key
 |   //, "jwt": opts.jwt // TODO sign a proof with a previously loaded public_key
 | ||||||
|   , debug: opts.debug || undefined |   , debug: opts.debug || undefined | ||||||
|   }; |   }; | ||||||
|   var uri = args.url; |  | ||||||
|   var body; |  | ||||||
|   if (opts.totp) { |  | ||||||
|     params.totp = opts.totp; |  | ||||||
|   } |  | ||||||
| 
 | 
 | ||||||
|   if (clientUri) { |   if (opts.client_uri) { | ||||||
|     params.clientAgreeTos = clientAgreeTos; |     params.clientAgreeTos = 'oauth3.org/tos/draft'; // opts.clientAgreeTos || opts.client_agree_tos;
 | ||||||
|     if (!clientAgreeTos) { |     if (!params.clientAgreeTos) { | ||||||
|       throw new Error('Developer Error: missing clientAgreeTos uri'); |       throw new Error('Developer Error: missing clientAgreeTos uri'); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   var scope = opts.scope || directive.authn_scope; | ||||||
|   if (scope) { |   if (scope) { | ||||||
|     params.scope = OAUTH3.scope.stringify(scope); |     params.scope = OAUTH3.scope.stringify(scope); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   var uri = args.url; | ||||||
|  |   var body; | ||||||
|   if ('GET' === args.method.toUpperCase()) { |   if ('GET' === args.method.toUpperCase()) { | ||||||
|     uri += '?' + OAUTH3.query.stringify(params); |     uri += '?' + OAUTH3.query.stringify(params); | ||||||
|   } else { |   } else { | ||||||
| @ -181,6 +161,10 @@ OAUTH3.urls.resourceOwnerPassword = function (directive, opts) { | |||||||
| OAUTH3.urls.grants = function (directive, opts) { | OAUTH3.urls.grants = function (directive, opts) { | ||||||
|   // directive = { issuer, authorization_decision }
 |   // directive = { issuer, authorization_decision }
 | ||||||
|   // opts = { response_type, scopes{ granted, requested, pending, accepted } }
 |   // opts = { response_type, scopes{ granted, requested, pending, accepted } }
 | ||||||
|  |   var grantsDir = directive.grants; | ||||||
|  |   if (!grantsDir) { | ||||||
|  |     throw new Error("provider doesn't support grants"); | ||||||
|  |   } | ||||||
| 
 | 
 | ||||||
|   if (!opts) { |   if (!opts) { | ||||||
|     throw new Error("You must supply a directive and an options object."); |     throw new Error("You must supply a directive and an options object."); | ||||||
| @ -195,35 +179,35 @@ OAUTH3.urls.grants = function (directive, opts) { | |||||||
|     console.warn("You should supply options.referrer"); |     console.warn("You should supply options.referrer"); | ||||||
|   } |   } | ||||||
|   if (!opts.method) { |   if (!opts.method) { | ||||||
|     console.warn("You must supply options.method as either 'GET', or 'POST'"); |     console.warn("You should supply options.method as either 'GET', or 'POST'"); | ||||||
|  |     opts.method = grantsDir.method || 'GET'; | ||||||
|   } |   } | ||||||
|   if ('POST' === opts.method) { |   if ('POST' === opts.method) { | ||||||
|     if ('string' !== typeof opts.scope) { |     if ('string' !== typeof opts.scope) { | ||||||
|       console.warn("You should supply options.scope as a space-delimited string of scopes"); |       throw new Error("You must supply options.scope as a comma-delimited string of scopes"); | ||||||
|     } |     } | ||||||
|     if (-1 === ['token', 'code'].indexOf(opts.response_type)) { |     if ('string' !== typeof opts.sub) { | ||||||
|       throw new Error("You must supply options.response_type as 'token' or 'code'"); |       console.log("provide 'sub' to urls.grants to specify the PPID for the client"); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   var url = OAUTH3.url.resolve(directive.api, directive.grants.url) |   var url = OAUTH3.url.resolve(directive.api, grantsDir.url) | ||||||
|     .replace(/(:azp|:client_id)/g, OAUTH3.uri.normalize(opts.client_id || opts.client_uri)) |  | ||||||
|     .replace(/(:sub|:account_id)/g, opts.session.token.sub || 'ISSUER:GRANT:TOKEN_SUB:UNDEFINED') |     .replace(/(:sub|:account_id)/g, opts.session.token.sub || 'ISSUER:GRANT:TOKEN_SUB:UNDEFINED') | ||||||
|  |     .replace(/(:azp|:client_id)/g, !opts.all && OAUTH3.uri.normalize(opts.client_id || opts.client_uri) || '') | ||||||
|  |     .replace(/\/\/$/, '/') // if there's a double slash due to the sub not existing
 | ||||||
|     ; |     ; | ||||||
|   var data = { |   var data = { | ||||||
|     client_id: opts.client_id |     client_id: opts.client_id | ||||||
|   , client_uri: opts.client_uri |   , client_uri: opts.client_uri | ||||||
|   , referrer: opts.referrer |   , referrer: opts.referrer | ||||||
|   , response_type: opts.response_type |  | ||||||
|   , scope: opts.scope |   , scope: opts.scope | ||||||
|   , tenant_id: opts.tenant_id |   , sub: opts.sub | ||||||
|   }; |   }; | ||||||
|   var body; |  | ||||||
| 
 | 
 | ||||||
|  |   var body; | ||||||
|   if ('GET' === opts.method) { |   if ('GET' === opts.method) { | ||||||
|     url += '?' + OAUTH3.query.stringify(data); |     url += '?' + OAUTH3.query.stringify(data); | ||||||
|   } |   } else { | ||||||
|   else { |  | ||||||
|     body = data; |     body = data; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
| @ -234,21 +218,96 @@ OAUTH3.urls.grants = function (directive, opts) { | |||||||
|   , session: opts.session |   , session: opts.session | ||||||
|   }; |   }; | ||||||
| }; | }; | ||||||
|  | //OAUTH3.urls.accessToken = function (directive, opts)
 | ||||||
|  | OAUTH3.urls.clientToken = function (directive, opts) { | ||||||
|  |   var tokenDir = directive.access_token; | ||||||
|  |   if (!tokenDir) { | ||||||
|  |     throw new Error("provider doesn't support getting access tokens"); | ||||||
|  |   } | ||||||
| 
 | 
 | ||||||
| OAUTH3.authn = {}; |   if (!opts) { | ||||||
|  |     throw new Error("You must supply a directive and an options object."); | ||||||
|  |   } | ||||||
|  |   if (!(opts.azp || 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.method) { | ||||||
|  |     opts.method = tokenDir.method || 'POST'; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   var params = { | ||||||
|  |     grant_type: 'issuer_token' | ||||||
|  |   , client_id:  opts.azp || opts.client_id | ||||||
|  |   , azp: opts.azp || opts.client_id | ||||||
|  |   , aud: opts.aud | ||||||
|  |   , exp: opts.exp | ||||||
|  |   , refresh_token: opts.refresh_token | ||||||
|  |   , refresh_exp: opts.refresh_exp | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   var url = OAUTH3.url.resolve(directive.api, tokenDir.url); | ||||||
|  |   var body; | ||||||
|  |   if ('GET' === opts.method) { | ||||||
|  |     url += '?' + OAUTH3.query.stringify(params); | ||||||
|  |   } else { | ||||||
|  |     body = params; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   return { | ||||||
|  |     method: opts.method | ||||||
|  |   , url: url | ||||||
|  |   , data: body | ||||||
|  |   , session: opts.session | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | OAUTH3.urls.publishKey = function (directive, opts) { | ||||||
|  |   var jwkDir = directive.publish_jwk; | ||||||
|  |   if (!jwkDir) { | ||||||
|  |     throw new Error("provider doesn't support publishing public keys"); | ||||||
|  |   } | ||||||
|  |   if (!opts) { | ||||||
|  |     throw new Error("You must supply a directive and an options object."); | ||||||
|  |   } | ||||||
|  |   if (!opts.session) { | ||||||
|  |     throw new Error("You must supply 'options.session'."); | ||||||
|  |   } | ||||||
|  |   if (!(opts.public_key || opts.publicKey)) { | ||||||
|  |     throw new Error("You must supply 'options.public_key'."); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   var url = OAUTH3.url.resolve(directive.api, jwkDir.url) | ||||||
|  |     .replace(/(:sub|:account_id)/g, opts.session.token.sub) | ||||||
|  |     ; | ||||||
|  | 
 | ||||||
|  |   return { | ||||||
|  |     method: jwkDir.method || opts.method || 'POST' | ||||||
|  |   , url: url | ||||||
|  |   , data: opts.public_key || opts.publicKey | ||||||
|  |   , session: opts.session | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | OAUTH3.urls.credentialMeta = function (directive, opts) { | ||||||
|  |   return OAUTH3.url.resolve(directive.api, directive.credential_meta.url) | ||||||
|  |       .replace(':type', 'email') | ||||||
|  |       .replace(':id', opts.email) | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | OAUTH3.authn = OAUTH3.authn || {}; | ||||||
| OAUTH3.authn.loginMeta = function (directive, opts) { | OAUTH3.authn.loginMeta = function (directive, opts) { | ||||||
|  |   var url = OAUTH3.urls.credentialMeta(directive, opts); | ||||||
|   return OAUTH3.request({ |   return OAUTH3.request({ | ||||||
|     method: directive.credential_meta.method || 'GET' |     method: directive.credential_meta.method || 'GET' | ||||||
|     // TODO lint urls
 |     // TODO lint urls
 | ||||||
|     // TODO client_uri
 |     // TODO client_uri
 | ||||||
|   , url: OAUTH3.url.resolve(directive.api, directive.credential_meta.url) |   , url: url | ||||||
|       .replace(':type', 'email') |  | ||||||
|       .replace(':id', opts.email) |  | ||||||
|   }); |   }); | ||||||
| }; | }; | ||||||
| OAUTH3.authn.otp = function (directive, opts) { | OAUTH3.urls.otp = function (directive, opts) { | ||||||
|   // TODO client_uri
 |   // TODO client_uri
 | ||||||
|   var preq = { |   return { | ||||||
|     method: directive.credential_otp.method || 'POST' |     method: directive.credential_otp.method || 'POST' | ||||||
|   , url: OAUTH3.url.resolve(directive.api, directive.credential_otp.url) |   , url: OAUTH3.url.resolve(directive.api, directive.credential_otp.url) | ||||||
|   , data: { |   , data: { | ||||||
| @ -261,20 +320,17 @@ OAUTH3.authn.otp = function (directive, opts) { | |||||||
|     , username: opts.email |     , username: opts.email | ||||||
|     } |     } | ||||||
|   }; |   }; | ||||||
|  | }; | ||||||
|  | OAUTH3.authn.otp = function (directive, opts) { | ||||||
|  |   var preq = OAUTH3.urls.otp(directive, opts); | ||||||
| 
 | 
 | ||||||
|   return OAUTH3.request(preq); |   return OAUTH3.request(preq); | ||||||
| }; | }; | ||||||
| OAUTH3.authn.resourceOwnerPassword = function (directive, opts) { | OAUTH3.authn.resourceOwnerPassword = function (directive, opts) { | ||||||
|   var providerUri = directive.issuer; |   var providerUri = directive.issuer; | ||||||
| 
 | 
 | ||||||
|   //var scope = opts.scope;
 |   return OAUTH3.request(OAUTH3.urls.resourceOwnerPassword(directive, opts)).then(function (resp) { | ||||||
|   //var appId = opts.appId;
 |     var data = resp.data; | ||||||
|   return OAUTH3.discover(providerUri, opts).then(function (directive) { |  | ||||||
|     var prequest = OAUTH3.urls.resourceOwnerPassword(directive, opts); |  | ||||||
| 
 |  | ||||||
|     // TODO return not the raw request?
 |  | ||||||
|     return OAUTH3.request(prequest).then(function (req) { |  | ||||||
|       var data = req.data; |  | ||||||
|     data.provider_uri = providerUri; |     data.provider_uri = providerUri; | ||||||
|     if (data.error) { |     if (data.error) { | ||||||
|       return OAUTH3.PromiseA.reject(OAUTH3.error.parse(providerUri, data)); |       return OAUTH3.PromiseA.reject(OAUTH3.error.parse(providerUri, data)); | ||||||
| @ -284,20 +340,52 @@ OAUTH3.authn.resourceOwnerPassword = function (directive, opts) { | |||||||
|       opts.session || { provider_uri: providerUri, client_uri: opts.client_uri || opts.clientUri } |       opts.session || { provider_uri: providerUri, client_uri: opts.client_uri || opts.clientUri } | ||||||
|     , data |     , data | ||||||
|     ); |     ); | ||||||
|  |   }).then(function (session) { | ||||||
|  |     if (!opts.rememberDevice && !opts.remember_device) { | ||||||
|  |       return session; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return OAUTH3.PromiseA.resolve().then(function () { | ||||||
|  |       if (!OAUTH3.crypto) { | ||||||
|  |         throw new Error("OAuth3 crypto library unavailable"); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       return OAUTH3.crypto.createKeyPair().then(function (keyPair) { | ||||||
|  |         return OAUTH3.request(OAUTH3.urls.publishKey(directive, { | ||||||
|  |           session: session | ||||||
|  |         , publicKey: keyPair.publicKey | ||||||
|  |         })).then(function () { | ||||||
|  |           return OAUTH3.hooks.keyPairs.set(session.token.sub, keyPair); | ||||||
|  |         }); | ||||||
|  |       }); | ||||||
|  |     }).then(function () { | ||||||
|  |       return session; | ||||||
|  |     }, function (err) { | ||||||
|  |       console.error('failed to save keys to remember device', err); | ||||||
|  |       window.alert('Failed to remember device'); | ||||||
|  |       return session; | ||||||
|     }); |     }); | ||||||
|   }); |   }); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| OAUTH3.authz = {}; | OAUTH3.authz = {}; | ||||||
| OAUTH3.authz.scopes = function (providerUri, session, clientParams) { | OAUTH3.authz.scopes = function (providerUri, session, clientParams) { | ||||||
|   // OAuth3.requests.grants(providerUri, {});         // return list of grants
 |  | ||||||
|   // OAuth3.checkGrants(providerUri, {});             //
 |  | ||||||
|   var clientUri = OAUTH3.uri.normalize(clientParams.client_uri || OAUTH3._browser.window.document.referrer); |   var clientUri = OAUTH3.uri.normalize(clientParams.client_uri || OAUTH3._browser.window.document.referrer); | ||||||
|   var scope = clientParams.scope || ''; |   var scope = clientParams.scope || 'authn@oauth3.org'; | ||||||
|   var clientObj = clientParams; |   if ('authn@oauth3.org' === scope.toString()) { | ||||||
|  |     // 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);
 | ||||||
|  |   } | ||||||
| 
 | 
 | ||||||
|   if (!scope) { |   return OAUTH3.hooks.grants.get(session.token.sub, clientUri).then(function (granted) { | ||||||
|     scope = 'oauth3_authn'; |     if (granted) { | ||||||
|  |       if (typeof granted.scope === 'string') { | ||||||
|  |         return OAUTH3.scope.parse(granted.scope); | ||||||
|  |       } else if (Array.isArray(granted.scope)) { | ||||||
|  |         return granted.scope; | ||||||
|  |       } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return OAUTH3.authz.grants(providerUri, { |     return OAUTH3.authz.grants(providerUri, { | ||||||
| @ -305,74 +393,31 @@ OAUTH3.authz.scopes = function (providerUri, session, clientParams) { | |||||||
|     , client_id: clientUri |     , client_id: clientUri | ||||||
|     , client_uri: clientUri |     , client_uri: clientUri | ||||||
|     , session: session |     , session: session | ||||||
|   }).then(function (grantResults) { |     }).then(function (results) { | ||||||
|     var grants; |       return results.grants; | ||||||
|     var grantedScopes; |     }, function (err) { | ||||||
|     var grantedScopesMap; |       if (!/no .*grants .*found/i.test(err.message)) { | ||||||
|     var pendingScopes; |         throw err; | ||||||
|     var acceptedScopes; |  | ||||||
|     var scopes = scope.split(/[+, ]/g); |  | ||||||
|     var callbackUrl; |  | ||||||
| 
 |  | ||||||
|     // 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.url.checkRedirect(grantResults.client, clientObj)) { |  | ||||||
|       callbackUrl = 'https://oauth3.org/docs/errors#E_REDIRECT_ATTACK' |  | ||||||
|         + '?redirect_uri=' + clientObj.redirect_uri |  | ||||||
|         + '&allowed_urls=' + grantResults.client.url |  | ||||||
|         + '&client_id=' + clientUri |  | ||||||
|         + '&referrer_uri=' + OAUTH3.uri.normalize(window.document.referrer) |  | ||||||
|         ; |  | ||||||
|       if (clientParams.debug) { |  | ||||||
|         console.log('grantResults Redirect Attack'); |  | ||||||
|         console.log(grantResults); |  | ||||||
|         console.log(clientObj); |  | ||||||
|         window.alert("DEBUG MODE checkRedirect error encountered. Check the console."); |  | ||||||
|       } |       } | ||||||
|       location.href = callbackUrl; |       return []; | ||||||
|       return; |     }); | ||||||
|     } |   }).then(function (granted) { | ||||||
| 
 |     var requested = OAUTH3.scope.parse(scope); | ||||||
|     if ('oauth3_authn' === scope) { |     var accepted = []; | ||||||
|       // implicit ppid grant is automatic
 |     var pending = []; | ||||||
|       console.warn('[security] fix scope checking on backend so that we can do automatic grants'); |     requested.forEach(function (scp) { | ||||||
|       // TODO check user preference if implicit ppid grant is allowed
 |       if (granted.indexOf(scp) < 0) { | ||||||
|       //return generateToken(session, clientObj);
 |         pending.push(scp); | ||||||
|     } |       } else { | ||||||
| 
 |         accepted.push(scp); | ||||||
|     grants = (grantResults).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 { |     return { | ||||||
|       pending: pendingScopes    // not yet accepted
 |       requested: requested  // all requested, now
 | ||||||
|     , granted: grantedScopes    // all granted, ever
 |     , granted:   granted    // all granted, ever
 | ||||||
|     , requested: scopes         // all requested, now
 |     , accepted:  accepted   // intersection of granted (ever) and requested (now)
 | ||||||
|     , accepted: acceptedScopes  // granted (ever) and requested (now)
 |     , pending:   pending     // not yet accepted
 | ||||||
|     }; |     }; | ||||||
|   }); |   }); | ||||||
| }; | }; | ||||||
| @ -381,156 +426,406 @@ OAUTH3.authz.grants = function (providerUri, opts) { | |||||||
|     client_id: providerUri |     client_id: providerUri | ||||||
|   , debug: opts.debug |   , debug: opts.debug | ||||||
|   }).then(function (directive) { |   }).then(function (directive) { | ||||||
| 
 |     return OAUTH3.request(OAUTH3.urls.grants(directive, opts), opts); | ||||||
|     return OAUTH3.request(OAUTH3.urls.grants(directive, opts), opts).then(function (grantsResult) { |   }).then(function (grantsResult) { | ||||||
|       if ('POST' === opts.method) { |  | ||||||
|         // TODO this is clientToken
 |  | ||||||
|         return grantsResult.originalData || grantsResult.data; |  | ||||||
|       } |  | ||||||
| 
 |  | ||||||
|     var grants = grantsResult.originalData || grantsResult.data; |     var grants = grantsResult.originalData || grantsResult.data; | ||||||
|       // TODO
 |  | ||||||
|     if (grants.error) { |     if (grants.error) { | ||||||
|       return OAUTH3.PromiseA.reject(OAUTH3.error.parse(providerUri, grants)); |       return OAUTH3.PromiseA.reject(OAUTH3.error.parse(providerUri, grants)); | ||||||
|     } |     } | ||||||
|  |     // the responses for GET and POST requests are now the same, so we should alway be able to
 | ||||||
|  |     // use the response and save it the same way.
 | ||||||
|  |     if (opts.all || ('GET' !== opts.method && 'POST' !== opts.method)) { | ||||||
|  |       return grants; | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|       OAUTH3.hooks.grants.set(opts.client_id + '-client', grants.client); |     OAUTH3.hooks.grants.set(grants.sub, grants.azp, grants); | ||||||
|       grants.grants.forEach(function (grant) { |  | ||||||
|         var clientId = grant.client_id || grant.oauth_client_id || grant.oauthClientId; |  | ||||||
|         // TODO should save as an array
 |  | ||||||
|         OAUTH3.hooks.grants.set(clientId, [ grant ]); |  | ||||||
|       }); |  | ||||||
| 
 |  | ||||||
|     return { |     return { | ||||||
|         client: OAUTH3.hooks.grants.get(opts.client_id + '-client') |       client: grants.azp | ||||||
|       , grants: OAUTH3.hooks.grants.get(opts.client_id) || [] |     , clientSub: grants.azpSub | ||||||
|  |     , grants: OAUTH3.scope.parse(grants.scope) | ||||||
|     }; |     }; | ||||||
|   }); |   }); | ||||||
|   }); |  | ||||||
| }; | }; | ||||||
|  | function calcExpiration(exp, now) { | ||||||
|  |   if (!exp) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   if (typeof exp === 'string') { | ||||||
|  |     var match = /^(\d+\.?\d*) *(seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|years?|yrs?|y)?$/i.exec(exp); | ||||||
|  |     if (!match) { | ||||||
|  |       return now; | ||||||
|  |     } | ||||||
|  |     var num = parseFloat(match[1]); | ||||||
|  |     var type = (match[2] || 's').toLowerCase()[0]; | ||||||
|  |     switch (type) { | ||||||
|  |       case 'y': num *= 365.25; /* falls through */ | ||||||
|  |       case 'd': num *= 24;     /* falls through */ | ||||||
|  |       case 'h': num *= 60;     /* falls through */ | ||||||
|  |       case 'm': num *= 60;     /* falls through */ | ||||||
|  |       case 's': exp = num; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   if (typeof exp !== 'number') { | ||||||
|  |     throw new Error('invalid expiration provided: '+exp); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   now = now || Math.floor(Date.now() / 1000); | ||||||
|  |   if (exp > now) { | ||||||
|  |     return exp; | ||||||
|  |   } else if (exp > 31557600) { | ||||||
|  |     console.warn('tried to set expiration to more that a year'); | ||||||
|  |     exp = 31557600; | ||||||
|  |   } | ||||||
|  |   return now + exp; | ||||||
|  | } | ||||||
| OAUTH3.authz.redirectWithToken = function (providerUri, session, clientParams, scopes) { | OAUTH3.authz.redirectWithToken = function (providerUri, session, clientParams, scopes) { | ||||||
|  |   if (!OAUTH3.url.checkRedirect(clientParams.client_uri, clientParams)) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   if ('token' !== clientParams.response_type) { | ||||||
|  |     var message; | ||||||
|  |     if ('code' === clientParams.response_type) { | ||||||
|  |       message = "Authorization Code Redirect NOT IMPLEMENTED"; | ||||||
|  |     } else { | ||||||
|  |       message = "Authorization response type '"+clientParams.response_type+"' not supported"; | ||||||
|  |     } | ||||||
|  |     window.alert(message); | ||||||
|  |     throw new Error(message); | ||||||
|  |   } | ||||||
| 
 | 
 | ||||||
|   scopes.new = scopes.new || []; |   var prom; | ||||||
| 
 |   if (scopes.new) { | ||||||
|   if ('token' === clientParams.response_type) { |     prom = OAUTH3.authz.grants(providerUri, { | ||||||
|     // get token and redirect client-side
 |       session: session | ||||||
|     return OAUTH3.authz.grants(providerUri, { |     , method: 'POST' | ||||||
|       method: 'POST' |  | ||||||
|     , client_id: clientParams.client_uri |     , client_id: clientParams.client_uri | ||||||
|     , client_uri: clientParams.client_uri |  | ||||||
|     , scope: scopes.granted.concat(scopes.new).join(',') |  | ||||||
|     , response_type: clientParams.response_type |  | ||||||
|     , referrer: clientParams.referrer |     , referrer: clientParams.referrer | ||||||
|     , session: session |     , scope: scopes.accepted.concat(scopes.new).join(',') | ||||||
|     , subject: clientParams.subject |     }); | ||||||
|     , debug: clientParams.debug |   } else { | ||||||
|     }).then(function (results) { |     prom = OAUTH3.PromiseA.resolve(); | ||||||
|  |   } | ||||||
| 
 | 
 | ||||||
|  |   return prom.then(function () { | ||||||
|  |     return OAUTH3.hooks.keyPairs.get(session.token.sub); | ||||||
|  |   }).then(function (keyPair) { | ||||||
|  |     if (!keyPair) { | ||||||
|  |       return OAUTH3.discover(providerUri, { | ||||||
|  |         client_id: providerUri | ||||||
|  |       , debug: clientParams.debug | ||||||
|  |       }).then(function (directive) { | ||||||
|  |         return OAUTH3.request(OAUTH3.urls.clientToken(directive, { | ||||||
|  |           method: 'POST' | ||||||
|  |         , session: session | ||||||
|  |         , referrer: clientParams.referrer | ||||||
|  |         , response_type: clientParams.response_type | ||||||
|  |         , client_id:  clientParams.client_uri | ||||||
|  |         , azp: clientParams.client_uri | ||||||
|  |         , aud: clientParams.aud | ||||||
|  |         , exp: clientParams.exp | ||||||
|  |         , refresh_token: clientParams.refresh_token | ||||||
|  |         , refresh_exp: clientParams.refresh_exp | ||||||
|  |         , debug: clientParams.debug | ||||||
|  |         })).then(function (result) { | ||||||
|  |           return result.originalData || result.data; | ||||||
|  |         }); | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return OAUTH3.hooks.grants.get(keyPair.sub, clientParams.client_uri).then(function (grant) { | ||||||
|  |       var now = Math.floor(Date.now()/1000); | ||||||
|  |       var payload = { | ||||||
|  |         iat: now | ||||||
|  |       , iss: providerUri | ||||||
|  |       , aud: clientParams.aud || providerUri | ||||||
|  |       , azp: clientParams.client_uri | ||||||
|  |       , sub: grant.azpSub | ||||||
|  |       , scope: OAUTH3.scope.stringify(grant.scope) | ||||||
|  |     , }; | ||||||
|  | 
 | ||||||
|  |       var signProms = []; | ||||||
|  |       signProms.push(OAUTH3.jwt.sign(Object.assign({ | ||||||
|  |         exp: calcExpiration(clientParams.exp || '1h', now) | ||||||
|  |       }, payload), keyPair)); | ||||||
|  |       // if (clientParams.refresh_token) {
 | ||||||
|  |         signProms.push(OAUTH3.jwt.sign(Object.assign({ | ||||||
|  |           exp: calcExpiration(clientParams.refresh_exp, now) | ||||||
|  |         }, payload), keyPair)); | ||||||
|  |       // }
 | ||||||
|  |       return OAUTH3.PromiseA.all(signProms).then(function (tokens) { | ||||||
|  |         console.log('created new tokens for client'); | ||||||
|  |         return { | ||||||
|  |           access_token: tokens[0] | ||||||
|  |         , refresh_token: tokens[1] | ||||||
|  |         , scope: OAUTH3.scope.stringify(grant.scope) | ||||||
|  |         , token_type: 'bearer' | ||||||
|  |         }; | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|  |   }).then(function (session) { | ||||||
|     // TODO limit refresh token to an expirable token
 |     // TODO limit refresh token to an expirable token
 | ||||||
|     // TODO inform client not to persist token
 |     // TODO inform client not to persist token
 | ||||||
|       /* |     OAUTH3.url.redirect(clientParams, scopes, session); | ||||||
|       if (clientParams.dnsTxt) { |   }, function (err) { | ||||||
|         Object.keys(results).forEach(function (key) { |     console.error('unexpected error creating client tokens', err); | ||||||
|           if (/refresh/.test(key)) { |     OAUTH3.url.redirect(clientParams, scopes, {error: err}); | ||||||
|             results[key] = undefined; |  | ||||||
|           } |  | ||||||
|   }); |   }); | ||||||
|       } |  | ||||||
|       */ |  | ||||||
|       OAUTH3.url.redirect(clientParams, scopes, results); |  | ||||||
|     }); |  | ||||||
|   } |  | ||||||
|   else if ('code' === clientParams.response_type) { |  | ||||||
|     // get token and redirect server-side
 |  | ||||||
|     // (requires insecure form post as per spec)
 |  | ||||||
|     //OAUTH3.requests.authorizationDecision();
 |  | ||||||
|     window.alert("Authorization Code Redirect NOT IMPLEMENTED"); |  | ||||||
|     throw new Error("Authorization Code Redirect NOT IMPLEMENTED"); |  | ||||||
|   } |  | ||||||
| }; | }; | ||||||
|  | 
 | ||||||
| OAUTH3.requests = {}; | OAUTH3.requests = {}; | ||||||
|  | //OAUTH3.accounts = {};
 | ||||||
| OAUTH3.requests.accounts = {}; | OAUTH3.requests.accounts = {}; | ||||||
| OAUTH3.requests.accounts.update = function (directive, session, opts) { | OAUTH3.urls.accounts = {}; | ||||||
|   var dir = directive.update_account || { | OAUTH3.urls.accounts._ = function (directives, directive, session, opts) { | ||||||
|     method: 'POST' |   opts = opts || {}; | ||||||
|   , url: OAUTH3.url.normalize(directive.api) + '/api/issuer@oauth3.org/accounts/:accountId' |   var dir = directive || { | ||||||
|  |      //url: OAUTH3.url.normalize(directives.api) + '/api/issuer@oauth3.org/accounts/:accountId'
 | ||||||
|  |      url: OAUTH3.url.normalize(directives.api) + '/api/issuer@oauth3.org/acl/profiles/:accountId' | ||||||
|  |   //, method: 'GET'
 | ||||||
|   , bearer: 'Bearer' |   , bearer: 'Bearer' | ||||||
|   }; |   }; | ||||||
|   var url = dir.url |   var url = dir.url | ||||||
|     .replace(/:accountId/, opts.accountId) |     .replace(/:accountId/, opts.accountId || '') | ||||||
|  |     .replace(/\/$/, '') | ||||||
|   ; |   ; | ||||||
| 
 | 
 | ||||||
|   return OAUTH3.request({ |   return { | ||||||
|     method: dir.method || 'POST' |     url: url | ||||||
|   , url: url |     //, method: dir.method || 'POST'
 | ||||||
|  |   , session: session | ||||||
|  |     /* | ||||||
|   , headers: { |   , headers: { | ||||||
|       'Authorization': (dir.bearer || 'Bearer') + ' ' + session.accessToken |       'Authorization': (dir.bearer || 'Bearer') + ' ' + (session.access_token || session.accessToken) | ||||||
|     } |     } | ||||||
|   , json: { |     */ | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | OAUTH3.urls.accounts.get = function (directives, session) { | ||||||
|  |   var urlObj = OAUTH3.urls.accounts._(directives, directives.account, session); | ||||||
|  |   urlObj.method = (directives.account || { method: 'GET' }).method; | ||||||
|  |   return urlObj; | ||||||
|  | }; | ||||||
|  | OAUTH3.urls.accounts.update = function (directives, session, opts) { | ||||||
|  |   var urlObj = OAUTH3.urls.accounts._(directives, directives.update_account, session, opts); | ||||||
|  |   urlObj.method = (directives.update_account || { method: 'POST' }).method; | ||||||
|  |   urlObj.json = { | ||||||
|     name: opts.name |     name: opts.name | ||||||
|   , comment: opts.comment |   , comment: opts.comment | ||||||
|   , displayName: opts.displayName |   , displayName: opts.displayName | ||||||
|   , priority: opts.priority |   , priority: opts.priority | ||||||
|     } |  | ||||||
|   }); |  | ||||||
|   }; |   }; | ||||||
| OAUTH3.requests.accounts.create = function (directive, session, account) { |   return urlObj; | ||||||
|   var dir = directive.create_account || { |  | ||||||
|     method: 'POST' |  | ||||||
|   , url: OAUTH3.url.normalize(directive.api) + '/api/issuer@oauth3.org/accounts' |  | ||||||
|   , bearer: 'Bearer' |  | ||||||
| }; | }; | ||||||
|   var data = { | OAUTH3.urls.accounts.create = function (directives, session, account) { | ||||||
|  |   var urlObj = OAUTH3.urls.accounts._(directives, directives.create_account, session); | ||||||
|  |   var profile = { | ||||||
|  |     nick: account.display_name | ||||||
|  |     // "name" is unique and what would be reserved in a url {{name}}.issuer.org or issuer.org/users/{{name}}
 | ||||||
|  |   , name: account.name | ||||||
|  |   , comment: account.comment | ||||||
|  |   , display_name: account.display_name | ||||||
|  |   , priority: account.priority | ||||||
|  |   }; | ||||||
|  |   var credentials = [ { token: session.access_token } ]; | ||||||
|  |   urlObj.method = (directives.create_account || { method: 'POST' }).method; | ||||||
|  |   urlObj.json = { | ||||||
|     // TODO fix the server to just use one scheme
 |     // TODO fix the server to just use one scheme
 | ||||||
|     // account = { nick, self: { comment, username } }
 |     // account = { nick, self: { comment, username } }
 | ||||||
|     // account = { name, comment, display_name, priority }
 |     // account = { name, comment, display_name, priority }
 | ||||||
|     account: { |     credentials: credentials | ||||||
|       nick: account.display_name |   , profile: profile | ||||||
|     , name: account.name |     // 'account' is deprecated in favor of 'profile'
 | ||||||
|     , comment: account.comment |   , account: profile | ||||||
|     , display_name: account.display_name |     // 'logins' is deprecated in favor of 'credentials'
 | ||||||
|     , priority: account.priority |   , logins: credentials | ||||||
|     , self: { |   }; | ||||||
|         nick: account.display_name |   return urlObj; | ||||||
|       , name: account.name | }; | ||||||
|       , comment: account.comment | OAUTH3.requests.accounts.get = function (directives, session) { | ||||||
|       , display_name: account.display_name |   var urlObj = OAUTH3.urls.accounts.get(directives, session); | ||||||
|       , priority: account.priority |   return OAUTH3.request(urlObj); | ||||||
|       } | }; | ||||||
|     } | OAUTH3.requests.accounts.update = function (directives, session, opts) { | ||||||
|   , logins: [ |   var urlObj = OAUTH3.urls.accounts.update(directives, session, opts); | ||||||
|       { |   return OAUTH3.request(urlObj); | ||||||
|         token: session.access_token | }; | ||||||
|       } | OAUTH3.requests.accounts.create = function (directive, session, account) { | ||||||
|     ] |   var urlObj = OAUTH3.urls.accounts.create(directives, session, account); | ||||||
|  |   return OAUTH3.request(urlObj); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|   return OAUTH3.request({ | OAUTH3.hooks.grants = { | ||||||
|     method: dir.method || 'POST' |   get: function (id, clientUri) { | ||||||
|   , url: dir.url |     OAUTH3.hooks._checkStorage('grants', 'get'); | ||||||
|   , session: session | 
 | ||||||
|   , data: data |     if (!id) { | ||||||
|  |       throw new Error("id is not set"); | ||||||
|  |     } | ||||||
|  |     if (!clientUri) { | ||||||
|  |       throw new Error("clientUri is not set"); | ||||||
|  |     } | ||||||
|  |     return OAUTH3.PromiseA.resolve(OAUTH3._hooks.grants.get(id, OAUTH3.uri.normalize(clientUri))); | ||||||
|  |   } | ||||||
|  | , set: function (id, clientUri, grants) { | ||||||
|  |     OAUTH3.hooks._checkStorage('grants', 'set'); | ||||||
|  | 
 | ||||||
|  |     if (!id) { | ||||||
|  |       throw new Error("id is not set"); | ||||||
|  |     } | ||||||
|  |     if (!clientUri) { | ||||||
|  |       throw new Error("clientUri is not set"); | ||||||
|  |     } | ||||||
|  |     return OAUTH3.PromiseA.resolve(OAUTH3._hooks.grants.set(id, OAUTH3.uri.normalize(clientUri), grants)); | ||||||
|  |   } | ||||||
|  | , all: function () { | ||||||
|  |     OAUTH3.hooks._checkStorage('grants', 'all'); | ||||||
|  | 
 | ||||||
|  |     return OAUTH3.PromiseA.resolve(OAUTH3._hooks.grants.all()); | ||||||
|  |   } | ||||||
|  | , clear: function () { | ||||||
|  |     OAUTH3.hooks._checkStorage('grants', 'clear'); | ||||||
|  | 
 | ||||||
|  |     return OAUTH3.PromiseA.resolve(OAUTH3._hooks.grants.clear()); | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  | OAUTH3.hooks.keyPairs = { | ||||||
|  |   get: function (id) { | ||||||
|  |     OAUTH3.hooks._checkStorage('keyPairs', 'get'); | ||||||
|  | 
 | ||||||
|  |     if (!id) { | ||||||
|  |       throw new Error("id is not set"); | ||||||
|  |     } | ||||||
|  |     return OAUTH3.PromiseA.resolve(OAUTH3._hooks.keyPairs.get(id)); | ||||||
|  |   } | ||||||
|  | , set: function (id, keyPair) { | ||||||
|  |     OAUTH3.hooks._checkStorage('keyPairs', 'set'); | ||||||
|  | 
 | ||||||
|  |     if (!keyPair && id.privateKey && id.publicKey && id.sub) { | ||||||
|  |       keyPair = id; | ||||||
|  |       id = keyPair.sub; | ||||||
|  |     } | ||||||
|  |     if (!keyPair) { | ||||||
|  |       return OAUTH3.PromiseA.reject(new Error("no key pair provided to save")); | ||||||
|  |     } | ||||||
|  |     if (!id) { | ||||||
|  |       throw new Error("id is not set"); | ||||||
|  |     } | ||||||
|  |     keyPair.sub = keyPair.sub || id; | ||||||
|  | 
 | ||||||
|  |     return OAUTH3.PromiseA.resolve(OAUTH3._hooks.keyPairs.set(id, keyPair)); | ||||||
|  |   } | ||||||
|  | , all: function () { | ||||||
|  |     OAUTH3.hooks._checkStorage('keyPairs', 'all'); | ||||||
|  | 
 | ||||||
|  |     return OAUTH3.PromiseA.resolve(OAUTH3._hooks.keyPairs.all()); | ||||||
|  |   } | ||||||
|  | , clear: function () { | ||||||
|  |     OAUTH3.hooks._checkStorage('keyPairs', 'clear'); | ||||||
|  | 
 | ||||||
|  |     return OAUTH3.PromiseA.resolve(OAUTH3._hooks.keyPairs.clear()); | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  | OAUTH3.hooks.session.get = function (providerUri, id) { | ||||||
|  |   OAUTH3.hooks._checkStorage('sessions', 'get'); | ||||||
|  |   var sessProm = OAUTH3.PromiseA.resolve(OAUTH3._hooks.sessions.get(providerUri, id)); | ||||||
|  |   if (providerUri !== OAUTH3.clientUri(window.location)) { | ||||||
|  |     return sessProm; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   return sessProm.then(function (session) { | ||||||
|  |     if (session && OAUTH3.jwt.freshness(session.token) === 'fresh') { | ||||||
|  |       return session; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return OAUTH3.hooks.keyPairs.all().then(function (keyPairs) { | ||||||
|  |       var pair; | ||||||
|  |       if (id) { | ||||||
|  |         pair = keyPairs[id]; | ||||||
|  |       } else if (Object.keys(keyPairs).length === 1) { | ||||||
|  |         id = Object.keys(keyPairs)[0]; | ||||||
|  |         pair = keyPairs[id]; | ||||||
|  |       } else if (Object.keys(keyPairs).length > 1) { | ||||||
|  |         console.error("too many users, don't know which key to use"); | ||||||
|  |       } | ||||||
|  |       if (!pair) { | ||||||
|  |         // even if the access token isn't fresh, the session might have a refresh token
 | ||||||
|  |         return session; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       var now = Math.floor(Date.now()/1000); | ||||||
|  |       var payload = { | ||||||
|  |         iat: now | ||||||
|  |       , iss: providerUri | ||||||
|  |       , aud: providerUri | ||||||
|  |       , azp: providerUri | ||||||
|  |       , sub: pair.sub || id | ||||||
|  |       , scope: '' | ||||||
|  |       , exp: now + 3600 | ||||||
|  |       }; | ||||||
|  |       return OAUTH3.jwt.sign(payload, pair.privateKey).then(function (token) { | ||||||
|  |         console.log('created new token for provider'); | ||||||
|  |         return OAUTH3.hooks.session.refresh( | ||||||
|  |           { provider_uri: providerUri, client_uri: providerUri || providerUri } | ||||||
|  |         , { access_token: token } | ||||||
|  |         ); | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|   }); |   }); | ||||||
| }; | }; | ||||||
| OAUTH3.hooks.grants = { | 
 | ||||||
|   // Provider Only
 | OAUTH3._defaultStorage.grants = { | ||||||
|   set: function (clientUri, newGrants) { |   prefix: 'grants-' | ||||||
|     clientUri = OAUTH3.uri.normalize(clientUri); | , get: function (id, clientUri) { | ||||||
|     console.warn('[oauth3.hooks.setGrants] PLEASE IMPLEMENT -- Your Fault'); |     var key = this.prefix + id+'/'+clientUri; | ||||||
|     console.warn(newGrants); |     var result = JSON.parse(window.localStorage.getItem(key) || 'null'); | ||||||
|     if (!this._cache) { this._cache = {}; } |     return OAUTH3.PromiseA.resolve(result); | ||||||
|     console.log('clientUri, newGrants'); |  | ||||||
|     console.log(clientUri, newGrants); |  | ||||||
|     this._cache[clientUri] = newGrants; |  | ||||||
|     return newGrants; |  | ||||||
|   } |   } | ||||||
| , get: function (clientUri) { | , set: function (id, clientUri, grants) { | ||||||
|     clientUri = OAUTH3.uri.normalize(clientUri); |     var key = this.prefix + id+'/'+clientUri; | ||||||
|     console.warn('[oauth3.hooks.getGrants] PLEASE IMPLEMENT -- Your Fault'); |     window.localStorage.setItem(key, JSON.stringify(grants)); | ||||||
|     if (!this._cache) { this._cache = {}; } |     return this.get(clientUri); | ||||||
|     console.log('clientUri, existingGrants'); |   } | ||||||
|     console.log(clientUri, this._cache[clientUri]); | , all: function () { | ||||||
|     return this._cache[clientUri]; |     var prefix = this.prefix; | ||||||
|  |     var result = {}; | ||||||
|  |     OAUTH3._defaultStorage._getStorageKeys(prefix, window.localStorage).forEach(function (key) { | ||||||
|  |       var split = key.replace(prefix, '').split('/'); | ||||||
|  |       if (!result[split[0]]) { result[split[0]] = {}; } | ||||||
|  |       result[split[0]][split[1]] = JSON.parse(window.localStorage.getItem(key) || 'null'); | ||||||
|  |     }); | ||||||
|  |     return OAUTH3.PromiseA.resolve(result); | ||||||
|  |   } | ||||||
|  | , clear: function () { | ||||||
|  |     OAUTH3._defaultStorage._getStorageKeys(this.prefix, window.localStorage).forEach(function (key) { | ||||||
|  |       window.localStorage.removeItem(key); | ||||||
|  |     }); | ||||||
|  |     return OAUTH3.PromiseA.resolve(); | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  | OAUTH3._defaultStorage.keyPairs = { | ||||||
|  |   prefix: 'key_pairs-' | ||||||
|  | , get: function (id) { | ||||||
|  |     var result = JSON.parse(window.localStorage.getItem(this.prefix + id) || 'null'); | ||||||
|  |     return OAUTH3.PromiseA.resolve(result); | ||||||
|  |   } | ||||||
|  | , set: function (id, keyPair) { | ||||||
|  |     window.localStorage.setItem(this.prefix + id, JSON.stringify(keyPair)); | ||||||
|  |     return this.get(id); | ||||||
|  |   } | ||||||
|  | , all: function () { | ||||||
|  |     var prefix = this.prefix; | ||||||
|  |     var result = {}; | ||||||
|  |     OAUTH3._defaultStorage._getStorageKeys(prefix, window.localStorage).forEach(function (key) { | ||||||
|  |       result[key.replace(prefix, '')] = JSON.parse(window.localStorage.getItem(key) || 'null'); | ||||||
|  |     }); | ||||||
|  |     return OAUTH3.PromiseA.resolve(result); | ||||||
|  |   } | ||||||
|  | , clear: function () { | ||||||
|  |     OAUTH3._defaultStorage._getStorageKeys(this.prefix, window.localStorage).forEach(function (key) { | ||||||
|  |       window.localStorage.removeItem(key); | ||||||
|  |     }); | ||||||
|  |     return OAUTH3.PromiseA.resolve(); | ||||||
|   } |   } | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -27,9 +27,9 @@ | |||||||
| 
 | 
 | ||||||
|   OAUTH3.authz.scopes = function () { |   OAUTH3.authz.scopes = function () { | ||||||
|     return OAUTH3.PromiseA.resolve({ |     return OAUTH3.PromiseA.resolve({ | ||||||
|       pending: ['oauth3_authn']   // not yet accepted
 |       pending: [ 'authn@oauth3.org' ]     // not yet accepted
 | ||||||
|     , granted: []                         // all granted, ever
 |     , granted: []                         // all granted, ever
 | ||||||
|     , requested: ['oauth3_authn'] // all requested, now
 |     , requested: [ 'authn@oauth3.org' ]   // all requested, now
 | ||||||
|     , accepted: []                        // granted (ever) and requested (now)
 |     , accepted: []                        // granted (ever) and requested (now)
 | ||||||
|     }); |     }); | ||||||
|   }; |   }; | ||||||
|  | |||||||
| @ -28,6 +28,7 @@ OAUTH3._base64.atob = function (base64) { | |||||||
| OAUTH3._base64.btoa = function (text) { | OAUTH3._base64.btoa = function (text) { | ||||||
|   return new Buffer(text, 'utf8').toString('base64'); |   return new Buffer(text, 'utf8').toString('base64'); | ||||||
| }; | }; | ||||||
|  | OAUTH3._defaultStorage = require('./oauth3.node.storage'); | ||||||
| 
 | 
 | ||||||
| OAUTH3._node = {}; | OAUTH3._node = {}; | ||||||
| OAUTH3._node.discover = function(providerUri/*, opts*/) { | OAUTH3._node.discover = function(providerUri/*, opts*/) { | ||||||
| @ -43,6 +44,7 @@ OAUTH3._node.request = function(preq/*, _sys*/) { | |||||||
|     method: preq.method |     method: preq.method | ||||||
|   , url: preq.url || preq.uri |   , url: preq.url || preq.uri | ||||||
|   , headers: preq.headers |   , headers: preq.headers | ||||||
|  |   , timeout: preq.timeout || undefined | ||||||
|   , json: preq.data || preq.body || preq.json || undefined // TODO which to use?
 |   , json: preq.data || preq.body || preq.json || undefined // TODO which to use?
 | ||||||
|   , formData: preq.formData || undefined |   , formData: preq.formData || undefined | ||||||
|   }; |   }; | ||||||
|  | |||||||
| @ -67,10 +67,9 @@ module.exports = { | |||||||
| 
 | 
 | ||||||
| , sessions: { | , sessions: { | ||||||
|     all: function (providerUri) { |     all: function (providerUri) { | ||||||
|       var dirname = path.join(oauth3dir, 'sessions'); |       return fs.readdirAsync(sessionsdir).then(function (nodes) { | ||||||
|       return fs.readdirAsync(dirname).then(function (nodes) { |  | ||||||
|         return nodes.map(function (node) { |         return nodes.map(function (node) { | ||||||
|           var result = require(path.join(dirname, node)); |           var result = require(path.join(sessionsdir, node)); | ||||||
|           if (result.link) { |           if (result.link) { | ||||||
|             return null; |             return null; | ||||||
|           } |           } | ||||||
| @ -91,7 +90,7 @@ module.exports = { | |||||||
|           result = require(path.join(sessionsdir, providerUri + '.json')); |           result = require(path.join(sessionsdir, providerUri + '.json')); | ||||||
|           // TODO make safer
 |           // TODO make safer
 | ||||||
|           if (result.link && '/' !== result.link[0] && !/\.\./.test(result.link)) { |           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) { |       } catch(e) { | ||||||
| @ -113,10 +112,9 @@ module.exports = { | |||||||
|       }); |       }); | ||||||
|     } |     } | ||||||
|   , clear: function () { |   , clear: function () { | ||||||
|       var dirname = path.join(oauth3dir, 'sessions'); |       return fs.readdirAsync(sessionsdir).then(function (nodes) { | ||||||
|       return fs.readdirAsync(dirname).then(function (nodes) { |  | ||||||
|         return PromiseA.all(nodes.map(function (node) { |         return PromiseA.all(nodes.map(function (node) { | ||||||
|           return fs.unlinkAsync(path.join(dirname, node)); |           return fs.unlinkAsync(path.join(sessionsdir, node)); | ||||||
|         })); |         })); | ||||||
|       }); |       }); | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -9,7 +9,7 @@ OAUTH3.api['tunnel.token'] = function (providerUri, opts) { | |||||||
|   return OAUTH3.request({ |   return OAUTH3.request({ | ||||||
|     method: 'POST' |     method: 'POST' | ||||||
|   , url: OAUTH3.url.normalize(providerUri) |   , 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 |   , session: session | ||||||
|   , data: { |   , data: { | ||||||
|       domains: opts.data.domains |       domains: opts.data.domains | ||||||
|  | |||||||
| @ -1,6 +1,6 @@ | |||||||
| { | { | ||||||
|   "name": "oauth3.js", |   "name": "oauth3.js", | ||||||
|   "version": "1.0.10", |   "version": "1.2.2", | ||||||
|   "description": "The world's smallest, fastest, and most secure OAuth3 (and OAuth2) JavaScript implementation.", |   "description": "The world's smallest, fastest, and most secure OAuth3 (and OAuth2) JavaScript implementation.", | ||||||
|   "main": "oauth3.node.js", |   "main": "oauth3.node.js", | ||||||
|   "scripts": { |   "scripts": { | ||||||
| @ -9,7 +9,7 @@ | |||||||
|   }, |   }, | ||||||
|   "repository": { |   "repository": { | ||||||
|     "type": "git", |     "type": "git", | ||||||
|     "url": "git@git.daplie.com:OAuth3/oauth3.js.git" |     "url": "git@git.oauth3.org:OAuth3/oauth3.js.git" | ||||||
|   }, |   }, | ||||||
|   "keywords": [ |   "keywords": [ | ||||||
|     "oauth", |     "oauth", | ||||||
| @ -35,7 +35,7 @@ | |||||||
|     "bluebird": "^3.5.0", |     "bluebird": "^3.5.0", | ||||||
|     "elliptic": "^6.4.0", |     "elliptic": "^6.4.0", | ||||||
|     "request": "^2.81.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": { |   "devDependencies": { | ||||||
|     "browserify-aes": "^1.0.6", |     "browserify-aes": "^1.0.6", | ||||||
| @ -49,6 +49,6 @@ | |||||||
|     "gulp-uglify": "^2.1.0", |     "gulp-uglify": "^2.1.0", | ||||||
|     "vinyl-source-stream": "^1.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)" |   "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,13 +0,0 @@ | |||||||
| { "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/otp" } |  | ||||||
| , "credential_otp": { "method": "POST", "url": "api/issuer@oauth3.org/otp" } |  | ||||||
| , "credential_meta": { "url": "api/issuer@oauth3.org/logins/meta/:type/:id" } |  | ||||||
| , "credential_create": { "method": "POST", "url": "api/issuer@oauth3.org/logins" } |  | ||||||
| , "grants": { "method": "GET", "url": "api/issuer@oauth3.org/grants/:azp/:sub" } |  | ||||||
| , "authorization_decision": { "method": "POST", "url": "api/issuer@oauth3.org/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/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); |  | ||||||
|     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