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