Compare commits

...

131 Commits

Author SHA1 Message Date
291bfd6a79 retrieve signing key 2018-07-06 08:12:13 +00:00
def91fb60d fix url for validating token 2018-07-06 07:51:39 +00:00
738573a79c typo fix _providerUri => providerUri 2018-07-06 05:56:01 +00:00
58f245f90c typo auth => oauth3 2018-07-06 05:48:25 +00:00
c5735f402c add symlinks for back-compat 2018-07-06 04:55:52 +00:00
db43f09ce2 .well-known => _apis 2018-07-06 04:55:34 +00:00
e7ffe06d9d .well-known => _apis 2018-07-06 00:45:58 +00:00
3d9d7b00d7 update links 2018-04-23 22:10:10 +00:00
cf5c106f64 correct logout 2017-11-29 05:30:22 +00:00
f4b3dbd495 Merge branch 'v1.2-next' of git.oauth3.org:OAuth3/oauth3.js into v1.2-next 2017-11-29 05:13:53 +00:00
6f6d07e670 add navigator.auth api 2017-11-29 05:13:49 +00:00
23db17a31e accept scope as array, use scope as delimited string 2017-11-29 04:17:43 +00:00
d87645d135 oauth3_authn -> authn@oauth3.org 2017-11-29 02:12:39 +00:00
989dbfb150 Merge branch 'v1.2-next' of git.oauth3.org:OAuth3/oauth3.js into v1.2-next 2017-11-29 02:09:39 +00:00
36fe8e2a80 default scope changed to authn@oauth3.org 2017-11-29 02:09:19 +00:00
ff6d9665e2 default broker is new.oauth3.org 2017-11-29 02:09:00 +00:00
2587d03860 fix trailing slash 2017-11-28 02:26:31 +00:00
f14f42404e Merge branch 'v1.2' into v1.2-next 2017-11-28 01:25:00 +00:00
0aa85baf6d Merge branch 'v1.2-next' of git.oauth3.org:OAuth3/oauth3.js into v1.2-next 2017-11-25 09:11:01 +00:00
39c0c775ed get issuer via rpc 2017-11-25 09:10:54 +00:00
2bfc752ea1 Merge branch 'rpc' into v1.2-next 2017-11-25 08:59:15 +00:00
d7c4d0ff13 demagicstringify 2017-11-25 08:58:58 +00:00
18170e94f4 Merge remote-tracking branch 'origin/rpc' into v1.2-next 2017-11-25 08:39:37 +00:00
ff76a4116e Merge branch 'rpc' into v1.2-next 2017-11-25 08:32:39 +00:00
c9c45ebe4e fix syntax error 2017-11-25 08:32:13 +00:00
d84b7bd6ea Merge branch 'rpc' into v1.2-next 2017-11-25 08:28:46 +00:00
e78999d4a1 default broker is self 2017-11-25 08:28:27 +00:00
687391e56b Merge branch 'rpc' into v1.2-next 2017-11-25 08:21:45 +00:00
6a131f6650 add broker 2017-11-25 08:21:17 +00:00
9f48a44958 merge rpc 2017-11-25 08:15:44 +00:00
be9e8852b8 WIP respond to RPC 2017-11-25 08:09:57 +00:00
d015e66f17 WIP request rpc 2017-11-25 07:46:37 +00:00
77c64df163 Merge branch 'discover-pixel' into v1.2-next 2017-11-24 01:28:23 +00:00
fcca994c65 fix parseSubject 2017-11-24 01:27:24 +00:00
e27af15485 Merge branch 'no-timeout' into v1.2-next 2017-11-24 01:00:20 +00:00
c59e23d114 Merge branch 'no-timeout' of OAuth3/oauth3.js into v1.2 2017-11-24 01:00:06 +00:00
6225b1e2fe better error message 2017-11-24 00:59:37 +00:00
9298776620 ws 2017-11-24 00:50:26 +00:00
2c0b757c13 no timeout 2017-11-24 00:49:35 +00:00
f6017e7e49 test oauth3 support via pixel 2017-11-23 07:25:35 +00:00
4da61d835e add clear.gif 2017-11-23 07:23:44 +00:00
215d3f0e92 add blank.gif 2017-11-23 03:14:00 +00:00
bc82bb6f1b Moved scopes to well-known dir. 2017-11-20 08:59:23 -08:00
d13728dd3d Initial scope discover. Needs testing and renaming. 2017-11-20 08:26:52 -08:00
tigerbot
9ac5c3ed89 Merge remote-tracking branch 'origin/v1.2' 2017-11-17 12:42:00 -07:00
c2ec21c446 update accounts apis 2017-11-17 06:10:25 +00:00
a7a9a16847 Merge branch 'v1.2' 2017-11-16 20:46:38 +00:00
82ed16e162 don't overwrite existing authn 2017-11-16 20:41:50 +00:00
1e459ce186 WIP started trying some things with scope discovery. 2017-11-15 21:39:10 -08:00
e6fa1d5314 Merge branch 'v1.2' 2017-11-16 05:30:53 +00:00
b7aa754c48 enable showing all grants 2017-11-16 05:30:27 +00:00
23d599835b Merge branch 'v1.2' of https://git.oauth3.org/OAuth3/oauth3.js into v1.2 2017-11-14 23:39:38 +00:00
0a980fd560 urls for otp 2017-11-14 23:39:14 +00:00
drewwarren
f79fa608d7 Merge branch 'remember-device' of OAuth3/oauth3.js into v1.2 2017-11-14 20:17:27 +00:00
a4b7833989 OAUTH3.crypto - defer calls to asynchronously loaded methods. 2017-11-13 13:09:58 -08:00
tigerbot
2438081487 fixed this scoping problem introduced with _getCached fix 2017-11-13 13:56:58 -07:00
AJ ONeal
c77d280b00 update URLs 2017-11-13 10:52:00 -07:00
AJ ONeal
69a8e6ec01 add standard files 2017-11-13 10:51:41 -07:00
AJ ONeal
19af663368 update urls 2017-11-10 12:47:29 -07:00
AJ ONeal
bcf2f315b8 update urls 2017-11-10 12:20:17 -07:00
AJ ONeal
9a8d742ff1 Merge branch 'master' into v1.2 2017-11-10 11:57:31 -07:00
AJ ONeal
1d3f4ca8bd cleanup cruft 2017-11-10 11:57:20 -07:00
AJ ONeal
6b0470607c update urls 2017-11-10 11:50:09 -07:00
AJ ONeal
bfd9e0ce1c add standard files 2017-11-10 11:49:50 -07:00
AJ ONeal
19b0e7a5d2 added some comments 2017-11-09 17:39:27 -07:00
AJ ONeal
e99b9ff4d6 print help for sub-commands 2017-11-09 17:32:03 -07:00
AJ ONeal
83030fb416 print help for top-level commands and command sets 2017-11-09 16:54:47 -07:00
AJ ONeal
523f57944f Merge branch 'v1.2' into merge 2017-11-09 15:25:57 -07:00
AJ ONeal
dedd851ff9 renamed 2017-11-09 15:25:19 -07:00
tigerbot
4d4d1af45d cleaned up all uses of _getCached on hooks
the _getCached function disappeared when the hooks were re-written, but some
of the places that still relied on the old way of things still survived
2017-11-08 19:03:28 -07:00
tigerbot
69d5cb382a v1.2.2 2017-11-07 13:17:39 -07:00
tigerbot
6acb027b3a added more information to request errors 2017-10-25 14:13:01 -06:00
tigerbot
081b2a23de added ability to define request timeout 2017-10-25 14:12:41 -06:00
tigerbot
815ba04d37 Merge branch 'issuer-rewrite' 2017-10-19 16:59:28 -06:00
tigerbot
d7d07b841a made the jwt freshness function work with non-expiring tokens 2017-10-19 15:27:35 -06:00
tigerbot
26e9a1c08b fixed bug clearing old session fields in session refresh 2017-10-12 12:10:42 -06:00
tigerbot
704337e30b changed error handling to try providing most useful messages 2017-09-28 13:50:52 -06:00
aj
db284fbf91 allow setting cookie session to fail 2017-09-01 16:18:42 +00:00
aj
1fe8733a06 allow Cookies with CORS requests (TODO: only for assets) 2017-08-30 21:47:13 +00:00
aj
7bb0fca116 set cookie 2017-08-30 19:23:33 +00:00
aj
a5742d1a2a add progress 2017-08-25 23:47:31 +00:00
aj
effee987be type: opts.selected -> opts.type 2017-08-23 17:10:54 +00:00
aj
bf71399d12 keep package vars even when then instance is changed 2017-08-23 16:16:18 +00:00
aj
5f68ea19e2 don't set all keys to undefined 2017-08-18 17:44:09 +00:00
aj
8961a4e519 separate client init from options init 2017-08-16 21:44:36 +00:00
aj
6b7fd877ec org.oauth3 -> oauth3.org 2017-08-15 20:23:07 +00:00
aj
4d80074046 org.oauth3 -> oauth3.org 2017-08-15 20:21:54 +00:00
tigerbot
b60e9b8fce updated the directives for the new issuer API 2017-08-15 14:06:15 -06:00
aj
9a7aa3261f allow multipart forms. add package api handler 2017-08-12 02:09:28 +00:00
aj
80d462c231 add prototype docs 2017-08-10 21:15:54 +00:00
aj
937a681c5d update examples 2017-08-10 20:54:34 +00:00
aj
5b11e2bca2 typo fix 2017-08-10 20:39:35 +00:00
aj
84505d1b0e Merge branch 'master' of git.daplie.com:OAuth3/oauth3.js 2017-08-10 20:35:59 +00:00
aj
52675f84c7 update docs and ng api 2017-08-10 20:35:24 +00:00
AJ ONeal
26ea3d931e setIssuer = setIdentityProvider; setAudience = setResourceProvider; 2017-08-09 11:28:03 -06:00
tigerbot
c250ab07f4 Merge branch 'master' into issuer-rewrite
# Conflicts:
#	oauth3.issuer.js
2017-08-07 10:55:43 -06:00
aj
ee631b97c7 org.oauth3.provider -> issuer@oauth3.org 2017-08-02 02:30:43 +00:00
aj
dac7c3936f Merge branch 'master' into v1.1 2017-08-02 01:52:27 +00:00
aj
0c0b85b1af Merge branch 'v1' into v1.1 2017-08-02 01:06:57 +00:00
aj
9c093ca3a1 match exact hostname for same-origin discovery 2017-08-02 01:06:33 +00:00
tigerbot
623d94e045 misc bug fixes 2017-08-01 15:08:21 -06:00
tigerbot
516eda4ea6 signing the token for the client in the browser 2017-08-01 14:21:11 -06:00
tigerbot
5d42f3e2cc using stored grants before fetching them from the server 2017-08-01 14:13:36 -06:00
aj
2cc96fef6e org.oauth3 -> oauth3.org 2017-08-01 18:04:25 +00:00
tigerbot
39b8e19bae added hook to create new session with stored key 2017-08-01 11:47:19 -06:00
tigerbot
e42079d856 changed default storage for grants and key pairs as well 2017-08-01 11:47:12 -06:00
aj
69add2a80f org.oauth3.provider -> issuer@oauth3.org 2017-08-01 17:28:27 +00:00
tigerbot
197c0fdcb2 changed how the default session/directive storage works 2017-07-31 16:42:22 -06:00
tigerbot
84a574e31b creating, publishing, and storing a key pair for remember_device 2017-07-28 17:55:19 -06:00
tigerbot
39c18ab184 added hooks to store key pairs in localStorage 2017-07-28 16:27:52 -06:00
tigerbot
5a5488f504 changed the API for most of the crypto functions
thus far I don't think anyone uses those functions so this should be safe
2017-07-28 13:02:36 -06:00
tigerbot
1993853d0d removed some files that shouldn't have been committed 2017-07-28 10:54:56 -06:00
tigerbot
28dbf9ab23 changed how grants are saved and how tokens are created for other clients 2017-07-26 18:15:09 -06:00
tigerbot
1ca6f0a324 updated how grants are retrieved 2017-07-26 16:27:03 -06:00
tigerbot
c38554a9dd added check for non-expired refresh token for session refresh 2017-07-25 17:18:14 -06:00
Drew Warren
7f1e67aaee Merge branch 'master' of git.daplie.com:OAuth3/oauth3.js 2017-07-12 16:11:09 -06:00
Drew Warren
e930881e0f Add devices.destroy API call 2017-07-12 16:11:01 -06:00
Drew Warren
3b34173ac8 Merge branch 'master' of git.daplie.com:OAuth3/oauth3.js 2017-07-07 11:45:43 -06:00
Drew Warren
146891a618 update README 2017-07-07 11:45:37 -06:00
Drew Warren
35405f8612 Merge branch 'master' of git.daplie.com:OAuth3/oauth3.js 2017-07-05 16:38:23 -06:00
Drew Warren
9574d9b982 Add subject to clientParams 2017-07-05 16:38:14 -06:00
Drew Warren
9f9610b6f5 Merge branch 'master' of git.daplie.com:OAuth3/oauth3.js 2017-07-05 16:16:58 -06:00
Drew Warren
186c0ea45a Add subject to implicitGrant 2017-07-05 16:16:51 -06:00
Drew Warren
9cb2ad036b Add removeAddress and extend functionality 2017-07-04 13:22:30 -06:00
tigerbot
1a43a58af1 fix bug that created invalid JSON for JWK thumbprint 2017-06-28 13:26:30 -06:00
Drew Warren
3879129674 WIP manual renewals - correct url 2017-06-23 15:01:19 -06:00
Drew Warren
d5befcaa39 semicolon 2017-06-21 16:52:30 -06:00
Drew Warren
33a0362524 WIP Manual Renew Function 2017-06-20 16:24:13 -06:00
tigerbot
5163463dd2 Merge remote-tracking branch 'origin/v1' 2017-06-15 12:46:59 -06:00
tigerbot
4a237b0703 fixed problem creating node storage directory 2017-06-15 12:45:19 -06:00
AJ ONeal
26be6411b5 Merge branch 'separate-providers' into 'master'
Separate providers

See merge request !4
2017-06-14 16:40:37 -06:00
43 changed files with 1876 additions and 3139 deletions

4
.gitignore vendored
View File

@ -1,3 +1,5 @@
prefactor
.well-known
node_modules/
DS_Store
.vscode
.vscode

2
.ignore Normal file
View File

@ -0,0 +1,2 @@
prefactor
.well-known

View File

@ -1 +1 @@
well-known
_apis

7
CHANGELOG Normal file
View 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
View 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.

122
README.md
View File

@ -1,6 +1,12 @@
oauth3.js
=========
| *oauth3.js*
| [issuer.html](https://git.oauth3.org/OAuth3/issuer.html)
| [issuer.rest.walnut.js](https://git.oauth3.org/OAuth3/issuer.rest.walnut.js)
| [issuer.srv](https://git.oauth3.org/OAuth3/issuer.srv)
| Sponsored by [ppl](https://ppl.family)
The world's smallest, fastest, and most secure OAuth3 (and OAuth2) JavaScript implementation
(Yes! works in browsers and node.js with no extra dependencies or bloat and no hacks!)
@ -19,13 +25,12 @@ If you have no idea what you're doing
1. Create a folder for your project named after your app, such as `example.com/`
2. Inside of the folder `example.com/` a folder called `assets/`
3. Inside of the folder `example.com/assets` a folder called `org.oauth3/`
4. Download [oauth3.js-v1.zip](https://git.daplie.com/OAuth3/oauth3.js/repository/archive.zip?ref=v1)
3. Inside of the folder `example.com/assets` a folder called `oauth3.org/`
4. Download [oauth3.js-v1.zip](https://git.oauth3.org/OAuth3/oauth3.js/repository/archive.zip?ref=v1)
5. Double-click to unzip the folder.
6. Copy the file `oauth3.core.js` into the folder `example.com/assets/org.oauth3/`
7. Copy the folder `well-known` into the folder `example.com/`
8. Rename the folder `well-known` to `.well-known` (when you do this, it become invisible, that's okay)
9. Add `<script src="assets/org.oauth3/oauth3.core.js"></script>` to your `index.html`
6. Copy the file `oauth3.core.js` into the folder `example.com/assets/oauth3.org/`
7. Copy the folder `_apis` into the folder `example.com/`
9. Add `<script src="assets/oauth3.org/oauth3.core.js"></script>` to your `index.html`
9. Add `<script src="app.js"></script>` to your `index.html`
10. Create files in `example.com` called `app.js` and `index.html` and put this in it:
@ -44,7 +49,7 @@ If you have no idea what you're doing
<script src="https://code.jquery.com/jquery-3.1.1.js"
integrity="sha256-16cdPddA6VdVInumRGo6IbivbERE8p7CQR3HzTBuELA="
crossorigin="anonymous"></script>
<script src="assets/org.oauth3/oauth3.core.js"></script>
<script src="assets/oauth3.org/oauth3.core.js"></script>
<script src="app.js"></script>
</body>
</html>
@ -53,13 +58,13 @@ If you have no idea what you're doing
`app.js`:
```js
var OAUTH3 = window.OAUTH3;
var auth = OAUTH3.create(window.location); // use window.location to set Client URI (your app's id)
var oauth3 = OAUTH3.create(window.location); // use window.location to set Client URI (your app's id)
// this is any OAuth3-compatible provider, such as oauth3.org
// in v1.1.0 we'll add backwards compatibility for facebook.com, google.com, etc
//
function onChangeProvider(_providerUri) {
function onChangeProvider(providerUri) {
// example https://oauth3.org
return oauth3.setIdentityProvider(providerUri);
}
@ -81,11 +86,13 @@ function onClickLogin() {
console.info('Secure PPID (aka subject):', session.token.sub);
return oauth3.request({
url: 'https://oauth3.org/api/org.oauth3.provider/inspect'
url: 'https://api.oauth3.org/api/issuer@oauth3.org/jwks/:sub/:kid'
.replace(/:sub/g, session.token.sub)
.replace(/:kid/g, session.token.kid || session.token.iss)
, session: session
}).then(function (resp) {
console.info("Inspect Token:");
console.info("Signing Public Key JWK:");
console.log(resp.data);
});
@ -138,13 +145,13 @@ it might look like this:
example.com
├── .well-known (hidden)
│   └── oauth3
├── _apis
│   └── oauth3.org
│   ├── callback.html
│   ├── directives.json
│   └── index.html
├── assets
│   └── org.oauth3
│   └── oauth3.org
│   └── oauth3.core.js
@ -165,17 +172,17 @@ Installation (if you know what you're doing)
pushd /path/to/your/web/app
# clone the project as assets/org.oauth3
# clone the project as assets/oauth3.org
mkdir -p assets
git clone git@git.daplie.com:OAuth3/oauth3.js.git assets/org.oauth3
pushd assets/org.oauth3
git clone git@git.oauth3.org:OAuth3/oauth3.js.git assets/oauth3.org
pushd assets/oauth3.org
git checkout v1
popd
# symlink `.well-known/oauth3` to `assets/org.oauth3/.well-known/oauth3`
mkdir -p .well-known
ln -sf ../assets/org.oauth3/.well-known/oauth3 .well-known/oauth3
# symlink `_apis/oauth3.org` to `assets/oauth3.org/_apis/oauth3.org`
mkdir -p _apis
ln -sf ../assets/oauth3.org/_apis/oauth3 _apis/oauth3.org
```
**Advanced Installation with `bower`**
@ -185,17 +192,17 @@ ln -sf ../assets/org.oauth3/.well-known/oauth3 .well-known/oauth3
bower install oauth3
# create a `.well-known` folder and an `assets` folder
mkdir -p .well-known assets
# create a `_apis` folder and an `assets` folder
mkdir -p _apis assets
# symlink `.well-known/oauth3` to `bower_components/oauth3/.well-known/oauth3`
ln -sf ../bower_components/oauth3/.well-known/oauth3 .well-known/oauth3
# symlink `_apis/oauth3.org` to `bower_components/oauth3.org/_apis/oauth3.org`
ln -sf ../bower_components/oauth3.org/_apis/oauth3.org _apis/oauth3.org
# symlink `assets/org.oauth3` to `bower_components/oauth3`
ln -sf ../bower_components/oauth3/.well-known/oauth3 .well-known/oauth3
ln -sf ../bower_components/oauth3 assets/org.oauth3
# symlink `assets/oauth3.org` to `bower_components/oauth3.org`
ln -sf ../bower_components/oauth3.org/_apis/oauth3.org _apis/oauth3.org
ln -sf ../bower_components/oauth3.org assets/oauth3.org
```
Usage
@ -204,7 +211,7 @@ Usage
Update your HTML to include the the following script tag:
```html
<script src="assets/org.oauth3/oauth3.core.js"></script>
<script src="assets/oauth3.org/oauth3.core.js"></script>
```
You can create a very simple demo application like this:
@ -239,7 +246,7 @@ function onClickLogin() {
console.info('Secure PPID (aka subject):', session.token.sub);
return OAUTH3.request({
url: 'https://oauth3.org/api/org.oauth3.provider/inspect_token'
url: 'https://oauth3.org/api/issuer@oauth3.org/inspect_token'
, session: session
}).then(function (resp) {
@ -260,6 +267,18 @@ function onClickLogin() {
onChangeProvider('oauth3.org');
```
A user's e-mail can be passed into the clientParams object as `clientParams.subject`.
To auto-populate the e-mail input of the login popup, make sure the input has the class `js-oauth3-email`.
Example:
```js
if (clientParams.subject) {
$('.js-oauth3-email').val(clientParams.subject);
$('.js-authn-show').prop('disabled', false);
}
```
### Compatibility with Frameworks and Libraries
**jQuery**:
@ -271,7 +290,19 @@ You're all set. Nothing else is needed.
We've created an `Oauth3` service just for you:
```html
<script src="assets/org.oauth3/oauth3.ng.js"></script>
<script src="assets/oauth3.org/oauth3.ng.js"></script>
```
```js
// Require the module as 'oauth3.org'
var app = angular.module('myAppName', [ 'ui.router', 'oauth3.org' ]);
// Require services and other submodules in the form {modulename}@oauth3.org
app.controller('authCtrl', [ '$scope', 'azp@oauth3.org', function ($scope, Oauth3) { /* ... */ } ]);
// For backwards compatibility with older angular applications that rely on string-name introspection
// you can also use the camel case version of the names in the format {Modulename}Oauth3
app.controller('authCtrl', function ($scope, AzpOauth3) { /* ... */ });
```
You can include that in addition to the standard file or,
@ -288,11 +319,11 @@ oauth3 = OAUTH3.create(location); // takes a location object,
// to create the Client URI (your app's id)
// and save it to an internal state
promise = oauth3.init(location); // set and fetch your own site/app's configuration details
// promises your site's config
promise = oauth3.init(opts); // set and fetch your own site/app's configuration details
// promises your site's config // opts = { location, session, issuer, audience }
promise = 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
promise = oauth3.setResourceProvider(url); // changes the Resource Provider URI (the site you're getting stuff from)
@ -305,14 +336,27 @@ promise = oauth3.authenticate(); // opens login window for th
promise = oauth3.authorize(permissions); // authenticates (if not authenticated) and opens a window to
// authorize a particular scope (contacts, photos, whatever)
promise = oauth3.request({ url, method, data }); // make an (authorized) request to a provider's resource
promise = oauth3.request({ url, method, data }); // make an (authorized) arbitrary request to an audience's resource
// (contacts, photos, whatever)
promise = oauth3.api(apiname, opts); // make an (authorized) well-known api call to an audience
// Ex: oauth3.api('dns.list', { sld: 'example', tld: 'com' });
// TODO
api = await oauth3.package(audience, schemaname); // make an (authorized) well-known api call to an audience
// Ex: api = await oauth3.package('domains.example.com', 'dns@oauth3.org');
// api.list({ sld: 'mydomain', tld: 'com' });
promise = oauth3.logout(); // opens logout window for the provider
oauth3.session(); // returns the current session, if any
```
<!-- TODO
Track down the old https://labs.daplie.com/docs/ for API schemas
--
Real API
----------
@ -440,10 +484,10 @@ Since we do not require the `protocol` to be specified, it is a URI
However, we do have a problem of disambiguation since a URI may look like a `path`:
1. https://example.com/api/org.oauth3.provider
2. example.com/api/org.oauth3.provider/ (not unique)
3. /api/org.oauth3.provider
4. api/org.oauth3.provider (not unique)
1. https://example.com/api/issuer@oauth3.org
2. example.com/api/issuer@oauth3.org/ (not unique)
3. /api/issuer@oauth3.org
4. api/issuer@oauth3.org (not unique)
Therefore anywhere a URI or a Path could be used, the URI must be a URL.
We eliminate #2.
@ -454,5 +498,5 @@ can be very ugly and confusing and we definitely need to allow relative paths.
A potential work-around would be to assume all paths are relative (eliminate #4 instead)
and have the path always key off of the base URL - if oauth3 directives are to be found at
https://example.com/username/.well-known/oauth3/directives.json then /api/whatever would refer
https://example.com/username/_apis/oauth3.org/index.json then /api/whatever would refer
to https://example.com/username/api/whatever.

BIN
_apis/oauth3/blank.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 B

View File

@ -15,7 +15,7 @@
<!-- TODO permanently cache with appcache (or service worker?) -->
<!-- TODO slim this all down to a single file -->
<script src="/assets/org.oauth3/oauth3.core.js"></script>
<script src="../../assets/oauth3.org/oauth3.core.js"></script>
<script>
;(function () {
'use strict';

BIN
_apis/oauth3/clear.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 B

View 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
View 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
View 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"
}

View File

@ -10,8 +10,8 @@ function parseArgs(argv, opts) {
var args = Array.prototype.slice.call(argv);
var sep = /[:\.\-]/;
args.shift(); // node
args.shift(); // oauth3.js
args.shift(); // 'node' is the first parameter
args.shift(); // 'oauth3.js' will be the
var command = args.shift() || 'help';
var cmdpair = command.split(sep);
@ -20,12 +20,12 @@ function parseArgs(argv, opts) {
var COMMAND = 'COMMAND';
var maxCmdLen = COMMAND.length;
var maxPairLen = 0;
var cmds;
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 = {};
cmds = opts.commands.filter(function (desc) {
var tlcs = opts.commands.filter(function (desc) {
var pair = desc[0].split(/\s+/)[0];
var psub = pair.split(sep)[0];
pairsMap[pair] = true;
@ -36,13 +36,7 @@ function parseArgs(argv, opts) {
}
});
if (-1 === Object.keys(pairsMap).indexOf(cmd)) {
console.log('fail', cmd);
arg1 = cmd;
cmd = 'help';
help();
}
// right pad (for making the printed lines longer)
function rpad(str, len) {
while (str.length < len) {
str += ' ';
@ -50,45 +44,124 @@ function parseArgs(argv, opts) {
return str;
}
// oauth3.js help
// oauth3.js help <command>
// oauth3.js help <command:sub> (alias of `oauth3.js <command:sub> --help')
function help() {
var status = 0;
function helpMain() {
console.log('');
console.log('Here are all the top-level commands:');
console.log('');
function printCmd(desc) {
var pcmd = rpad(desc[0].split(/\s+/)[0], maxCmdLen);
var pdesc = desc[1];
console.info('\t' + defaults.main + ' ' + pcmd, ' # ' + pdesc);
}
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 pdesc = desc[1];
console.log('\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)) {
status = 1;
console.log('');
console.log(defaults.main + ": Unknown command '" + arg1 + "'");
console.info('');
console.info(defaults.main + ": Unknown command '" + arg1 + "'");
console.info('');
console.info("Try '" + defaults.main + " help'");
console.info('');
arg1 = null;
return;
}
// the case of "oauth3 help --something"
if (!arg1 || '-' === arg1[0]) {
helpMain();
process.exit(status);
return;
}
// the case of "oauth3 help help"
if ('help' === arg1) {
helpMain();
console.log("no more help available for 'help'");
console.info("no more help available for 'help'");
process.exit(status);
return;
}
// matches the first part of the command
// and has second parts
if (arg1 === arg1.split(':')[0] && opts.commands.filter(function (desc) {
return arg1 === desc[0].split(/\s+/)[0].split(':')[0] && desc[0].split(/\s+/)[0].split(':');
}).length > 1) {
console.info('');
console.info("Here are all the '" + command + "'-related commands:");
printCmds(
opts.commands.filter(function (desc) {
var pair = desc[0].split(/\s+/)[0];
var psub = pair.split(sep)[0];
maxPairLen = Math.max(maxPairLen, pair.length);
if (arg1 === psub || arg1 === pair) {
maxCmdLen = Math.max(maxCmdLen, pair.length);
return true;
}
})
);
console.info('');
} else {
console.info('');
console.info("Here are all the options and flags for '" + arg1 + "':");
console.info('');
opts.commands.some(function (desc) {
var pair = desc[0].split(/\s+/)[0];
var psub = pair.split(sep)[0];
maxPairLen = Math.max(maxPairLen, pair.length);
if (arg1 !== psub && arg1 !== pair) {
return false;
}
maxCmdLen = Math.max(maxCmdLen, pair.length);
console.log('\t' + desc[0] + '\t# ' + desc[1]);
(desc[2]||[]).forEach(function (flag) {
var pair = flag.split(', ');
var f = pair.shift();
var d = pair.join(', ');
console.log('\t\t' + f + ' # ' + d);
});
return true;
});
console.info('');
}
}
// If the command is not in the list of commands
if (-1 === Object.keys(pairsMap).indexOf(cmd)) {
arg1 = cmd;
cmd = 'help';
help();
return;
}
// If help is explictly requested
if (-1 !== [ 'help', '-h', '--help' ].indexOf(command) || -1 !== args.indexOf('-h') || -1 !== args.indexOf('--help')) {
help();
return;
}
// If we're ready to rock and roll!
console.log('RUN', cmd, sub || '(n/a)', arg1 || '(n/a)', '... not yet implemented');
}
parseArgs(process.argv, {
@ -131,8 +204,8 @@ parseArgs(process.argv, {
// authn / authz
, [ 'devices', 'manages devices for your account(s)' ]
, [ 'devices:new', 'create a new device (default name is hostname, default ip is the result of :provider/api/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/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/tunnel@oauth3.org/checkip)'.replace(/\b:provider\b/, defaults.provider) ]
, [ 'devices:attach', "attach a device to a domain's DNS record" ]
, [ 'devices:detach', "detach an account from a domain's DNS record" ]
, [ 'devices:select', '(re)claim the specified device as this device (i.e. you re-installed your OS or deleted your ~/.oauth3)' ]

View File

@ -26,7 +26,7 @@
"sign"
],
"license": "MIT",
"homepage": "https://git.daplie.com/OAuth3/oauth3.js",
"homepage": "https://git.oauth3.org/OAuth3/oauth3.js",
"ignore": [
"**/.*",
"browserify",

View File

@ -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
View 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);
});
});
});
}
};
}());

View File

@ -70,4 +70,17 @@ OAUTH3.api['account.addAddress'] = function (providerUri, opts) {
});
};
OAUTH3.api['account.removeAddress'] = function (providerUri, opts) {
var session = opts.session;
return OAUTH3.request({
method: 'DELETE'
, url: OAUTH3.url.normalize(providerUri)
+ '/api/com.daplie.me/accounts/' + session.token.sub + '/addresses/' + opts.addressId
, session: session
}).then(function (res) {
return res;
});
};
}('undefined' !== typeof exports ? exports : window));

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
;(function (exports) {
'use strict';
'use strict';
var OAUTH3 = exports.OAUTH3 = exports.OAUTH3 || require('./oauth3.core.js').OAUTH3;
@ -8,6 +8,9 @@
OAUTH3.crypto.core = require('./oauth3.node.crypto');
} catch (error) {
OAUTH3.crypto.core = {};
OAUTH3.crypto.core.ready = false;
var finishBeforeReady = [];
var deferedCalls = [];
// We don't currently have a fallback method for this function, so we assign
// it directly to the core object instead of the webCrypto object.
@ -17,10 +20,31 @@
};
var webCrypto = {};
var deferCryptoCall = function(name) {
return function() {
var args = arguments;
return new OAUTH3.PromiseA(function(resolve, reject) {
deferedCalls.push(function(){
try {
webCrypto[name].apply(webCrypto, args)
.then(function(result){
resolve(result);
});
} catch(e) {
reject(e);
}
});
});
};
};
OAUTH3.crypto.core.sha256 = deferCryptoCall("sha256");
webCrypto.sha256 = function (buf) {
return OAUTH3._browser.window.crypto.subtle.digest({name: 'SHA-256'}, buf);
};
OAUTH3.crypto.core.pbkdf2 = deferCryptoCall("pbkdf2");
webCrypto.pbkdf2 = function (password, salt) {
return OAUTH3._browser.window.crypto.subtle.importKey('raw', OAUTH3._binStr.binStrToBuffer(password), {name: 'PBKDF2'}, false, ['deriveKey'])
.then(function (key) {
@ -32,12 +56,15 @@
});
};
OAUTH3.crypto.core.encrypt = deferCryptoCall("encrypt");
webCrypto.encrypt = function (rawKey, iv, data) {
return OAUTH3._browser.window.crypto.subtle.importKey('raw', rawKey, {name: 'AES-GCM'}, false, ['encrypt'])
.then(function (key) {
return OAUTH3._browser.window.crypto.subtle.encrypt({name: 'AES-GCM', iv: iv}, key, data);
});
};
OAUTH3.crypto.core.decrypt = deferCryptoCall("decrypt");
webCrypto.decrypt = function (rawKey, iv, data) {
return OAUTH3._browser.window.crypto.subtle.importKey('raw', rawKey, {name: 'AES-GCM'}, false, ['decrypt'])
.then(function (key) {
@ -45,6 +72,7 @@
});
};
OAUTH3.crypto.core.genEcdsaKeyPair = deferCryptoCall("genEcdsaKeyPair");
webCrypto.genEcdsaKeyPair = function () {
return OAUTH3._browser.window.crypto.subtle.generateKey({name: 'ECDSA', namedCurve: 'P-256'}, true, ['sign', 'verify'])
.then(function (keyPair) {
@ -57,6 +85,7 @@
});
};
OAUTH3.crypto.core.sign = deferCryptoCall("sign");
webCrypto.sign = function (jwk, msg) {
return OAUTH3._browser.window.crypto.subtle.importKey('jwk', jwk, {name: 'ECDSA', namedCurve: jwk.crv}, false, ['sign'])
.then(function (key) {
@ -66,6 +95,8 @@
return new Uint8Array(sig);
});
};
OAUTH3.crypto.core.verify = deferCryptoCall("verify");
webCrypto.verify = function (jwk, msg, signature) {
// If the JWK has properties that should only exist on the private key or is missing
// "verify" in the key_ops, importing in as a public key won't work.
@ -82,6 +113,7 @@
};
function checkWebCrypto() {
/* global OAUTH3_crypto_fallback */
var loadFallback = function() {
var prom;
loadFallback = function () { return prom; };
@ -96,25 +128,25 @@
resolve();
}
};
script.src = '/assets/org.oauth3/oauth3.crypto.fallback.js';
script.src = '/assets/oauth3.org/oauth3.crypto.fallback.js';
body.appendChild(script);
});
return prom;
};
function checkException(name, func) {
new OAUTH3.PromiseA(function (resolve) { resolve(func()); })
return OAUTH3.PromiseA.resolve().then(func)
.then(function () {
OAUTH3.crypto.core[name] = webCrypto[name];
})
.catch(function (err) {
}, function (err) {
console.warn('error with WebCrypto', name, '- using fallback', err);
loadFallback().then(function () {
return loadFallback().then(function () {
OAUTH3.crypto.core[name] = OAUTH3_crypto_fallback[name];
});
});
}
function checkResult(name, expected, func) {
checkException(name, function () {
finishBeforeReady.push(checkException(name, function () {
return func()
.then(function (result) {
if (typeof expected === typeof result) {
@ -127,7 +159,7 @@
throw new Error("result ("+result+") doesn't match expectation ("+expected+")");
}
});
});
}));
}
var zeroBuf = new Uint8Array([0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]);
@ -159,12 +191,20 @@
return webCrypto.verify(jwk, dataBuf, sig);
});
// The results of these functions are less predictable, so we can't check their return value.
checkException('genEcdsaKeyPair', function () {
finishBeforeReady.push(checkException('genEcdsaKeyPair', function () {
return webCrypto.genEcdsaKeyPair();
});
checkException('sign', function () {
}));
finishBeforeReady.push(checkException('sign', function () {
return webCrypto.sign(jwk, dataBuf);
});
}));
OAUTH3.PromiseA.all(finishBeforeReady)
.then(function(results) {
OAUTH3.crypto.core.ready = true;
deferedCalls.forEach(function(request) {
request();
});
});
}
checkWebCrypto();
}
@ -188,106 +228,68 @@
return OAUTH3.PromiseA.reject(new Error('JWK of type '+jwk.kty+' missing fields ' + missing));
}
var jwkStr = '{' + keys.map(function (name) { return name+':'+jwk[name]; }).join(',') + '}';
// I'm not actually 100% sure this behavior is guaranteed, but when we use an array as the
// replacer argument the keys are always in the order they appeared in the array.
var jwkStr = JSON.stringify(jwk, keys);
return OAUTH3.crypto.core.sha256(OAUTH3._binStr.binStrToBuffer(jwkStr))
.then(OAUTH3._base64.bufferToUrlSafe);
};
OAUTH3.crypto._createKey = function (ppid) {
var saltProm = OAUTH3.crypto.core.randomBytes(16);
var kekProm = saltProm.then(function (salt) {
return OAUTH3.crypto.core.pbkdf2(ppid, salt);
});
var ecdsaProm = OAUTH3.crypto.core.genEcdsaKeyPair()
.then(function (keyPair) {
OAUTH3.crypto.createKeyPair = function () {
// TODO: maybe support other types of key pairs, not just ECDSA P-256
return OAUTH3.crypto.core.genEcdsaKeyPair().then(function (keyPair) {
return OAUTH3.crypto.thumbprintJwk(keyPair.publicKey).then(function (kid) {
keyPair.privateKey.alg = keyPair.publicKey.alg = 'ES256';
keyPair.privateKey.kid = keyPair.publicKey.kid = kid;
return keyPair;
});
});
};
OAUTH3.crypto.encryptKeyPair = function (keyPair, password) {
var saltProm = OAUTH3.crypto.core.randomBytes(16);
var kekProm = saltProm.then(function (salt) {
return OAUTH3.crypto.core.pbkdf2(password, salt);
});
return OAUTH3.PromiseA.all([
kekProm
, ecdsaProm
, saltProm
, OAUTH3.crypto.core.randomBytes(16)
, OAUTH3.crypto.core.randomBytes(12)
, OAUTH3.crypto.core.randomBytes(12)
]).then(function (results) {
, ]).then(function (results) {
var kek = results[0];
var keyPair = results[1];
var salt = results[2];
var userSecret = results[3];
var ecdsaIv = results[4];
var secretIv = results[5];
var salt = results[1];
var ecdsaIv = results[2];
return OAUTH3.PromiseA.all([
OAUTH3.crypto.core.encrypt(kek, ecdsaIv, OAUTH3._binStr.binStrToBuffer(JSON.stringify(keyPair.privateKey)))
, OAUTH3.crypto.core.encrypt(kek, secretIv, userSecret)
])
.then(function (encrypted) {
var privKeyBuf = OAUTH3._binStr.binStrToBuffer(JSON.stringify(keyPair.privateKey));
return OAUTH3.crypto.core.encrypt(kek, ecdsaIv, privKeyBuf).then(function (encrypted) {
return {
publicKey: keyPair.publicKey
, privateKey: OAUTH3._base64.bufferToUrlSafe(encrypted[0])
, userSecret: OAUTH3._base64.bufferToUrlSafe(encrypted[1])
, privateKey: OAUTH3._base64.bufferToUrlSafe(encrypted)
, salt: OAUTH3._base64.bufferToUrlSafe(salt)
, ecdsaIv: OAUTH3._base64.bufferToUrlSafe(ecdsaIv)
, secretIv: OAUTH3._base64.bufferToUrlSafe(secretIv)
};
, };
});
});
};
OAUTH3.crypto._decryptKey = function (ppid, storedObj) {
OAUTH3.crypto.decryptKeyPair = function (storedObj, password) {
var salt = OAUTH3._base64.urlSafeToBuffer(storedObj.salt);
var encJwk = OAUTH3._base64.urlSafeToBuffer(storedObj.privateKey);
var iv = OAUTH3._base64.urlSafeToBuffer(storedObj.ecdsaIv);
return OAUTH3.crypto.core.pbkdf2(ppid, salt)
return OAUTH3.crypto.core.pbkdf2(password, salt)
.then(function (key) {
return OAUTH3.crypto.core.decrypt(key, iv, encJwk);
})
.then(OAUTH3._binStr.bufferToBinStr)
.then(JSON.parse);
};
OAUTH3.crypto._getKey = function (ppid) {
return OAUTH3.crypto.core.sha256(OAUTH3._binStr.binStrToBuffer(ppid))
.then(function (hash) {
var name = 'kek-' + OAUTH3._base64.bufferToUrlSafe(hash);
var promise;
if (window.localStorage.getItem(name) === null) {
promise = OAUTH3.crypto._createKey(ppid).then(function (key) {
window.localStorage.setItem(name, JSON.stringify(key));
return key;
});
} else {
promise = OAUTH3.PromiseA.resolve(JSON.parse(window.localStorage.getItem(name)));
}
return promise.then(function (storedObj) {
return OAUTH3.crypto._decryptKey(ppid, storedObj);
.then(JSON.parse)
.then(function (privateKey) {
return {
privateKey: privateKey
, publicKey: storedObj.publicKey
, };
});
});
};
OAUTH3.crypto._signPayload = function (payload) {
return OAUTH3.crypto._getKey('some PPID').then(function (key) {
var header = {type: 'JWT', alg: key.alg, kid: key.kid};
var input = [
OAUTH3._base64.encodeUrlSafe(JSON.stringify(header, null))
, OAUTH3._base64.encodeUrlSafe(JSON.stringify(payload, null))
].join('.');
return OAUTH3.crypto.core.sign(key, OAUTH3._binStr.binStrToBuffer(input))
.then(OAUTH3._base64.bufferToUrlSafe)
.then(function (signature) {
return input + '.' + signature;
});
});
};
}('undefined' !== typeof exports ? exports : window));

View File

@ -71,12 +71,27 @@ OAUTH3.api['devices.detach'] = function (providerUri, opts) {
});
};
OAUTH3.api['devices.destroy'] = function (providerUri, opts) {
var session = opts.session;
var device = opts.device;
return OAUTH3.request({
url: OAUTH3.url.normalize(providerUri)
+ '/api/com.daplie.domains/accounts/' + session.token.sub
+ '/devices/' + device
, method: 'DELETE'
, session: session
}, {}).then(function (res) {
return res.data.device || res.data;
});
};
OAUTH3.api['dns.set'] = function (providerUri, opts) {
var session = opts.session;
var tld = opts.tld;
var sld = opts.sld;
var sub = opts.sub;
var type = opts.selected;
var type = opts.type;
var value = opts.value;
var ttl = opts.ttl;
var priority = (opts.priority || '');

View File

@ -45,6 +45,22 @@ OAUTH3.api['domains.list'] = function (providerUri, opts) {
});
};
// TODO: Manual Renew Function
OAUTH3.api['domains.extend'] = function (providerUri, opts) {
var session = opts.session;
return OAUTH3.request({
method: 'POST'
, url: OAUTH3.url.normalize(providerUri)
+ '/api/com.daplie.domains/accounts/' + session.token.sub + '/registrations/' + opts.data.tld + '/' + opts.data.sld + '/extend'
, session: session
, data: opts.data
}).then(function (res) {
return res;
});
};
OAUTH3.api['ns.list'] = function (providerUri, opts) {
var session = opts.session;
var domain = opts.domain;
@ -77,7 +93,7 @@ OAUTH3.api['ns.list'] = function (providerUri, opts) {
};
OAUTH3.api['ns.add'] = function (providerUri, opts) {
var session = opts.session
var session = opts.session;
var server = opts.server;
var tld = opts.tld;
var sld = opts.sld;

File diff suppressed because it is too large Load Diff

View File

@ -27,10 +27,10 @@
OAUTH3.authz.scopes = function () {
return OAUTH3.PromiseA.resolve({
pending: ['oauth3_authn'] // not yet accepted
, granted: [] // all granted, ever
, requested: ['oauth3_authn'] // all requested, now
, accepted: [] // granted (ever) and requested (now)
pending: [ 'authn@oauth3.org' ] // not yet accepted
, granted: [] // all granted, ever
, requested: [ 'authn@oauth3.org' ] // all requested, now
, accepted: [] // granted (ever) and requested (now)
});
};
OAUTH3.authz.grants = function (providerUri, opts) {

View File

@ -1,38 +1,48 @@
;(function () {
'use strict';
angular
.module('org.oauth3', [])
.service('Oauth3', [
var modules = {
azp: [
'$timeout'
, '$q'
, function Oauth3($timeout, $q) {
, '$rootScope'
, function Oauth3($timeout, $q, $rootScope) {
var OAUTH3 = window.OAUTH3;
var OAUTH3 = window.OAUTH3;
// We need to make angular's $q appear to be a standard Promise/A+
// fortunately, this is pretty easy
function PromiseAngularQ(fn) {
var d = $q.defer();
// We need to make angular's $q appear to be a standard Promise/A+
// fortunately, this is pretty easy
function PromiseAngularQ(fn) {
var d = $q.defer();
//$timeout(function () {
fn(d.resolve, d.reject);
//}, 0);
//$timeout(function () {
fn(d.resolve, d.reject);
//}, 0);
//this.then = d.promise.then;
//this.catch = d.promise.catch;
return d.promise;
}
//this.then = d.promise.then;
//this.catch = d.promise.catch;
return d.promise;
//PromiseAngularQ.create = PromiseAngularQ;
PromiseAngularQ.resolve = $q.when;
PromiseAngularQ.reject = $q.reject;
PromiseAngularQ.all = $q.all;
OAUTH3.PromiseA = PromiseAngularQ;
OAUTH3._digest = function () {
$rootScope.$digest();
};
window.ngOauth3 = OAUTH3;
return OAUTH3;
}
]
};
//PromiseAngularQ.create = PromiseAngularQ;
PromiseAngularQ.resolve = $q.when;
PromiseAngularQ.reject = $q.reject;
PromiseAngularQ.all = $q.all;
OAUTH3.PromiseA = PromiseAngularQ;
window.ngOauth3 = OAUTH3;
return OAUTH3;
}]);
angular
.module('oauth3.org', [])
.service('azp@oauth3.org', modules.azp)
.service('AzpOauth3', modules.azp)
;
}());

View File

@ -28,6 +28,7 @@ OAUTH3._base64.atob = function (base64) {
OAUTH3._base64.btoa = function (text) {
return new Buffer(text, 'utf8').toString('base64');
};
OAUTH3._defaultStorage = require('./oauth3.node.storage');
OAUTH3._node = {};
OAUTH3._node.discover = function(providerUri/*, opts*/) {
@ -43,6 +44,7 @@ OAUTH3._node.request = function(preq/*, _sys*/) {
method: preq.method
, url: preq.url || preq.uri
, headers: preq.headers
, timeout: preq.timeout || undefined
, json: preq.data || preq.body || preq.json || undefined // TODO which to use?
, formData: preq.formData || undefined
};
@ -59,10 +61,7 @@ OAUTH3._node._parseJson = function (resp) {
// TODO toCamelCase
if (!(resp.statusCode >= 200 && resp.statusCode < 400)) {
// console.log('[A3] DEBUG', resp.body);
err = new Error("bad response code: " + resp.statusCode);
err.result = resp.body;
return PromiseA.reject(err);
}
//console.log('resp.body', typeof resp.body);
@ -70,15 +69,16 @@ OAUTH3._node._parseJson = function (resp) {
try {
json = JSON.parse(json);
} catch(e) {
err = new Error('response not parsable:' + resp.body);
err.result = resp.body;
return PromiseA.reject(err);
err = err || (new Error('response not parsable: ' + resp.body));
}
}
// handle both Oauth2- and node-style errors
if (json.error) {
err = new Error(json.error && json.error.message || json.error_description || json.error);
if (json && json.error) {
err = new Error(json.error.message || json.error_description || JSON.stringify(json.error));
}
if (err) {
err.result = json;
return PromiseA.reject(err);
}

View File

@ -9,6 +9,11 @@ var sessionsdir = path.join(oauth3dir, 'sessions');
var directivesdir = path.join(oauth3dir, 'directives');
var metadir = path.join(oauth3dir, 'meta');
// We can reasonably assume the existence of the home directory, but we can't assume
// that there will already be a `.oauth3` directory or anything inside of it.
if (!fs.existsSync(path.join(oauth3dir, '..'))) {
fs.mkdirSync(path.join(oauth3dir, '..'));
}
if (!fs.existsSync(oauth3dir)) {
fs.mkdirSync(oauth3dir);
}
@ -62,10 +67,9 @@ module.exports = {
, sessions: {
all: function (providerUri) {
var dirname = path.join(oauth3dir, 'sessions');
return fs.readdirAsync(dirname).then(function (nodes) {
return fs.readdirAsync(sessionsdir).then(function (nodes) {
return nodes.map(function (node) {
var result = require(path.join(dirname, node));
var result = require(path.join(sessionsdir, node));
if (result.link) {
return null;
}
@ -86,7 +90,7 @@ module.exports = {
result = require(path.join(sessionsdir, providerUri + '.json'));
// TODO make safer
if (result.link && '/' !== result.link[0] && !/\.\./.test(result.link)) {
result = require(path.join(oauth3dir, 'sessions', result.link));
result = require(path.join(sessionsdir, result.link));
}
}
} catch(e) {
@ -108,10 +112,9 @@ module.exports = {
});
}
, clear: function () {
var dirname = path.join(oauth3dir, 'sessions');
return fs.readdirAsync(dirname).then(function (nodes) {
return fs.readdirAsync(sessionsdir).then(function (nodes) {
return PromiseA.all(nodes.map(function (node) {
return fs.unlinkAsync(path.join(dirname, node));
return fs.unlinkAsync(path.join(sessionsdir, node));
}));
});
}

View File

@ -1,66 +0,0 @@
{
"provider_uri": "https://oauth3.org",
"client_uri": "oauth3.org",
"token_type": "bearer",
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJqdGkiOiIzOTZjMzJlNzE1NmIzNGI1ZWY1ZWYwZWU4Zjk3Y2NiMyIsImlhdCI6MTQ5MDE5NzUyNywiaXNzIjoib2F1dGgzLm9yZyIsImF1ZCI6Im9hdXRoMy5vcmciLCJhenAiOiJvYXV0aDMub3JnIiwic3ViIjoiIiwia2lkIjoib2F1dGgzLm9yZyIsInNjcCI6IiIsImFzIjoibG9naW4iLCJncnQiOiJwYXNzd29yZCIsInNydiI6ZmFsc2UsImsiOiJvYXV0aDMub3JnIiwiYXBwIjoib2F1dGgzLm9yZyIsImFjeCI6eyJpZCI6IjE1LUxhM3JnZXBFelBCR0xITHlrdEZOT1NDZFNVOXZJdjlKc2EzTkMxYVJUc3ZmUTZ5cDJuVFFfZWxmdkhzYTEifSwiYXhzIjpbXSwidXNyIjoiYTM3YWVkYTk5ZDQ5MThhMDM0YzM0MmQ2NGNkZjRiN2VkMjM0ZGZlNSIsImFjYyI6eyJpZCI6IjUzZTUwMTk2LTE4ZTMtNGJlNi04NDcyLTQ1ZDBjNDMxZjdhZCJ9LCJhY3MiOltdLCJpZHgiOiJuZnZ1bHRETE0tT0EzVUV3dVJHTDE3RFY1UXpIbWhac005Z2xMdnFLVGJacGh1T0NqMnBEUzByRk9XSXhaRjZLIiwidG9rZW5UeXBlIjoiYmVhcmVyIiwiZXhwIjoxNDkwMTk5MzI3LCJpcCI6IjIwNy4xNzMuMTY1LjUwIn0.qQu6NdsU4oVucv4uV_jusfL2HKgnPpfwF6iVG0H-P08akDtGgDoXcyVfl6hQdpVL9DGYVwvCPPUkLT0bJztM08lWhg69dVs-2e2I2BhjClsKeLsrFDBrUMwWVqqzCNVj8WBzcULLtl_mEgZc1qwVpZvXXiu0vmrRl3gtzVRaLL0",
"scope": "",
"expires_in": 1800,
"refresh_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJqdGkiOiI0OGJhNjJhNTQxNGFlODQ3OWJhMzA0MGQ1Mzc5NmY3MiIsImlhdCI6MTQ5MDE5NzUyNywiaXNzIjoib2F1dGgzLm9yZyIsImF1ZCI6Im9hdXRoMy5vcmciLCJhenAiOiJvYXV0aDMub3JnIiwic3ViIjoiIiwia2lkIjoib2F1dGgzLm9yZyIsInNjcCI6IiIsImFzIjoibG9naW4iLCJncnQiOiJwYXNzd29yZCIsInNydiI6ZmFsc2UsImsiOiJvYXV0aDMub3JnIiwiYXBwIjoib2F1dGgzLm9yZyIsImFjeCI6eyJpZCI6IjE1LUxhM3JnZXBFelBCR0xITHlrdEZOT1NDZFNVOXZJdjlKc2EzTkMxYVJUc3ZmUTZ5cDJuVFFfZWxmdkhzYTEifSwiYXhzIjpbXSwidXNyIjoiYTM3YWVkYTk5ZDQ5MThhMDM0YzM0MmQ2NGNkZjRiN2VkMjM0ZGZlNSIsImFjYyI6eyJpZCI6IjUzZTUwMTk2LTE4ZTMtNGJlNi04NDcyLTQ1ZDBjNDMxZjdhZCJ9LCJhY3MiOltdLCJpZHgiOiJuZnZ1bHRETE0tT0EzVUV3dVJHTDE3RFY1UXpIbWhac005Z2xMdnFLVGJacGh1T0NqMnBEUzByRk9XSXhaRjZLIiwicmVmcmVzaCI6dHJ1ZX0.QfufVyAGit2YOy9Hs9mv4eoCuyCYb9FDT_UXGd3JaFZe6MwqxLLnq2fWkkV2jgzDAK5t0JMu2Vk91jPP2IBXMkpZSzjaEKJ3-Eokb14Mo5GIrp54ndM20gWVZc-ReQtOUtSVG28bfnOBT5ceUM6SBrTxfz1ENOfmAiWl5591roQ",
"token": {
"jti": "396c32e7156b34b5ef5ef0ee8f97ccb3",
"iat": 1490197527,
"iss": "oauth3.org",
"aud": "oauth3.org",
"azp": "oauth3.org",
"sub": "15-La3rgepEzPBGLHLyktFNOSCdSU9vIv9Jsa3NC1aRTsvfQ6yp2nTQ_elfvHsa1",
"kid": "oauth3.org",
"scp": "",
"as": "login",
"grt": "password",
"srv": false,
"k": "oauth3.org",
"app": "oauth3.org",
"acx": {
"id": "15-La3rgepEzPBGLHLyktFNOSCdSU9vIv9Jsa3NC1aRTsvfQ6yp2nTQ_elfvHsa1"
},
"axs": [],
"usr": "a37aeda99d4918a034c342d64cdf4b7ed234dfe5",
"acc": {
"id": "53e50196-18e3-4be6-8472-45d0c431f7ad"
},
"acs": [],
"idx": "nfvultDLM-OA3UEwuRGL17DV5QzHmhZsM9glLvqKTbZphuOCj2pDS0rFOWIxZF6K",
"tokenType": "bearer",
"exp": 1490199327,
"ip": "207.173.165.50",
"client_uri": "oauth3.org",
"provider_uri": "https://oauth3.org"
},
"refresh": {
"jti": "48ba62a5414ae8479ba3040d53796f72",
"iat": 1490197527,
"iss": "oauth3.org",
"aud": "oauth3.org",
"azp": "oauth3.org",
"sub": "15-La3rgepEzPBGLHLyktFNOSCdSU9vIv9Jsa3NC1aRTsvfQ6yp2nTQ_elfvHsa1",
"kid": "oauth3.org",
"scp": "",
"as": "login",
"grt": "password",
"srv": false,
"k": "oauth3.org",
"app": "oauth3.org",
"acx": {
"id": "15-La3rgepEzPBGLHLyktFNOSCdSU9vIv9Jsa3NC1aRTsvfQ6yp2nTQ_elfvHsa1"
},
"axs": [],
"usr": "a37aeda99d4918a034c342d64cdf4b7ed234dfe5",
"acc": {
"id": "53e50196-18e3-4be6-8472-45d0c431f7ad"
},
"acs": [],
"idx": "nfvultDLM-OA3UEwuRGL17DV5QzHmhZsM9glLvqKTbZphuOCj2pDS0rFOWIxZF6K",
"refresh": true,
"provider_uri": "https://oauth3.org"
}
}

View File

@ -1,66 +0,0 @@
{
"provider_uri": "https://oauth3.org",
"client_uri": "oauth3.org",
"token_type": "bearer",
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJqdGkiOiI4MmU5MDhlMTljMmIxM2IxNmM3N2JlMTNkMTljZmEzOSIsImlhdCI6MTQ5MDEzODg2MiwiaXNzIjoib2F1dGgzLm9yZyIsImF1ZCI6Im9hdXRoMy5vcmciLCJhenAiOiJvYXV0aDMub3JnIiwic3ViIjoiIiwia2lkIjoib2F1dGgzLm9yZyIsInNjcCI6IiIsImFzIjoibG9naW4iLCJncnQiOiJwYXNzd29yZCIsInNydiI6ZmFsc2UsImsiOiJvYXV0aDMub3JnIiwiYXBwIjoib2F1dGgzLm9yZyIsImFjeCI6eyJpZCI6IjE1LUxhM3JnZXBFelBCR0xITHlrdEZOT1NDZFNVOXZJdjlKc2EzTkMxYVJUc3ZmUTZ5cDJuVFFfZWxmdkhzYTEifSwiYXhzIjpbXSwidXNyIjoiYTM3YWVkYTk5ZDQ5MThhMDM0YzM0MmQ2NGNkZjRiN2VkMjM0ZGZlNSIsImFjYyI6eyJpZCI6IjUzZTUwMTk2LTE4ZTMtNGJlNi04NDcyLTQ1ZDBjNDMxZjdhZCJ9LCJhY3MiOltdLCJpZHgiOiJuZnZ1bHRETE0tT0EzVUV3dVJHTDE3RFY1UXpIbWhac005Z2xMdnFLVGJacGh1T0NqMnBEUzByRk9XSXhaRjZLIiwidG9rZW5UeXBlIjoiYmVhcmVyIiwiZXhwIjoxNDkwMTQwNjYyLCJpcCI6IjIwNy4xNzMuMTY1LjUwIn0.luWnALBIv9TD_mGHUIddRpOnbVAhkYO-DJtEQitODQX2IEC7cIcbSrvJBKI3i_djeMj69fm-ctr6XFUU7WiEVnBsMh55WK1gkdFqFzImo67apQ5kAV8GTGGbG___kisjl12AMvL09_shU1Sp8F8cHayTZTmSbyyWbbFKT3cZCsg",
"scope": "",
"expires_in": 1800,
"refresh_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJqdGkiOiI1ZDBjMGMyNTkyNmZhNmMxOTM0MjlhZWRkMjJjOTEyYiIsImlhdCI6MTQ5MDEzODg2MiwiaXNzIjoib2F1dGgzLm9yZyIsImF1ZCI6Im9hdXRoMy5vcmciLCJhenAiOiJvYXV0aDMub3JnIiwic3ViIjoiIiwia2lkIjoib2F1dGgzLm9yZyIsInNjcCI6IiIsImFzIjoibG9naW4iLCJncnQiOiJwYXNzd29yZCIsInNydiI6ZmFsc2UsImsiOiJvYXV0aDMub3JnIiwiYXBwIjoib2F1dGgzLm9yZyIsImFjeCI6eyJpZCI6IjE1LUxhM3JnZXBFelBCR0xITHlrdEZOT1NDZFNVOXZJdjlKc2EzTkMxYVJUc3ZmUTZ5cDJuVFFfZWxmdkhzYTEifSwiYXhzIjpbXSwidXNyIjoiYTM3YWVkYTk5ZDQ5MThhMDM0YzM0MmQ2NGNkZjRiN2VkMjM0ZGZlNSIsImFjYyI6eyJpZCI6IjUzZTUwMTk2LTE4ZTMtNGJlNi04NDcyLTQ1ZDBjNDMxZjdhZCJ9LCJhY3MiOltdLCJpZHgiOiJuZnZ1bHRETE0tT0EzVUV3dVJHTDE3RFY1UXpIbWhac005Z2xMdnFLVGJacGh1T0NqMnBEUzByRk9XSXhaRjZLIiwicmVmcmVzaCI6dHJ1ZX0.XljVX_QXgnYw8gyIZjQdxfyDlAAwWtU-kLitibw2_3xo9muLFPCL_dAk5XnMRygyyh5B9H4p4qB2Gb5BEJKJRfAtQ6TeZadTBtxwoY7zcns9f4Nx59VNii4k_Xp3uhJ6fQp8ERvkMgBy52Sj5ag0PFnuIwk35wLdSfiikDGnwKo",
"token": {
"jti": "82e908e19c2b13b16c77be13d19cfa39",
"iat": 1490138862,
"iss": "oauth3.org",
"aud": "oauth3.org",
"azp": "oauth3.org",
"sub": "15-La3rgepEzPBGLHLyktFNOSCdSU9vIv9Jsa3NC1aRTsvfQ6yp2nTQ_elfvHsa1",
"kid": "oauth3.org",
"scp": "",
"as": "login",
"grt": "password",
"srv": false,
"k": "oauth3.org",
"app": "oauth3.org",
"acx": {
"id": "15-La3rgepEzPBGLHLyktFNOSCdSU9vIv9Jsa3NC1aRTsvfQ6yp2nTQ_elfvHsa1"
},
"axs": [],
"usr": "a37aeda99d4918a034c342d64cdf4b7ed234dfe5",
"acc": {
"id": "53e50196-18e3-4be6-8472-45d0c431f7ad"
},
"acs": [],
"idx": "nfvultDLM-OA3UEwuRGL17DV5QzHmhZsM9glLvqKTbZphuOCj2pDS0rFOWIxZF6K",
"tokenType": "bearer",
"exp": 1490140662,
"ip": "207.173.165.50",
"client_uri": "oauth3.org",
"provider_uri": "https://oauth3.org"
},
"refresh": {
"jti": "5d0c0c25926fa6c193429aedd22c912b",
"iat": 1490138862,
"iss": "oauth3.org",
"aud": "oauth3.org",
"azp": "oauth3.org",
"sub": "15-La3rgepEzPBGLHLyktFNOSCdSU9vIv9Jsa3NC1aRTsvfQ6yp2nTQ_elfvHsa1",
"kid": "oauth3.org",
"scp": "",
"as": "login",
"grt": "password",
"srv": false,
"k": "oauth3.org",
"app": "oauth3.org",
"acx": {
"id": "15-La3rgepEzPBGLHLyktFNOSCdSU9vIv9Jsa3NC1aRTsvfQ6yp2nTQ_elfvHsa1"
},
"axs": [],
"usr": "a37aeda99d4918a034c342d64cdf4b7ed234dfe5",
"acc": {
"id": "53e50196-18e3-4be6-8472-45d0c431f7ad"
},
"acs": [],
"idx": "nfvultDLM-OA3UEwuRGL17DV5QzHmhZsM9glLvqKTbZphuOCj2pDS0rFOWIxZF6K",
"refresh": true,
"provider_uri": "https://oauth3.org"
}
}

View File

@ -9,7 +9,7 @@ OAUTH3.api['tunnel.token'] = function (providerUri, opts) {
return OAUTH3.request({
method: 'POST'
, url: OAUTH3.url.normalize(providerUri)
+ '/api/org.oauth3.tunnel/accounts/' + session.token.sub + '/token'
+ '/api/tunnel@oauth3.org/accounts/' + session.token.sub + '/token'
, session: session
, data: {
domains: opts.data.domains

View File

@ -1,6 +1,6 @@
{
"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.",
"main": "oauth3.node.js",
"scripts": {
@ -9,7 +9,7 @@
},
"repository": {
"type": "git",
"url": "git@git.daplie.com:OAuth3/oauth3.js.git"
"url": "git@git.oauth3.org:OAuth3/oauth3.js.git"
},
"keywords": [
"oauth",
@ -35,7 +35,7 @@
"bluebird": "^3.5.0",
"elliptic": "^6.4.0",
"request": "^2.81.0",
"terminal-forms.js": "git+https://git.daplie.com/OAuth3/terminal-forms.js.git#v1"
"terminal-forms.js": "git+https://git.oauth3.org/OAuth3/terminal-forms.js.git#v1"
},
"devDependencies": {
"browserify-aes": "^1.0.6",
@ -49,6 +49,6 @@
"gulp-uglify": "^2.1.0",
"vinyl-source-stream": "^1.1.0"
},
"author": "AJ ONeal <aj@daplie.com> (https://daplie.com/)",
"author": "AJ ONeal <coolaj86@gmail.com> (https://coolaj86.com/)",
"license": "(MIT OR Apache-2.0)"
}

View File

@ -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));

View File

@ -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;
};

View File

@ -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));

View File

@ -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));

View File

@ -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);
}());

View File

@ -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));

View File

@ -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;
});
};

View File

@ -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));

View File

@ -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
View File

@ -0,0 +1 @@
_apis

View File

@ -1,13 +0,0 @@
{ "terms": [ "oauth3.org/tos/draft" ]
, "api": "api.:hostname"
, "authorization_dialog": { "url": "#/authorization_dialog" }
, "access_token": { "method": "POST", "url": "api/org.oauth3.provider/access_token" }
, "otp": { "method": "POST", "url": "api/org.oauth3.provider/otp" }
, "credential_otp": { "method": "POST", "url": "api/org.oauth3.provider/otp" }
, "credential_meta": { "url": "api/org.oauth3.provider/logins/meta/:type/:id" }
, "credential_create": { "method": "POST", "url": "api/org.oauth3.provider/logins" }
, "grants": { "method": "GET", "url": "api/org.oauth3.provider/grants/:azp/:sub" }
, "authorization_decision": { "method": "POST", "url": "api/org.oauth3.provider/authorization_decision" }
, "callback": { "method": "GET", "url": ".well-known/oauth3/callback.html#/" }
, "logout": { "method": "GET", "url": "#/logout/" }
}

View File

@ -1,71 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<style>
body {
background-color: #ffcccc;
}
</style>
</head>
<body>
OAuth3 RPC
<script src="/assets/org.oauth3/oauth3.core.js"></script>
<script>
;(function () {
'use strict';
// Taken from oauth3.core.js
// TODO what about search within hash?
var prefix = "(" + window.location.hostname + ") [.well-known/oauth3/]";
var params = OAUTH3.query.parse(window.location.hash || window.location.search);
if (params.debug) {
console.warn(prefix, "DEBUG MODE ENABLED. Automatic redirects disabled.");
}
console.log(prefix, 'hash||search:');
console.log(window.location.hash || window.location.search);
console.log(prefix, 'params:');
console.log(params);
OAUTH3.request({ url: 'directives.json' }).then(function (resp) {
var urlsafe64 = OAUTH3._base64.encodeUrlSafe(JSON.stringify(resp.data, null, 0));
var redirect;
console.log(prefix, 'directives');
console.log(resp);
console.log(prefix, 'base64');
console.log(urlsafe64);
// TODO try postMessage back to redirect_uri domain right here
// window.postMessage();
// TODO make sure it's https NOT http
// NOTE: this can be only up to 2,083 characters
console.log(prefix, 'params.redirect_uri:', params.redirect_uri);
redirect = params.redirect_uri + '?' + OAUTH3.query.stringify({
state: params.state
, directives: urlsafe64
, debug: params.debug || undefined
})
console.log(prefix, 'redirect');
console.log(redirect);
if (!params.debug) {
window.location = redirect;
} else {
// yes, we're violating the security lint with purpose
document.body.innerHTML += window.location.host + window.location.pathname
+ '<br/><br/>You\'ve passed the \'debug\' parameter so we\'re pausing'
+ ' to let you look at logs or whatever it is that you intended to do.'
+ '<br/><br/>Continue with redirect: <a href="' + redirect + '">' + redirect + '</' + 'a>';
}
});
}());
</script>
</body>
</html>