Compare commits
199 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 | ||
|
de0bc51967 | ||
|
42c4e72b57 | ||
|
6b8d08c278 | ||
|
e8c631a416 | ||
|
ddfdc0d9ba | ||
|
0bfdd009b7 | ||
|
516c989cc1 | ||
|
3fc02ee6f0 | ||
|
d4dd268fb4 | ||
|
1926d1e8f7 | ||
|
f505932d12 | ||
|
831fc31809 | ||
|
79ee4ba7da | ||
|
dd0b257038 | ||
|
abb788780d | ||
|
02bb01fdf4 | ||
|
e8cafbf921 |
4
.gitignore
vendored
4
.gitignore
vendored
@ -1 +1,5 @@
|
||||
prefactor
|
||||
.well-known
|
||||
node_modules/
|
||||
DS_Store
|
||||
.vscode
|
||||
|
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -1,3 +0,0 @@
|
||||
[submodule "node_modules/terminal-forms.js"]
|
||||
path = node_modules/terminal-forms.js
|
||||
url = git@git.daplie.com:/OAuth3/terminal-forms.js
|
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
|
||||
}
|
5
.npmignore
Normal file
5
.npmignore
Normal file
@ -0,0 +1,5 @@
|
||||
.git*
|
||||
browserify/
|
||||
prefactor/
|
||||
gulpfile.js
|
||||
bump-version.sh
|
@ -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.
|
152
README.md
152
README.md
@ -1,6 +1,12 @@
|
||||
oauth3.js
|
||||
=========
|
||||
|
||||
| *oauth3.js*
|
||||
| [issuer.html](https://git.oauth3.org/OAuth3/issuer.html)
|
||||
| [issuer.rest.walnut.js](https://git.oauth3.org/OAuth3/issuer.rest.walnut.js)
|
||||
| [issuer.srv](https://git.oauth3.org/OAuth3/issuer.srv)
|
||||
| Sponsored by [ppl](https://ppl.family)
|
||||
|
||||
The world's smallest, fastest, and most secure OAuth3 (and OAuth2) JavaScript implementation
|
||||
(Yes! works in browsers and node.js with no extra dependencies or bloat and no hacks!)
|
||||
|
||||
@ -19,13 +25,12 @@ If you have no idea what you're doing
|
||||
|
||||
1. Create a folder for your project named after your app, such as `example.com/`
|
||||
2. Inside of the folder `example.com/` a folder called `assets/`
|
||||
3. Inside of the folder `example.com/assets` a folder called `org.oauth3/`
|
||||
4. Download [oauth.js-v1.zip](https://git.daplie.com/Daplie/oauth3.js/repository/archive.zip?ref=v1)
|
||||
3. Inside of the folder `example.com/assets` a folder called `oauth3.org/`
|
||||
4. Download [oauth3.js-v1.zip](https://git.oauth3.org/OAuth3/oauth3.js/repository/archive.zip?ref=v1)
|
||||
5. Double-click to unzip the folder.
|
||||
6. Copy the file `oauth3.core.js` into the folder `example.com/assets/org.oauth3/`
|
||||
7. Copy the folder `well-known` into the folder `example.com/`
|
||||
8. Rename the folder `well-known` to `.well-known` (when you do this, it become invisible, that's okay)
|
||||
9. Add `<script src="assets/org.oauth3/oauth3.core.js"></script>` to your `index.html`
|
||||
6. Copy the file `oauth3.core.js` into the folder `example.com/assets/oauth3.org/`
|
||||
7. Copy the folder `_apis` into the folder `example.com/`
|
||||
9. Add `<script src="assets/oauth3.org/oauth3.core.js"></script>` to your `index.html`
|
||||
9. Add `<script src="app.js"></script>` to your `index.html`
|
||||
10. Create files in `example.com` called `app.js` and `index.html` and put this in it:
|
||||
|
||||
@ -44,7 +49,7 @@ If you have no idea what you're doing
|
||||
<script src="https://code.jquery.com/jquery-3.1.1.js"
|
||||
integrity="sha256-16cdPddA6VdVInumRGo6IbivbERE8p7CQR3HzTBuELA="
|
||||
crossorigin="anonymous"></script>
|
||||
<script src="assets/org.oauth3/oauth3.core.js"></script>
|
||||
<script src="assets/oauth3.org/oauth3.core.js"></script>
|
||||
<script src="app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@ -53,15 +58,15 @@ If you have no idea what you're doing
|
||||
`app.js`:
|
||||
```js
|
||||
var OAUTH3 = window.OAUTH3;
|
||||
var auth = OAUTH3.create(window.location); // use window.location to set Client URI (your app's id)
|
||||
var oauth3 = OAUTH3.create(window.location); // use window.location to set Client URI (your app's id)
|
||||
|
||||
|
||||
// this is any OAuth3-compatible provider, such as oauth3.org
|
||||
// in v1.1.0 we'll add backwards compatibility for facebook.com, google.com, etc
|
||||
//
|
||||
function onChangeProvider(_providerUri) {
|
||||
function onChangeProvider(providerUri) {
|
||||
// example https://oauth3.org
|
||||
return auth.setProvider(providerUri);
|
||||
return oauth3.setIdentityProvider(providerUri);
|
||||
}
|
||||
|
||||
|
||||
@ -69,7 +74,7 @@ function onChangeProvider(_providerUri) {
|
||||
//
|
||||
function onClickLogin() {
|
||||
|
||||
return auth.authenticate().then(function (session) {
|
||||
return oauth3.authenticate().then(function (session) {
|
||||
|
||||
console.info('Authentication was Successful:');
|
||||
console.log(session);
|
||||
@ -80,12 +85,14 @@ function onClickLogin() {
|
||||
//
|
||||
console.info('Secure PPID (aka subject):', session.token.sub);
|
||||
|
||||
return auth.request({
|
||||
url: 'https://oauth3.org/api/org.oauth3.provider/inspect'
|
||||
return oauth3.request({
|
||||
url: 'https://api.oauth3.org/api/issuer@oauth3.org/jwks/:sub/:kid'
|
||||
.replace(/:sub/g, session.token.sub)
|
||||
.replace(/:kid/g, session.token.kid || session.token.iss)
|
||||
, session: session
|
||||
}).then(function (resp) {
|
||||
|
||||
console.info("Inspect Token:");
|
||||
console.info("Signing Public Key JWK:");
|
||||
console.log(resp.data);
|
||||
|
||||
});
|
||||
@ -102,7 +109,7 @@ function onClickLogin() {
|
||||
//
|
||||
function onClickLogout() {
|
||||
|
||||
return auth.logout().then(function () {
|
||||
return oauth3.logout().then(function () {
|
||||
localStorage.clear();
|
||||
|
||||
console.info('Logout was Successful');
|
||||
@ -138,13 +145,13 @@ it might look like this:
|
||||
example.com
|
||||
│
|
||||
│
|
||||
├── .well-known (hidden)
|
||||
│ └── oauth3
|
||||
├── _apis
|
||||
│ └── oauth3.org
|
||||
│ ├── callback.html
|
||||
│ ├── directives.json
|
||||
│ └── index.html
|
||||
├── assets
|
||||
│ └── org.oauth3
|
||||
│ └── oauth3.org
|
||||
│ └── oauth3.core.js
|
||||
│
|
||||
│
|
||||
@ -165,17 +172,17 @@ Installation (if you know what you're doing)
|
||||
pushd /path/to/your/web/app
|
||||
|
||||
|
||||
# clone the project as assets/org.oauth3
|
||||
# clone the project as assets/oauth3.org
|
||||
mkdir -p assets
|
||||
git clone git@git.daplie.com:Daplie/oauth3.js.git assets/org.oauth3
|
||||
pushd assets/org.oauth3
|
||||
git clone git@git.oauth3.org:OAuth3/oauth3.js.git assets/oauth3.org
|
||||
pushd assets/oauth3.org
|
||||
git checkout v1
|
||||
popd
|
||||
|
||||
|
||||
# symlink `.well-known/oauth3` to `assets/org.oauth3/.well-known/oauth3`
|
||||
mkdir -p .well-known
|
||||
ln -sf ../assets/org.oauth3/.well-known/oauth3 .well-known/oauth3
|
||||
# symlink `_apis/oauth3.org` to `assets/oauth3.org/_apis/oauth3.org`
|
||||
mkdir -p _apis
|
||||
ln -sf ../assets/oauth3.org/_apis/oauth3 _apis/oauth3.org
|
||||
```
|
||||
|
||||
**Advanced Installation with `bower`**
|
||||
@ -185,17 +192,17 @@ ln -sf ../assets/org.oauth3/.well-known/oauth3 .well-known/oauth3
|
||||
bower install oauth3
|
||||
|
||||
|
||||
# create a `.well-known` folder and an `assets` folder
|
||||
mkdir -p .well-known assets
|
||||
# create a `_apis` folder and an `assets` folder
|
||||
mkdir -p _apis assets
|
||||
|
||||
|
||||
# symlink `.well-known/oauth3` to `bower_components/oauth3/.well-known/oauth3`
|
||||
ln -sf ../bower_components/oauth3/.well-known/oauth3 .well-known/oauth3
|
||||
# symlink `_apis/oauth3.org` to `bower_components/oauth3.org/_apis/oauth3.org`
|
||||
ln -sf ../bower_components/oauth3.org/_apis/oauth3.org _apis/oauth3.org
|
||||
|
||||
|
||||
# symlink `assets/org.oauth3` to `bower_components/oauth3`
|
||||
ln -sf ../bower_components/oauth3/.well-known/oauth3 .well-known/oauth3
|
||||
ln -sf ../bower_components/oauth3 assets/org.oauth3
|
||||
# symlink `assets/oauth3.org` to `bower_components/oauth3.org`
|
||||
ln -sf ../bower_components/oauth3.org/_apis/oauth3.org _apis/oauth3.org
|
||||
ln -sf ../bower_components/oauth3.org assets/oauth3.org
|
||||
```
|
||||
|
||||
Usage
|
||||
@ -204,13 +211,14 @@ Usage
|
||||
Update your HTML to include the the following script tag:
|
||||
|
||||
```html
|
||||
<script src="assets/org.oauth3/oauth3.core.js"></script>
|
||||
<script src="assets/oauth3.org/oauth3.core.js"></script>
|
||||
```
|
||||
|
||||
You can create a very simple demo application like this:
|
||||
|
||||
```javascript
|
||||
var providerUri;
|
||||
var opts = { client_uri: OAUTH3.utils.clientUri(window.location) };
|
||||
|
||||
|
||||
// this is any OAuth3-compatible provider, such as oauth3.org
|
||||
@ -218,7 +226,7 @@ var providerUri;
|
||||
//
|
||||
function onChangeProvider(_providerUri) {
|
||||
providerUri = _providerUri;
|
||||
return OAUTH3.discover(providerUri); // just to cache
|
||||
return OAUTH3.discover(providerUri, opts); // just to cache
|
||||
}
|
||||
|
||||
|
||||
@ -226,7 +234,6 @@ function onChangeProvider(_providerUri) {
|
||||
//
|
||||
function onClickLogin() {
|
||||
|
||||
var opts = { client_uri: OAuth3.clientUri(window.location) };
|
||||
return OAUTH3.implicitGrant(providerUri, opts).then(function (session) {
|
||||
|
||||
console.info('Authentication was Successful:');
|
||||
@ -239,7 +246,7 @@ function onClickLogin() {
|
||||
console.info('Secure PPID (aka subject):', session.token.sub);
|
||||
|
||||
return OAUTH3.request({
|
||||
url: 'https://oauth3.org/api/org.oauth3.provider/inspect_token'
|
||||
url: 'https://oauth3.org/api/issuer@oauth3.org/inspect_token'
|
||||
, session: session
|
||||
}).then(function (resp) {
|
||||
|
||||
@ -260,6 +267,18 @@ function onClickLogin() {
|
||||
onChangeProvider('oauth3.org');
|
||||
```
|
||||
|
||||
A user's e-mail can be passed into the clientParams object as `clientParams.subject`.
|
||||
|
||||
To auto-populate the e-mail input of the login popup, make sure the input has the class `js-oauth3-email`.
|
||||
|
||||
Example:
|
||||
```js
|
||||
if (clientParams.subject) {
|
||||
$('.js-oauth3-email').val(clientParams.subject);
|
||||
$('.js-authn-show').prop('disabled', false);
|
||||
}
|
||||
```
|
||||
|
||||
### Compatibility with Frameworks and Libraries
|
||||
|
||||
**jQuery**:
|
||||
@ -271,7 +290,19 @@ You're all set. Nothing else is needed.
|
||||
We've created an `Oauth3` service just for you:
|
||||
|
||||
```html
|
||||
<script src="assets/org.oauth3/oauth3.ng.js"></script>
|
||||
<script src="assets/oauth3.org/oauth3.ng.js"></script>
|
||||
```
|
||||
|
||||
```js
|
||||
// Require the module as 'oauth3.org'
|
||||
var app = angular.module('myAppName', [ 'ui.router', 'oauth3.org' ]);
|
||||
|
||||
// Require services and other submodules in the form {modulename}@oauth3.org
|
||||
app.controller('authCtrl', [ '$scope', 'azp@oauth3.org', function ($scope, Oauth3) { /* ... */ } ]);
|
||||
|
||||
// For backwards compatibility with older angular applications that rely on string-name introspection
|
||||
// you can also use the camel case version of the names in the format {Modulename}Oauth3
|
||||
app.controller('authCtrl', function ($scope, AzpOauth3) { /* ... */ });
|
||||
```
|
||||
|
||||
You can include that in addition to the standard file or,
|
||||
@ -284,31 +315,48 @@ We include a small wrapper function of just a few lines in the bottom of `oauth3
|
||||
which exposes a `create` method to make using the underlying library require typing fewer keystrokes.
|
||||
|
||||
```
|
||||
auth = OAUTH3.create(location); // takes a location object, such as window.location
|
||||
oauth3 = OAUTH3.create(location); // takes a location object, such as window.location
|
||||
// to create the Client URI (your app's id)
|
||||
// and save it to an internal state
|
||||
|
||||
promise = auth.init(location); // set and fetch your own site/app's configuration details
|
||||
// promises your site's config
|
||||
promise = oauth3.init(opts); // set and fetch your own site/app's configuration details
|
||||
// promises your site's config // opts = { location, session, issuer, audience }
|
||||
|
||||
promise = auth.setProvider(url); // changes the Provider URI (the site you're logging into),
|
||||
// promises the provider's config // gets the config for that site (from their .well-known/oauth3),
|
||||
promise = oauth3.setIdentityProvider(url); // changes the Identity Provider URI (the site you're logging into),
|
||||
// promises the provider's config // gets the config for that site (from their _apis/oauth3.org),
|
||||
// and caches it in internal state as the default
|
||||
|
||||
promise = auth.authenticate(); // opens login window for the provider and returns a session
|
||||
// (must be called after the setProvider promise has completed)
|
||||
promise = oauth3.setResourceProvider(url); // changes the Resource Provider URI (the site you're getting stuff from)
|
||||
|
||||
promise = auth.authorize(permissions); // authenticates (if not authenticated) and opens a window to
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
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
|
||||
----------
|
||||
@ -436,10 +484,10 @@ Since we do not require the `protocol` to be specified, it is a URI
|
||||
|
||||
However, we do have a problem of disambiguation since a URI may look like a `path`:
|
||||
|
||||
1. https://example.com/api/org.oauth3.provider
|
||||
2. example.com/api/org.oauth.provider/ (not unique)
|
||||
3. /api/org.oauth3.provider
|
||||
4. api/org.oauth3.provider (not unique)
|
||||
1. https://example.com/api/issuer@oauth3.org
|
||||
2. example.com/api/issuer@oauth3.org/ (not unique)
|
||||
3. /api/issuer@oauth3.org
|
||||
4. api/issuer@oauth3.org (not unique)
|
||||
|
||||
Therefore anywhere a URI or a Path could be used, the URI must be a URL.
|
||||
We eliminate #2.
|
||||
@ -450,5 +498,5 @@ can be very ugly and confusing and we definitely need to allow relative paths.
|
||||
|
||||
A potential work-around would be to assume all paths are relative (eliminate #4 instead)
|
||||
and have the path always key off of the base URL - if oauth3 directives are to be found at
|
||||
https://example.com/username/.well-known/oauth3/directives.json then /api/whatever would refer
|
||||
https://example.com/username/_apis/oauth3.org/index.json then /api/whatever would refer
|
||||
to https://example.com/username/api/whatever.
|
||||
|
BIN
_apis/oauth3/blank.gif
Normal file
BIN
_apis/oauth3/blank.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 43 B |
@ -15,7 +15,7 @@
|
||||
|
||||
<!-- TODO permanently cache with appcache (or service worker?) -->
|
||||
<!-- TODO slim this all down to a single file -->
|
||||
<script src="/assets/org.oauth3/oauth3.core.js"></script>
|
||||
<script src="../../assets/oauth3.org/oauth3.core.js"></script>
|
||||
<script>
|
||||
;(function () {
|
||||
'use strict';
|
BIN
_apis/oauth3/clear.gif
Normal file
BIN
_apis/oauth3/clear.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 43 B |
12
_apis/oauth3/directives.json
Normal file
12
_apis/oauth3/directives.json
Normal file
@ -0,0 +1,12 @@
|
||||
{ "terms": [ "oauth3.org/tos/draft" ]
|
||||
, "api": "api.:hostname"
|
||||
, "authorization_dialog": { "url": "#/authorization_dialog" }
|
||||
, "access_token": { "method": "POST", "url": "api/issuer@oauth3.org/access_token" }
|
||||
, "otp": { "method": "POST", "url": "api/issuer@oauth3.org/access_token/send_otp" }
|
||||
, "credential_otp": { "method": "POST", "url": "api/issuer@oauth3.org/access_token/send_otp" }
|
||||
, "grants": { "method": "GET", "url": "api/issuer@oauth3.org/grants/:sub/:azp" }
|
||||
, "publish_jwk": { "method": "POST", "url": "api/issuer@oauth3.org/jwks/:sub" }
|
||||
, "retrieve_jwk": { "method": "GET", "url": "api/issuer@oauth3.org/jwks/:sub/:kid.json" }
|
||||
, "callback": { "method": "GET", "url": ".well-known/oauth3/callback.html#/" }
|
||||
, "logout": { "method": "GET", "url": "#/logout/" }
|
||||
}
|
140
_apis/oauth3/index.html
Normal file
140
_apis/oauth3/index.html
Normal file
@ -0,0 +1,140 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
body {
|
||||
background-color: #ffcccc;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
OAuth3 RPC
|
||||
|
||||
<script src="../../assets/oauth3.org/oauth3.core.js"></script>
|
||||
<script>
|
||||
;(function () {
|
||||
'use strict';
|
||||
|
||||
// Taken from oauth3.core.js
|
||||
|
||||
// TODO what about search within hash?
|
||||
var prefix = "(" + window.location.hostname + ") [.well-known/oauth3/]";
|
||||
var params = OAUTH3.query.parse(window.location.hash || window.location.search);
|
||||
var urlsafe64;
|
||||
var redirect;
|
||||
var err;
|
||||
var oldRpc;
|
||||
var sub = params.sub || params.subject;
|
||||
var subData;
|
||||
|
||||
function doRedirect(redirect) {
|
||||
if (params.debug) {
|
||||
console.log(prefix, 'params.redirect_uri:', params.redirect_uri);
|
||||
console.log(prefix, 'redirect');
|
||||
console.log(redirect);
|
||||
}
|
||||
|
||||
if (!params.debug) {
|
||||
window.location = redirect;
|
||||
} else {
|
||||
// yes, we're violating the security lint with purpose
|
||||
document.body.innerHTML += window.location.host + window.location.pathname
|
||||
+ '<br/><br/>You\'ve passed the \'debug\' parameter so we\'re pausing'
|
||||
+ ' to let you look at logs or whatever it is that you intended to do.'
|
||||
+ '<br/><br/>Continue with redirect: <a href="' + redirect + '">' + redirect + '</' + 'a>';
|
||||
}
|
||||
}
|
||||
|
||||
function onError(err) {
|
||||
var redirect = params.redirect_uri + '?' + OAUTH3.query.stringify({
|
||||
state: params.state
|
||||
, error: err.code
|
||||
, error_description: err.message
|
||||
, error_uri: err.uri
|
||||
, debug: params.debug || undefined
|
||||
});
|
||||
|
||||
doRedirect(redirect);
|
||||
}
|
||||
|
||||
function onSuccess(urlsafe64, hasSub) {
|
||||
if (params.debug) {
|
||||
console.log(prefix, 'directives');
|
||||
console.log(resp);
|
||||
|
||||
console.log(prefix, 'base64');
|
||||
console.log(urlsafe64);
|
||||
}
|
||||
|
||||
// TODO try postMessage back to redirect_uri domain right here
|
||||
// window.postMessage();
|
||||
|
||||
// TODO SECURITY make sure it's https NOT http
|
||||
// NOTE: this can be only up to 2,083 characters
|
||||
redirect = params.redirect_uri + '?' + OAUTH3.query.stringify({
|
||||
state: params.state
|
||||
, directives: oldRpc ? urlsafe64 : undefined
|
||||
, data: !oldRpc ? urlsafe64 : undefined
|
||||
, sub: hasSub && sub || undefined
|
||||
, debug: params.debug || undefined
|
||||
});
|
||||
|
||||
doRedirect(redirect);
|
||||
}
|
||||
|
||||
if (params.debug) {
|
||||
console.warn(prefix, "DEBUG MODE ENABLED. Automatic redirects disabled.");
|
||||
|
||||
console.log(prefix, 'hash||search:');
|
||||
console.log(window.location.hash || window.location.search);
|
||||
|
||||
console.log(prefix, 'params:');
|
||||
console.log(params);
|
||||
}
|
||||
|
||||
if ('rpc' !== params.response_type) {
|
||||
err = new Error("response_type '" + params.response_type + "' is not supported");
|
||||
err.code = "E_RESPONSE_TYPE";
|
||||
// TODO err.uri
|
||||
onError(err);
|
||||
return;
|
||||
}
|
||||
|
||||
if (params.action) {
|
||||
oldRpc = true;
|
||||
}
|
||||
|
||||
var loco = window.location.href.replace(/\/\.well-known.*/, '');
|
||||
//var loco = 'sso.hellabit.com';
|
||||
var resp;
|
||||
if (/localstorage/i.test(params._scheme)) {
|
||||
if (sub) {
|
||||
subData = localStorage.getItem(sub + '@oauth3.org:issuer');
|
||||
}
|
||||
resp = subData || localStorage.getItem('oauth3.org:issuer') || loco;
|
||||
onSuccess(resp, subData && true);
|
||||
return;
|
||||
}
|
||||
|
||||
var fileWhiteList = [
|
||||
'.well-known/oauth3/directives.json'
|
||||
, '.well-known/oauth3/scopes.json'
|
||||
];
|
||||
|
||||
if (-1 === fileWhiteList.indexOf(params._pathname)) {
|
||||
err = new Error("No access to requested file: " + params._pathname);
|
||||
err.code = "E_ACCESS_DENIED"
|
||||
// TODO err.uri
|
||||
onError(err);
|
||||
}
|
||||
|
||||
OAUTH3.request({ url: params._pathname.replace(/^\.well-known\/oauth3\//, '') }).then(function (resp) {
|
||||
urlsafe64 = OAUTH3._base64.encodeUrlSafe(JSON.stringify(resp.data, null, 0));
|
||||
|
||||
onSuccess(urlsafe64);
|
||||
});
|
||||
|
||||
}());
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
26
_apis/oauth3/scopes.json
Normal file
26
_apis/oauth3/scopes.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
|
||||
"oauth3_authn": "Basic secure authentication"
|
||||
, "auth@oauth3.org": "Basic secure authentication"
|
||||
, "wallet": "Access to payments and subscriptions"
|
||||
, "bucket": "Access to file storage"
|
||||
, "db": "Access to app data"
|
||||
, "domains": "Domain registration (and Glue and NS records)"
|
||||
, "domains@oauth3.org": "Domain registration (and Glue and NS records)"
|
||||
, "domains:glue": "Glue Record management (for vanity nameservers)"
|
||||
, "domains:ns": "Name Server management"
|
||||
, "dns": "DNS records (A/AAAA, TXT, SRV, MX, etc)"
|
||||
|
||||
, "hello@example.com": "Hello World Example Access"
|
||||
, "authn@oauth3.org": "Basic secure authentication"
|
||||
, "wallet@oauth3.org": "Access to payments and subscriptions"
|
||||
, "bucket@oauth3.org": "Access to file storage"
|
||||
, "db@oauth3.org": "Access to app data"
|
||||
, "domains@oauth3.org": "Domain registration (and Glue and NS records)"
|
||||
, "domains:glue@oauth3.org": "Glue Record management (for vanity nameservers)"
|
||||
, "domains:ns@oauth3.org": "Name Server management"
|
||||
, "dns@oauth3.org": "DNS records (A/AAAA, TXT, SRV, MX, etc)"
|
||||
, "www@daplie.com": "Websites and webapps"
|
||||
|
||||
, "*": "FULL ACCOUNT ACCESS"
|
||||
}
|
217
bin/cli.js
Normal file
217
bin/cli.js
Normal file
@ -0,0 +1,217 @@
|
||||
'use strict';
|
||||
|
||||
var oauth3 = require('./oauth3.js');
|
||||
var defaults = {
|
||||
main: 'oauth3'
|
||||
, provider: 'oauth3.org'
|
||||
};
|
||||
|
||||
function parseArgs(argv, opts) {
|
||||
var args = Array.prototype.slice.call(argv);
|
||||
var sep = /[:\.\-]/;
|
||||
|
||||
args.shift(); // 'node' is the first parameter
|
||||
args.shift(); // 'oauth3.js' will be the
|
||||
|
||||
var command = args.shift() || 'help';
|
||||
var cmdpair = command.split(sep);
|
||||
var cmd = cmdpair[0];
|
||||
var sub = cmdpair[1];
|
||||
var COMMAND = 'COMMAND';
|
||||
var maxCmdLen = COMMAND.length;
|
||||
var maxPairLen = 0;
|
||||
var arg1 = args[0];
|
||||
|
||||
// build top-level commands (tlcs) list
|
||||
// also count the word-width (for the space needed to print the commands)
|
||||
var pairsMap = {};
|
||||
var tlcs = opts.commands.filter(function (desc) {
|
||||
var pair = desc[0].split(/\s+/)[0];
|
||||
var psub = pair.split(sep)[0];
|
||||
pairsMap[pair] = true;
|
||||
maxPairLen = Math.max(maxPairLen, pair.length);
|
||||
if (pair === psub) {
|
||||
maxCmdLen = Math.max(maxCmdLen, psub.length);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
// right pad (for making the printed lines longer)
|
||||
function rpad(str, len) {
|
||||
while (str.length < len) {
|
||||
str += ' ';
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
// oauth3.js help
|
||||
// oauth3.js help <command>
|
||||
// oauth3.js help <command:sub> (alias of `oauth3.js <command:sub> --help')
|
||||
function help() {
|
||||
var status = 0;
|
||||
|
||||
function printCmd(desc) {
|
||||
var pcmd = rpad(desc[0].split(/\s+/)[0], maxCmdLen);
|
||||
var pdesc = desc[1];
|
||||
console.info('\t' + defaults.main + ' ' + pcmd, ' # ' + pdesc);
|
||||
}
|
||||
|
||||
function printCmds(cmds) {
|
||||
console.info('');
|
||||
|
||||
var title = defaults.main + ' ' + rpad(COMMAND, maxCmdLen) + ' # description';
|
||||
var bars = title.replace(/./g, '-').split('');
|
||||
bars[bars.length - ' # description'.length] = ' ';
|
||||
bars[bars.length - (' # description'.length + 1)] = ' ';
|
||||
console.info('\t' + title);
|
||||
console.info('\t' + bars.join(''));
|
||||
cmds.forEach(printCmd);
|
||||
console.info('');
|
||||
}
|
||||
|
||||
function helpMain() {
|
||||
console.info('');
|
||||
console.info('Here are all the top-level commands:');
|
||||
|
||||
printCmds(tlcs);
|
||||
}
|
||||
|
||||
if (arg1 && -1 === Object.keys(pairsMap).indexOf(arg1)) {
|
||||
status = 1;
|
||||
console.info('');
|
||||
console.info(defaults.main + ": Unknown command '" + arg1 + "'");
|
||||
console.info('');
|
||||
console.info("Try '" + defaults.main + " help'");
|
||||
console.info('');
|
||||
arg1 = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// the case of "oauth3 help --something"
|
||||
if (!arg1 || '-' === arg1[0]) {
|
||||
helpMain();
|
||||
process.exit(status);
|
||||
return;
|
||||
}
|
||||
|
||||
// the case of "oauth3 help help"
|
||||
if ('help' === arg1) {
|
||||
helpMain();
|
||||
console.info("no more help available for 'help'");
|
||||
process.exit(status);
|
||||
return;
|
||||
}
|
||||
|
||||
// matches the first part of the command
|
||||
// and has second parts
|
||||
if (arg1 === arg1.split(':')[0] && opts.commands.filter(function (desc) {
|
||||
return arg1 === desc[0].split(/\s+/)[0].split(':')[0] && desc[0].split(/\s+/)[0].split(':');
|
||||
}).length > 1) {
|
||||
console.info('');
|
||||
console.info("Here are all the '" + command + "'-related commands:");
|
||||
printCmds(
|
||||
opts.commands.filter(function (desc) {
|
||||
var pair = desc[0].split(/\s+/)[0];
|
||||
var psub = pair.split(sep)[0];
|
||||
maxPairLen = Math.max(maxPairLen, pair.length);
|
||||
if (arg1 === psub || arg1 === pair) {
|
||||
maxCmdLen = Math.max(maxCmdLen, pair.length);
|
||||
return true;
|
||||
}
|
||||
})
|
||||
);
|
||||
console.info('');
|
||||
} else {
|
||||
console.info('');
|
||||
console.info("Here are all the options and flags for '" + arg1 + "':");
|
||||
console.info('');
|
||||
opts.commands.some(function (desc) {
|
||||
var pair = desc[0].split(/\s+/)[0];
|
||||
var psub = pair.split(sep)[0];
|
||||
maxPairLen = Math.max(maxPairLen, pair.length);
|
||||
if (arg1 !== psub && arg1 !== pair) {
|
||||
return false;
|
||||
}
|
||||
maxCmdLen = Math.max(maxCmdLen, pair.length);
|
||||
console.log('\t' + desc[0] + '\t# ' + desc[1]);
|
||||
(desc[2]||[]).forEach(function (flag) {
|
||||
var pair = flag.split(', ');
|
||||
var f = pair.shift();
|
||||
var d = pair.join(', ');
|
||||
console.log('\t\t' + f + ' # ' + d);
|
||||
});
|
||||
return true;
|
||||
});
|
||||
console.info('');
|
||||
}
|
||||
}
|
||||
|
||||
// If the command is not in the list of commands
|
||||
if (-1 === Object.keys(pairsMap).indexOf(cmd)) {
|
||||
arg1 = cmd;
|
||||
cmd = 'help';
|
||||
help();
|
||||
return;
|
||||
}
|
||||
|
||||
// If help is explictly requested
|
||||
if (-1 !== [ 'help', '-h', '--help' ].indexOf(command) || -1 !== args.indexOf('-h') || -1 !== args.indexOf('--help')) {
|
||||
help();
|
||||
return;
|
||||
}
|
||||
|
||||
// If we're ready to rock and roll!
|
||||
console.log('RUN', cmd, sub || '(n/a)', arg1 || '(n/a)', '... not yet implemented');
|
||||
}
|
||||
|
||||
parseArgs(process.argv, {
|
||||
// CLI goals:
|
||||
//
|
||||
// whoami / login: you are now logged in as
|
||||
// * john@example.com [current] (just now)
|
||||
// * john@work.net (2 minutes ago)
|
||||
// * john@family.me (2 weeks ago)
|
||||
commands: [
|
||||
[ 'login [email or cloud address]', 'alias of session:attach', [
|
||||
"--auto, create a new account without asking if none exists"
|
||||
//, "--exclusive, logout all other ids, removing access to their accounts"
|
||||
, "--provider, specify an authentication provider (default: :provider)".replace(/\b:provider\b/, defaults.provider)
|
||||
//, "--email [addr], use the given id as an email address, even if it works as a cloud address"
|
||||
//, "--cloud [addr], use the given id as a cloud address or fail (don't fallback to email)"
|
||||
]
|
||||
]
|
||||
, [ 'logout', 'alias of session:detach' ]
|
||||
, [ 'whoami', 'show current account(s) and login(s) and device(s)' ]
|
||||
|
||||
// authn
|
||||
, [ 'session', 'Manage your ids (credentials / logins)' ]
|
||||
, [ 'session:new', 'alias of `login --exclusive`' ]
|
||||
, [ 'session:attach', 'Create a session (and account if needed) for a given email address or cloud address' ]
|
||||
, [ 'session:detach', 'remove login from session' ]
|
||||
, [ 'session:list', 'show all of the ids in the current session' ]
|
||||
|
||||
// authz
|
||||
, [ 'accounts', 'Manage your accounts (authorization / profiles)' ]
|
||||
, [ 'accounts:new', 'create a new account attached to the credentials of the current session' ]
|
||||
, [ 'accounts:set', 'change account details' ] // todo changing the name should be restricted john@provider.net -> jonathan@provider.net would be bad
|
||||
, [ 'accounts:list', 'show all of the accounts in the current session' ]
|
||||
, [ 'accounts:attach', 'attach an account to an id' ]
|
||||
, [ 'accounts:detach', 'detach an account from an id' ]
|
||||
, [ 'accounts:select', 'select an account to use as the primary account for this session' ]
|
||||
, [ 'accounts:update', '(deprecated) alias of set' ]
|
||||
, [ 'accounts:login', '(deprecated) alias of login' ]
|
||||
, [ 'accounts:whoami', '(deprecated) alias of whoami' ]
|
||||
|
||||
// authn / authz
|
||||
, [ 'devices', 'manages devices for your account(s)' ]
|
||||
, [ 'devices:new', 'create a new device (default name is hostname, default ip is the result of :provider/api/tunnel@oauth3.org/checkip)'.replace(/\b:provider\b/, defaults.provider) ]
|
||||
, [ 'devices:set', 'set the ip address of the device (defaults ip is the result of :provider/api/tunnel@oauth3.org/checkip)'.replace(/\b:provider\b/, defaults.provider) ]
|
||||
, [ 'devices:attach', "attach a device to a domain's DNS record" ]
|
||||
, [ 'devices:detach', "detach an account from a domain's DNS record" ]
|
||||
, [ 'devices:select', '(re)claim the specified device as this device (i.e. you re-installed your OS or deleted your ~/.oauth3)' ]
|
||||
, [ 'devices:list', 'show all devices for your account(s)' ]
|
||||
|
||||
// help
|
||||
, [ 'help', "show this menu; use '" + defaults.main + " help COMMAND' (even 'help') for options and sub-commands" ]
|
||||
]
|
||||
});
|
167
bin/oauth3.js
167
bin/oauth3.js
@ -15,52 +15,33 @@ OAUTH3._hooks.session.get = require('../oauth3.node.storage.js').sessions.get;
|
||||
OAUTH3._hooks.session.set = require('../oauth3.node.storage.js').sessions.set;
|
||||
*/
|
||||
|
||||
var url = require('url');
|
||||
//console.log('stdin tty', process.stdin.isTTY);
|
||||
//console.log('stdout tty', process.stdout.isTTY);
|
||||
var oauth3;
|
||||
var opts = {
|
||||
providerUri: undefined
|
||||
};
|
||||
|
||||
function getCurrentUserEmail() {
|
||||
return form.ask({ label: "What's your email (or cloud mail) address? ", type: 'email' }).then(function (emailResult) {
|
||||
var emailParts = (emailResult.result || emailResult.input).split('@');
|
||||
var domain = emailParts[1];
|
||||
var username;
|
||||
var sameProvider;
|
||||
|
||||
var urlObj = url.parse(opts.providerUri || domain);
|
||||
// TODO get unique client id for bootstrapping app
|
||||
oauth3 = OAUTH3.create(urlObj);
|
||||
form.println("got to setProvider");
|
||||
return oauth3.setProvider(domain).then(function () {
|
||||
form.println("got to setProvider SUCCESS");
|
||||
sameProvider = true;
|
||||
// ignore
|
||||
}, function () {
|
||||
form.println("got to setProvider ERROR");
|
||||
function askOauth3Url() {
|
||||
return form.ask({ label: "What's your OAuth3 Provider URL? ", type: 'url' }).then(function (urlResult) {
|
||||
var urlObj = url.parse(urlResult.result || urlResult.input);
|
||||
// TODO get unique client id for bootstrapping app
|
||||
oauth3 = OAUTH3.create(urlObj);
|
||||
return oauth3.setProvider(urlResult.result || urlResult.input).then(function () {
|
||||
// ignore
|
||||
}, function (err) {
|
||||
form.println(err.stack || err.message || err.toString());
|
||||
return askOauth3Url();
|
||||
});
|
||||
});
|
||||
// opts = { email, providerUri }
|
||||
module.exports.login = function (options) {
|
||||
options = options || {};
|
||||
var url = require('url');
|
||||
//console.log('stdin tty', process.stdin.isTTY);
|
||||
//console.log('stdout tty', process.stdout.isTTY);
|
||||
var oauth3;
|
||||
var opts = {
|
||||
email: options.email
|
||||
, providerUri: options.providerUri
|
||||
};
|
||||
if (opts.form) {
|
||||
form = opts.form;
|
||||
}
|
||||
var email;
|
||||
var providerUrl;
|
||||
var providerUri;
|
||||
var sameProvider;
|
||||
var username;
|
||||
|
||||
function getSession() {
|
||||
var username;
|
||||
|
||||
return askOauth3Url();
|
||||
}).then(function () {
|
||||
// TODO lookup uuid locally before performing loginMeta
|
||||
// TODO lookup token locally before performing loginMeta / otp
|
||||
form.println("got to loginMeta");
|
||||
return OAUTH3.authn.loginMeta(oauth3._providerDirectives, { email: emailResult.input }).then(function (/*result*/) {
|
||||
return { node: emailResult.result || emailResult.input, type: 'email' };
|
||||
return OAUTH3.authn.loginMeta(oauth3._providerDirectives, { email: email }).then(function (/*result*/) {
|
||||
return { node: email, type: 'email' };
|
||||
}, function (/*err*/) {
|
||||
// TODO require hashcash to create user account
|
||||
function confirmCreateAccount() {
|
||||
@ -81,15 +62,14 @@ function getCurrentUserEmail() {
|
||||
}
|
||||
|
||||
if (!sameProvider) {
|
||||
return { node: emailResult.result || emailResult.input, type: 'email' };
|
||||
return { node: email, type: 'email' };
|
||||
}
|
||||
|
||||
return form.ask({
|
||||
label: "What's your recovery email (or cloud mail) address? ", type: 'email'
|
||||
}).then(function (recoveryResult) {
|
||||
username = emailParts[0];
|
||||
return {
|
||||
node: emailResult.result || emailResult.input
|
||||
node: email
|
||||
, type: 'name'
|
||||
, recovery: recoveryResult.result || recoveryResult.input
|
||||
};
|
||||
@ -98,12 +78,7 @@ function getCurrentUserEmail() {
|
||||
}
|
||||
|
||||
return confirmCreateAccount();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return getCurrentUserEmail().then(function (user) {
|
||||
}).then(function (user) {
|
||||
// TODO skip if token exists locally
|
||||
var email = (user.recovery || user.node);
|
||||
form.println("Sending login code to '" + email + "'...");
|
||||
@ -140,13 +115,91 @@ return getCurrentUserEmail().then(function (user) {
|
||||
// returns session instead of input
|
||||
var colors = require('colors');
|
||||
form.setStatus(colors.dim("authenticating with server..."));
|
||||
return OAUTH3.authn.resourceOwnerPassword(oauth3._providerDirectives, data);
|
||||
return OAUTH3.authn.resourceOwnerPassword(oauth3._providerDirectives, data).then(function (result) {
|
||||
return result;
|
||||
}, function (/*err*/) {
|
||||
// TODO test error
|
||||
return form.PromiseA.reject(new Error("The code '" + formatted + "' is mistyped or incorrect. Double check and try again."));
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}).then(function (results) {
|
||||
var session = results.result;
|
||||
|
||||
form.println('session:');
|
||||
form.println(session);
|
||||
function getCurrentUserEmail() {
|
||||
return form.ask({
|
||||
label: "What's your email (or cloud mail) address? ", type: 'email', value: opts.email
|
||||
}).then(function (emailResult) {
|
||||
opts.email = undefined;
|
||||
email = (emailResult.result || emailResult.input);
|
||||
var emailParts = email.split('@');
|
||||
var domain = emailParts[1];
|
||||
username = emailParts[0];
|
||||
providerUrl = 'https://' + domain;
|
||||
providerUri = domain;
|
||||
|
||||
var urlObj = url.parse(providerUrl);
|
||||
// TODO get unique client id for bootstrapping app
|
||||
oauth3 = OAUTH3.create(urlObj);
|
||||
return oauth3.setProvider(domain).then(function () {
|
||||
sameProvider = true;
|
||||
// ignore
|
||||
}, function () {
|
||||
function askOauth3Url() {
|
||||
return form.ask({
|
||||
label: "What's your OAuth3 Provider URL? ", type: 'url', value: opts.providerUri
|
||||
}).then(function (urlResult) {
|
||||
opts.providerUri = undefined;
|
||||
providerUrl = urlResult.result || urlResult.input;
|
||||
providerUri = OAUTH3.uri.normalize(providerUrl);
|
||||
|
||||
var urlObj = url.parse(providerUrl);
|
||||
// TODO get unique client id for bootstrapping app
|
||||
oauth3 = OAUTH3.create(urlObj);
|
||||
return oauth3.setProvider(providerUri).then(function () {
|
||||
// ignore
|
||||
}, function (err) {
|
||||
form.println(err.stack || err.message || err.toString());
|
||||
return askOauth3Url();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return askOauth3Url();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return getCurrentUserEmail().then(function () {
|
||||
return OAUTH3._hooks.meta.get(email).then(function (id) {
|
||||
if (!id) {
|
||||
return null;
|
||||
}
|
||||
return OAUTH3._hooks.sessions.get(providerUri, id).then(function (session) {
|
||||
if (session) {
|
||||
return session;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
});
|
||||
}).then(function (session) {
|
||||
if (session) {
|
||||
return session;
|
||||
}
|
||||
|
||||
return getSession().then(function (sessionResult) {
|
||||
var session = sessionResult.result;
|
||||
var id = require('crypto').createHash('sha256').update(session.token.sub || '').digest('hex');
|
||||
|
||||
return OAUTH3._hooks.sessions.set(providerUri, session, id).then(function (session) {
|
||||
return OAUTH3._hooks.meta.set(email, id).then(function () {
|
||||
return session;
|
||||
});
|
||||
});
|
||||
});
|
||||
}).then(function (session) {
|
||||
oauth3.__session = session;
|
||||
return oauth3;
|
||||
});
|
||||
};
|
||||
|
@ -26,13 +26,18 @@
|
||||
"sign"
|
||||
],
|
||||
"license": "MIT",
|
||||
"homepage": "https://git.daplie.com/OAuth3/oauth3.js",
|
||||
"homepage": "https://git.oauth3.org/OAuth3/oauth3.js",
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"browserify",
|
||||
"prefactor",
|
||||
"gulpfile.js",
|
||||
"bump-version.sh",
|
||||
"oauth3.node.*",
|
||||
"node_modules",
|
||||
"bower_components",
|
||||
"test",
|
||||
"tests"
|
||||
],
|
||||
"version": "1.0.1"
|
||||
"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);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
}());
|
1
node_modules/terminal-forms.js
generated
vendored
1
node_modules/terminal-forms.js
generated
vendored
@ -1 +0,0 @@
|
||||
Subproject commit f078d479b085e8658fb2039eb3e4de49afd9db0e
|
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));
|
826
oauth3.core.js
826
oauth3.core.js
File diff suppressed because it is too large
Load Diff
16581
oauth3.crypto.fallback.js
Normal file
16581
oauth3.crypto.fallback.js
Normal file
File diff suppressed because it is too large
Load Diff
8
oauth3.crypto.fallback.min.js
vendored
Normal file
8
oauth3.crypto.fallback.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
156
oauth3.crypto.js
156
oauth3.crypto.js
@ -1,5 +1,5 @@
|
||||
;(function (exports) {
|
||||
'use strict';
|
||||
'use strict';
|
||||
|
||||
var OAUTH3 = exports.OAUTH3 = exports.OAUTH3 || require('./oauth3.core.js').OAUTH3;
|
||||
|
||||
@ -8,6 +8,9 @@
|
||||
OAUTH3.crypto.core = require('./oauth3.node.crypto');
|
||||
} catch (error) {
|
||||
OAUTH3.crypto.core = {};
|
||||
OAUTH3.crypto.core.ready = false;
|
||||
var finishBeforeReady = [];
|
||||
var deferedCalls = [];
|
||||
|
||||
// We don't currently have a fallback method for this function, so we assign
|
||||
// it directly to the core object instead of the webCrypto object.
|
||||
@ -17,10 +20,31 @@
|
||||
};
|
||||
|
||||
var webCrypto = {};
|
||||
|
||||
var deferCryptoCall = function(name) {
|
||||
return function() {
|
||||
var args = arguments;
|
||||
return new OAUTH3.PromiseA(function(resolve, reject) {
|
||||
deferedCalls.push(function(){
|
||||
try {
|
||||
webCrypto[name].apply(webCrypto, args)
|
||||
.then(function(result){
|
||||
resolve(result);
|
||||
});
|
||||
} catch(e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
OAUTH3.crypto.core.sha256 = deferCryptoCall("sha256");
|
||||
webCrypto.sha256 = function (buf) {
|
||||
return OAUTH3._browser.window.crypto.subtle.digest({name: 'SHA-256'}, buf);
|
||||
};
|
||||
|
||||
OAUTH3.crypto.core.pbkdf2 = deferCryptoCall("pbkdf2");
|
||||
webCrypto.pbkdf2 = function (password, salt) {
|
||||
return OAUTH3._browser.window.crypto.subtle.importKey('raw', OAUTH3._binStr.binStrToBuffer(password), {name: 'PBKDF2'}, false, ['deriveKey'])
|
||||
.then(function (key) {
|
||||
@ -32,12 +56,15 @@
|
||||
});
|
||||
};
|
||||
|
||||
OAUTH3.crypto.core.encrypt = deferCryptoCall("encrypt");
|
||||
webCrypto.encrypt = function (rawKey, iv, data) {
|
||||
return OAUTH3._browser.window.crypto.subtle.importKey('raw', rawKey, {name: 'AES-GCM'}, false, ['encrypt'])
|
||||
.then(function (key) {
|
||||
return OAUTH3._browser.window.crypto.subtle.encrypt({name: 'AES-GCM', iv: iv}, key, data);
|
||||
});
|
||||
};
|
||||
|
||||
OAUTH3.crypto.core.decrypt = deferCryptoCall("decrypt");
|
||||
webCrypto.decrypt = function (rawKey, iv, data) {
|
||||
return OAUTH3._browser.window.crypto.subtle.importKey('raw', rawKey, {name: 'AES-GCM'}, false, ['decrypt'])
|
||||
.then(function (key) {
|
||||
@ -45,6 +72,7 @@
|
||||
});
|
||||
};
|
||||
|
||||
OAUTH3.crypto.core.genEcdsaKeyPair = deferCryptoCall("genEcdsaKeyPair");
|
||||
webCrypto.genEcdsaKeyPair = function () {
|
||||
return OAUTH3._browser.window.crypto.subtle.generateKey({name: 'ECDSA', namedCurve: 'P-256'}, true, ['sign', 'verify'])
|
||||
.then(function (keyPair) {
|
||||
@ -57,6 +85,7 @@
|
||||
});
|
||||
};
|
||||
|
||||
OAUTH3.crypto.core.sign = deferCryptoCall("sign");
|
||||
webCrypto.sign = function (jwk, msg) {
|
||||
return OAUTH3._browser.window.crypto.subtle.importKey('jwk', jwk, {name: 'ECDSA', namedCurve: jwk.crv}, false, ['sign'])
|
||||
.then(function (key) {
|
||||
@ -66,6 +95,8 @@
|
||||
return new Uint8Array(sig);
|
||||
});
|
||||
};
|
||||
|
||||
OAUTH3.crypto.core.verify = deferCryptoCall("verify");
|
||||
webCrypto.verify = function (jwk, msg, signature) {
|
||||
// If the JWK has properties that should only exist on the private key or is missing
|
||||
// "verify" in the key_ops, importing in as a public key won't work.
|
||||
@ -82,6 +113,7 @@
|
||||
};
|
||||
|
||||
function checkWebCrypto() {
|
||||
/* global OAUTH3_crypto_fallback */
|
||||
var loadFallback = function() {
|
||||
var prom;
|
||||
loadFallback = function () { return prom; };
|
||||
@ -96,25 +128,25 @@
|
||||
resolve();
|
||||
}
|
||||
};
|
||||
script.src = '/assets/org.oauth3/oauth3.crypto.fallback.js';
|
||||
script.src = '/assets/oauth3.org/oauth3.crypto.fallback.js';
|
||||
body.appendChild(script);
|
||||
});
|
||||
return prom;
|
||||
};
|
||||
function checkException(name, func) {
|
||||
new OAUTH3.PromiseA(function (resolve) { resolve(func()); })
|
||||
return OAUTH3.PromiseA.resolve().then(func)
|
||||
.then(function () {
|
||||
OAUTH3.crypto.core[name] = webCrypto[name];
|
||||
})
|
||||
.catch(function (err) {
|
||||
}, function (err) {
|
||||
console.warn('error with WebCrypto', name, '- using fallback', err);
|
||||
loadFallback().then(function () {
|
||||
return loadFallback().then(function () {
|
||||
OAUTH3.crypto.core[name] = OAUTH3_crypto_fallback[name];
|
||||
});
|
||||
});
|
||||
}
|
||||
function checkResult(name, expected, func) {
|
||||
checkException(name, function () {
|
||||
|
||||
finishBeforeReady.push(checkException(name, function () {
|
||||
return func()
|
||||
.then(function (result) {
|
||||
if (typeof expected === typeof result) {
|
||||
@ -127,7 +159,7 @@
|
||||
throw new Error("result ("+result+") doesn't match expectation ("+expected+")");
|
||||
}
|
||||
});
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
var zeroBuf = new Uint8Array([0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]);
|
||||
@ -159,11 +191,19 @@
|
||||
return webCrypto.verify(jwk, dataBuf, sig);
|
||||
});
|
||||
// The results of these functions are less predictable, so we can't check their return value.
|
||||
checkException('genEcdsaKeyPair', function () {
|
||||
finishBeforeReady.push(checkException('genEcdsaKeyPair', function () {
|
||||
return webCrypto.genEcdsaKeyPair();
|
||||
});
|
||||
checkException('sign', function () {
|
||||
}));
|
||||
finishBeforeReady.push(checkException('sign', function () {
|
||||
return webCrypto.sign(jwk, dataBuf);
|
||||
}));
|
||||
|
||||
OAUTH3.PromiseA.all(finishBeforeReady)
|
||||
.then(function(results) {
|
||||
OAUTH3.crypto.core.ready = true;
|
||||
deferedCalls.forEach(function(request) {
|
||||
request();
|
||||
});
|
||||
});
|
||||
}
|
||||
checkWebCrypto();
|
||||
@ -188,105 +228,67 @@
|
||||
return OAUTH3.PromiseA.reject(new Error('JWK of type '+jwk.kty+' missing fields ' + missing));
|
||||
}
|
||||
|
||||
var jwkStr = '{' + keys.map(function (name) { return name+':'+jwk[name]; }).join(',') + '}';
|
||||
// I'm not actually 100% sure this behavior is guaranteed, but when we use an array as the
|
||||
// replacer argument the keys are always in the order they appeared in the array.
|
||||
var jwkStr = JSON.stringify(jwk, keys);
|
||||
return OAUTH3.crypto.core.sha256(OAUTH3._binStr.binStrToBuffer(jwkStr))
|
||||
.then(OAUTH3._base64.bufferToUrlSafe);
|
||||
};
|
||||
|
||||
OAUTH3.crypto._createKey = function (ppid) {
|
||||
var saltProm = OAUTH3.crypto.core.randomBytes(16);
|
||||
var kekProm = saltProm.then(function (salt) {
|
||||
return OAUTH3.crypto.core.pbkdf2(ppid, salt);
|
||||
});
|
||||
|
||||
var ecdsaProm = OAUTH3.crypto.core.genEcdsaKeyPair()
|
||||
.then(function (keyPair) {
|
||||
OAUTH3.crypto.createKeyPair = function () {
|
||||
// TODO: maybe support other types of key pairs, not just ECDSA P-256
|
||||
return OAUTH3.crypto.core.genEcdsaKeyPair().then(function (keyPair) {
|
||||
return OAUTH3.crypto.thumbprintJwk(keyPair.publicKey).then(function (kid) {
|
||||
keyPair.privateKey.alg = keyPair.publicKey.alg = 'ES256';
|
||||
keyPair.privateKey.kid = keyPair.publicKey.kid = kid;
|
||||
return keyPair;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
OAUTH3.crypto.encryptKeyPair = function (keyPair, password) {
|
||||
var saltProm = OAUTH3.crypto.core.randomBytes(16);
|
||||
var kekProm = saltProm.then(function (salt) {
|
||||
return OAUTH3.crypto.core.pbkdf2(password, salt);
|
||||
});
|
||||
|
||||
return OAUTH3.PromiseA.all([
|
||||
kekProm
|
||||
, ecdsaProm
|
||||
, saltProm
|
||||
, OAUTH3.crypto.core.randomBytes(16)
|
||||
, OAUTH3.crypto.core.randomBytes(12)
|
||||
, OAUTH3.crypto.core.randomBytes(12)
|
||||
]).then(function (results) {
|
||||
, ]).then(function (results) {
|
||||
var kek = results[0];
|
||||
var keyPair = results[1];
|
||||
var salt = results[2];
|
||||
var userSecret = results[3];
|
||||
var ecdsaIv = results[4];
|
||||
var secretIv = results[5];
|
||||
var salt = results[1];
|
||||
var ecdsaIv = results[2];
|
||||
|
||||
return OAUTH3.PromiseA.all([
|
||||
OAUTH3.crypto.core.encrypt(kek, ecdsaIv, OAUTH3._binStr.binStrToBuffer(JSON.stringify(keyPair.privateKey)))
|
||||
, OAUTH3.crypto.core.encrypt(kek, secretIv, userSecret)
|
||||
])
|
||||
.then(function (encrypted) {
|
||||
var privKeyBuf = OAUTH3._binStr.binStrToBuffer(JSON.stringify(keyPair.privateKey));
|
||||
return OAUTH3.crypto.core.encrypt(kek, ecdsaIv, privKeyBuf).then(function (encrypted) {
|
||||
return {
|
||||
publicKey: keyPair.publicKey
|
||||
, privateKey: OAUTH3._base64.bufferToUrlSafe(encrypted[0])
|
||||
, userSecret: OAUTH3._base64.bufferToUrlSafe(encrypted[1])
|
||||
, privateKey: OAUTH3._base64.bufferToUrlSafe(encrypted)
|
||||
, salt: OAUTH3._base64.bufferToUrlSafe(salt)
|
||||
, ecdsaIv: OAUTH3._base64.bufferToUrlSafe(ecdsaIv)
|
||||
, secretIv: OAUTH3._base64.bufferToUrlSafe(secretIv)
|
||||
};
|
||||
, };
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
OAUTH3.crypto._decryptKey = function (ppid, storedObj) {
|
||||
OAUTH3.crypto.decryptKeyPair = function (storedObj, password) {
|
||||
var salt = OAUTH3._base64.urlSafeToBuffer(storedObj.salt);
|
||||
var encJwk = OAUTH3._base64.urlSafeToBuffer(storedObj.privateKey);
|
||||
var iv = OAUTH3._base64.urlSafeToBuffer(storedObj.ecdsaIv);
|
||||
|
||||
return OAUTH3.crypto.core.pbkdf2(ppid, salt)
|
||||
return OAUTH3.crypto.core.pbkdf2(password, salt)
|
||||
.then(function (key) {
|
||||
return OAUTH3.crypto.core.decrypt(key, iv, encJwk);
|
||||
})
|
||||
.then(OAUTH3._binStr.bufferToBinStr)
|
||||
.then(JSON.parse);
|
||||
};
|
||||
|
||||
OAUTH3.crypto._getKey = function (ppid) {
|
||||
return OAUTH3.crypto.core.sha256(OAUTH3._binStr.binStrToBuffer(ppid))
|
||||
.then(function (hash) {
|
||||
var name = 'kek-' + OAUTH3._base64.bufferToUrlSafe(hash);
|
||||
var promise;
|
||||
|
||||
if (window.localStorage.getItem(name) === null) {
|
||||
promise = OAUTH3.crypto._createKey(ppid).then(function (key) {
|
||||
window.localStorage.setItem(name, JSON.stringify(key));
|
||||
return key;
|
||||
});
|
||||
} else {
|
||||
promise = OAUTH3.PromiseA.resolve(JSON.parse(window.localStorage.getItem(name)));
|
||||
}
|
||||
|
||||
return promise.then(function (storedObj) {
|
||||
return OAUTH3.crypto._decryptKey(ppid, storedObj);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
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;
|
||||
});
|
||||
.then(JSON.parse)
|
||||
.then(function (privateKey) {
|
||||
return {
|
||||
privateKey: privateKey
|
||||
, publicKey: storedObj.publicKey
|
||||
, };
|
||||
});
|
||||
};
|
||||
|
||||
|
104
oauth3.dns.js
104
oauth3.dns.js
@ -31,15 +31,23 @@ OAUTH3.api['devices.list'] = function (providerUri, opts) {
|
||||
|
||||
OAUTH3.api['devices.attach'] = function (providerUri, opts) {
|
||||
var session = opts.session;
|
||||
var device = opts.device;
|
||||
var tld = opts.tld;
|
||||
var sld = opts.sld;
|
||||
var sub = opts.sub;
|
||||
var ip = opts.ip;
|
||||
var ttl = opts.ttl;
|
||||
|
||||
return OAUTH3.request({
|
||||
url: OAUTH3.url.normalize(providerUri)
|
||||
+ '/api/com.daplie.domains/accounts/' + session.token.sub
|
||||
//+ '/devices/' + device + '/'
|
||||
+ '/devices/' + (opts.data.uid || '_') + '/' + opts.data.device
|
||||
+ '/' + opts.data.tld + '/' + opts.data.sld + '/' + (opts.data.sub || '')
|
||||
+ '/api/com.daplie.domains/accounts/' + session.token.sub + '/devices/'
|
||||
+ device + '/' + tld + '/' + sld + '/' + (sub || '')
|
||||
, method: 'POST'
|
||||
, session: session
|
||||
, data: {
|
||||
addresses: ip
|
||||
, ttl: ttl
|
||||
}
|
||||
}, {}).then(function (res) {
|
||||
return res.data.devices || res.data;
|
||||
});
|
||||
@ -47,28 +55,15 @@ OAUTH3.api['devices.attach'] = function (providerUri, opts) {
|
||||
|
||||
OAUTH3.api['devices.detach'] = function (providerUri, opts) {
|
||||
var session = opts.session;
|
||||
var device = opts.device;
|
||||
var tld = opts.tld;
|
||||
var sld = opts.sld;
|
||||
var sub = opts.sub;
|
||||
|
||||
return OAUTH3.request({
|
||||
url: OAUTH3.url.normalize(providerUri)
|
||||
+ '/api/com.daplie.domains/accounts/' + session.token.sub
|
||||
//+ '/devices/' + device + '/'
|
||||
+ '/devices/' + (opts.data.uid || '_') + '/' + opts.data.device
|
||||
+ '/' + opts.data.tld + '/' + opts.data.sld + '/' + (opts.data.sub || '')
|
||||
, method: 'DELETE'
|
||||
, session: session
|
||||
}, {}).then(function (res) {
|
||||
return res.data.devices || res.data;
|
||||
});
|
||||
};
|
||||
|
||||
OAUTH3.api['devices.detach'] = function (providerUri, opts) {
|
||||
var session = opts.session;
|
||||
|
||||
return OAUTH3.request({
|
||||
url: OAUTH3.url.normalize(providerUri)
|
||||
+ '/api/com.daplie.domains/accounts/' + session.token.sub
|
||||
+ '/devices/' + opts.data.device
|
||||
+ '/' + opts.data.tld + '/' + opts.data.sld + '/' + (opts.data.sub || '')
|
||||
+ '/devices/' + device + '/' + tld + '/' + sld + '/' + (sub || '')
|
||||
, method: 'DELETE'
|
||||
, session: session
|
||||
}, {}).then(function (res) {
|
||||
@ -76,4 +71,69 @@ OAUTH3.api['devices.detach'] = function (providerUri, opts) {
|
||||
});
|
||||
};
|
||||
|
||||
OAUTH3.api['devices.destroy'] = function (providerUri, opts) {
|
||||
var session = opts.session;
|
||||
var device = opts.device;
|
||||
|
||||
return OAUTH3.request({
|
||||
url: OAUTH3.url.normalize(providerUri)
|
||||
+ '/api/com.daplie.domains/accounts/' + session.token.sub
|
||||
+ '/devices/' + device
|
||||
, method: 'DELETE'
|
||||
, session: session
|
||||
}, {}).then(function (res) {
|
||||
return res.data.device || res.data;
|
||||
});
|
||||
};
|
||||
|
||||
OAUTH3.api['dns.set'] = function (providerUri, opts) {
|
||||
var session = opts.session;
|
||||
var tld = opts.tld;
|
||||
var sld = opts.sld;
|
||||
var sub = opts.sub;
|
||||
var type = opts.type;
|
||||
var value = opts.value;
|
||||
var ttl = opts.ttl;
|
||||
var priority = (opts.priority || '');
|
||||
var weight = (opts.weight || '');
|
||||
var port = (opts.port || '');
|
||||
|
||||
return OAUTH3.request({
|
||||
url: OAUTH3.url.normalize(providerUri)
|
||||
+ '/api/com.daplie.domains/accounts/' + session.token.sub
|
||||
+ '/dns/' + tld + '/' + sld + '/' + sub
|
||||
, method: 'POST'
|
||||
, session: session
|
||||
, data: [{
|
||||
type: type
|
||||
, value: value
|
||||
, ttl: ttl
|
||||
, priority: priority
|
||||
, weight: weight
|
||||
, port: port
|
||||
}]
|
||||
}, {}).then(function (res) {
|
||||
return res.data || res;
|
||||
});
|
||||
};
|
||||
|
||||
OAUTH3.api['dns.unset'] = function (providerUri, opts) {
|
||||
var session = opts.session;
|
||||
var tld = opts.tld;
|
||||
var sld = opts.sld;
|
||||
var sub = (opts.sub || '@');
|
||||
var type = opts.type;
|
||||
var value = opts.value;
|
||||
|
||||
return OAUTH3.request({
|
||||
url: OAUTH3.url.normalize(providerUri)
|
||||
+ '/api/com.daplie.domains/accounts/' + session.token.sub
|
||||
+ '/dns/' + tld + '/' + sld + '/' + sub + '/' + type + '/' + value
|
||||
, method: 'DELETE'
|
||||
, session: session
|
||||
}, {}).then(function (res) {
|
||||
return res.data || res;
|
||||
});
|
||||
};
|
||||
|
||||
}('undefined' !== typeof exports ? exports : window));
|
||||
|
@ -3,6 +3,35 @@
|
||||
|
||||
var OAUTH3 = exports.OAUTH3 = exports.OAUTH3 || require('./oauth3.core.js').OAUTH3;
|
||||
|
||||
OAUTH3.api['domains.checkAvailability'] = function (providerUri, opts) {
|
||||
var session = opts.session;
|
||||
var sld = opts.sld;
|
||||
var tld = opts.tld;
|
||||
|
||||
return OAUTH3.request({
|
||||
method: 'GET'
|
||||
, url: OAUTH3.url.normalize(providerUri)
|
||||
+ '/api/com.daplie.domains/check-availability/' + sld + '/' + tld
|
||||
, session: session
|
||||
}).then(function (res) {
|
||||
return res;
|
||||
});
|
||||
};
|
||||
|
||||
OAUTH3.api['domains.purchase'] = function (providerUri, opts) {
|
||||
var session = opts.session;
|
||||
|
||||
return OAUTH3.request({
|
||||
method: 'POST'
|
||||
, url: OAUTH3.url.normalize(providerUri)
|
||||
+ '/api/com.daplie.domains/accounts/' + session.token.sub + '/registrations'
|
||||
, session: session
|
||||
, data: opts.data
|
||||
}).then(function (res) {
|
||||
return res;
|
||||
});
|
||||
};
|
||||
|
||||
OAUTH3.api['domains.list'] = function (providerUri, opts) {
|
||||
var session = opts.session;
|
||||
|
||||
@ -16,4 +45,102 @@ OAUTH3.api['domains.list'] = function (providerUri, opts) {
|
||||
});
|
||||
};
|
||||
|
||||
// TODO: Manual Renew Function
|
||||
OAUTH3.api['domains.extend'] = function (providerUri, opts) {
|
||||
var session = opts.session;
|
||||
|
||||
return OAUTH3.request({
|
||||
method: 'POST'
|
||||
, url: OAUTH3.url.normalize(providerUri)
|
||||
+ '/api/com.daplie.domains/accounts/' + session.token.sub + '/registrations/' + opts.data.tld + '/' + opts.data.sld + '/extend'
|
||||
, session: session
|
||||
, data: opts.data
|
||||
}).then(function (res) {
|
||||
return res;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
OAUTH3.api['ns.list'] = function (providerUri, opts) {
|
||||
var session = opts.session;
|
||||
var domain = opts.domain;
|
||||
var nameArr = domain.split('.');
|
||||
var reverseNameArr = nameArr.reverse();
|
||||
var nameSubArr = reverseNameArr.slice(3);
|
||||
var tld;
|
||||
var sld;
|
||||
var sub;
|
||||
|
||||
if (reverseNameArr[0] === 'me' && reverseNameArr[1] === 'daplie') {
|
||||
tld = 'daplie.me';
|
||||
sld = reverseNameArr[2];
|
||||
sub = nameSubArr.reverse().join('.') || '';
|
||||
} else {
|
||||
tld = nameArr[0];
|
||||
sld = nameArr[1];
|
||||
sub = reverseNameArr.slice(2).reverse().join('.') || '';
|
||||
}
|
||||
|
||||
return OAUTH3.request({
|
||||
method: 'GET'
|
||||
, url: OAUTH3.url.normalize(providerUri)
|
||||
+ '/api/com.daplie.domains/accounts/' + session.token.sub + '/ns/'
|
||||
+ tld + '/' + sld + '/' + sub
|
||||
, session: session
|
||||
}).then(function (res) {
|
||||
return res.data;
|
||||
});
|
||||
};
|
||||
|
||||
OAUTH3.api['ns.add'] = function (providerUri, opts) {
|
||||
var session = opts.session;
|
||||
var server = opts.server;
|
||||
var tld = opts.tld;
|
||||
var sld = opts.sld;
|
||||
var sub = opts.sub;
|
||||
|
||||
return OAUTH3.request({
|
||||
method: 'POST'
|
||||
, url: OAUTH3.url.normalize(providerUri)
|
||||
+ '/api/com.daplie.domains/accounts/' + session.token.sub + '/ns/'
|
||||
+ tld + '/' + sld + '/' + sub
|
||||
, session: session
|
||||
, data: { nameservers: [server] }
|
||||
}).then(function (res) {
|
||||
return res;
|
||||
});
|
||||
};
|
||||
|
||||
OAUTH3.api['glue.list'] = function (providerUri, opts) {
|
||||
var session = opts.session;
|
||||
|
||||
return OAUTH3.request({
|
||||
method: 'GET'
|
||||
, url: OAUTH3.url.normalize(providerUri)
|
||||
+ '/api/com.daplie.domains/accounts/' + session.token.sub + '/glue'
|
||||
, session: session
|
||||
}).then(function (res) {
|
||||
return res.data;
|
||||
});
|
||||
};
|
||||
|
||||
OAUTH3.api['glue.add'] = function (providerUri, opts) {
|
||||
var session = opts.session;
|
||||
var ip = opts.ip;
|
||||
var tld = opts.tld;
|
||||
var sld = opts.sld;
|
||||
var sub = (opts.sub || '@');
|
||||
|
||||
return OAUTH3.request({
|
||||
method: 'POST'
|
||||
, url: OAUTH3.url.normalize(providerUri)
|
||||
+ '/api/com.daplie.domains/accounts/' + session.token.sub + '/glue/'
|
||||
+ tld + '/' + sld + '/' + sub
|
||||
, session: session
|
||||
, data: { ip: ip }
|
||||
}, {}).then(function (res) {
|
||||
return res;
|
||||
});
|
||||
};
|
||||
|
||||
}('undefined' !== typeof exports ? exports : window));
|
||||
|
811
oauth3.issuer.js
811
oauth3.issuer.js
@ -3,39 +3,6 @@
|
||||
|
||||
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) {
|
||||
// TODO browser
|
||||
// Node should replace this
|
||||
@ -58,8 +25,16 @@ OAUTH3.url._isRedirectHostSafe = function (referrerUrl, redirectUrl) {
|
||||
};
|
||||
OAUTH3.url.checkRedirect = function (client, query) {
|
||||
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);
|
||||
|
||||
// General rule:
|
||||
@ -72,6 +47,18 @@ OAUTH3.url.checkRedirect = function (client, query) {
|
||||
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;
|
||||
};
|
||||
OAUTH3.url.redirect = function (clientParams, grants, tokenOrError) {
|
||||
@ -110,13 +97,11 @@ OAUTH3.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
|
||||
// POST https://example.com/api/issuer@oauth3.org/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) {
|
||||
@ -125,16 +110,13 @@ OAUTH3.urls.resourceOwnerPassword = function (directive, opts) {
|
||||
}
|
||||
}
|
||||
|
||||
var scope = opts.scope || directive.authn_scope;
|
||||
var clientAgreeTos = 'oauth3.org/tos/draft'; // opts.clientAgreeTos || opts.client_agree_tos;
|
||||
var clientUri = opts.client_uri;
|
||||
var args = directive[type];
|
||||
var args = directive.access_token;
|
||||
var otpCode = opts.otp || opts.otpCode || opts.otp_code || opts.otpToken || opts.otp_token || undefined;
|
||||
// TODO require user agent
|
||||
var params = {
|
||||
client_id: opts.client_id || opts.client_uri
|
||||
, client_uri: opts.client_uri
|
||||
, grant_type: grantType
|
||||
, grant_type: 'password'
|
||||
, username: opts.username
|
||||
, password: opts.password || otpCode || 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
|
||||
, debug: opts.debug || undefined
|
||||
};
|
||||
var uri = args.url;
|
||||
var body;
|
||||
if (opts.totp) {
|
||||
params.totp = opts.totp;
|
||||
}
|
||||
|
||||
if (clientUri) {
|
||||
params.clientAgreeTos = clientAgreeTos;
|
||||
if (!clientAgreeTos) {
|
||||
if (opts.client_uri) {
|
||||
params.clientAgreeTos = 'oauth3.org/tos/draft'; // opts.clientAgreeTos || opts.client_agree_tos;
|
||||
if (!params.clientAgreeTos) {
|
||||
throw new Error('Developer Error: missing clientAgreeTos uri');
|
||||
}
|
||||
}
|
||||
|
||||
var scope = opts.scope || directive.authn_scope;
|
||||
if (scope) {
|
||||
params.scope = OAUTH3.scope.stringify(scope);
|
||||
}
|
||||
|
||||
var uri = args.url;
|
||||
var body;
|
||||
if ('GET' === args.method.toUpperCase()) {
|
||||
uri += '?' + OAUTH3.query.stringify(params);
|
||||
} else {
|
||||
@ -173,7 +153,7 @@ OAUTH3.urls.resourceOwnerPassword = function (directive, opts) {
|
||||
}
|
||||
|
||||
return {
|
||||
url: OAUTH3.url.resolve(directive.issuer, uri)
|
||||
url: OAUTH3.url.resolve(directive.api, uri)
|
||||
, method: args.method
|
||||
, data: body
|
||||
};
|
||||
@ -181,6 +161,10 @@ OAUTH3.urls.resourceOwnerPassword = function (directive, opts) {
|
||||
OAUTH3.urls.grants = function (directive, opts) {
|
||||
// directive = { issuer, authorization_decision }
|
||||
// opts = { response_type, scopes{ granted, requested, pending, accepted } }
|
||||
var grantsDir = directive.grants;
|
||||
if (!grantsDir) {
|
||||
throw new Error("provider doesn't support grants");
|
||||
}
|
||||
|
||||
if (!opts) {
|
||||
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");
|
||||
}
|
||||
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 ('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)) {
|
||||
throw new Error("You must supply options.response_type as 'token' or 'code'");
|
||||
if ('string' !== typeof opts.sub) {
|
||||
console.log("provide 'sub' to urls.grants to specify the PPID for the client");
|
||||
}
|
||||
}
|
||||
|
||||
var url = OAUTH3.url.resolve(directive.issuer, directive.grants.url)
|
||||
.replace(/(:azp|:client_id)/g, OAUTH3.uri.normalize(opts.client_id || opts.client_uri))
|
||||
.replace(/(:sub|:account_id)/g, opts.session.token.sub)
|
||||
var url = OAUTH3.url.resolve(directive.api, grantsDir.url)
|
||||
.replace(/(:sub|:account_id)/g, opts.session.token.sub || 'ISSUER:GRANT:TOKEN_SUB:UNDEFINED')
|
||||
.replace(/(:azp|:client_id)/g, !opts.all && OAUTH3.uri.normalize(opts.client_id || opts.client_uri) || '')
|
||||
.replace(/\/\/$/, '/') // if there's a double slash due to the sub not existing
|
||||
;
|
||||
var data = {
|
||||
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
|
||||
, sub: opts.sub
|
||||
};
|
||||
var body;
|
||||
|
||||
var body;
|
||||
if ('GET' === opts.method) {
|
||||
url += '?' + OAUTH3.query.stringify(data);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
body = data;
|
||||
}
|
||||
|
||||
@ -234,23 +218,98 @@ OAUTH3.urls.grants = function (directive, opts) {
|
||||
, 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) {
|
||||
var url = OAUTH3.urls.credentialMeta(directive, opts);
|
||||
return OAUTH3.request({
|
||||
method: directive.credential_meta.method || 'GET'
|
||||
// TODO lint urls
|
||||
// TODO client_uri
|
||||
, url: OAUTH3.url.resolve(directive.issuer, directive.credential_meta.url)
|
||||
.replace(':type', 'email')
|
||||
.replace(':id', opts.email)
|
||||
, url: url
|
||||
});
|
||||
};
|
||||
OAUTH3.authn.otp = function (directive, opts) {
|
||||
OAUTH3.urls.otp = function (directive, opts) {
|
||||
// TODO client_uri
|
||||
var preq = {
|
||||
return {
|
||||
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: {
|
||||
// TODO replace with signed hosted file
|
||||
client_agree_tos: 'oauth3.org/tos/draft'
|
||||
@ -261,20 +320,17 @@ OAUTH3.authn.otp = function (directive, opts) {
|
||||
, username: opts.email
|
||||
}
|
||||
};
|
||||
};
|
||||
OAUTH3.authn.otp = function (directive, opts) {
|
||||
var preq = OAUTH3.urls.otp(directive, opts);
|
||||
|
||||
return OAUTH3.request(preq);
|
||||
};
|
||||
OAUTH3.authn.resourceOwnerPassword = function (directive, opts) {
|
||||
var providerUri = directive.issuer;
|
||||
|
||||
//var scope = opts.scope;
|
||||
//var appId = opts.appId;
|
||||
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;
|
||||
return OAUTH3.request(OAUTH3.urls.resourceOwnerPassword(directive, opts)).then(function (resp) {
|
||||
var data = resp.data;
|
||||
data.provider_uri = providerUri;
|
||||
if (data.error) {
|
||||
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 }
|
||||
, 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.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 scope = clientParams.scope || '';
|
||||
var clientObj = clientParams;
|
||||
var scope = clientParams.scope || 'authn@oauth3.org';
|
||||
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) {
|
||||
scope = 'oauth3_authn';
|
||||
return OAUTH3.hooks.grants.get(session.token.sub, clientUri).then(function (granted) {
|
||||
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, {
|
||||
@ -305,74 +393,31 @@ OAUTH3.authz.scopes = function (providerUri, session, clientParams) {
|
||||
, 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;
|
||||
|
||||
// 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.");
|
||||
}).then(function (results) {
|
||||
return results.grants;
|
||||
}, function (err) {
|
||||
if (!/no .*grants .*found/i.test(err.message)) {
|
||||
throw err;
|
||||
}
|
||||
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).grants.filter(function (grant) {
|
||||
if (clientUri === (grant.azp || grant.oauth_client_id || grant.oauthClientId)) {
|
||||
return true;
|
||||
return [];
|
||||
});
|
||||
}).then(function (granted) {
|
||||
var requested = OAUTH3.scope.parse(scope);
|
||||
var accepted = [];
|
||||
var pending = [];
|
||||
requested.forEach(function (scp) {
|
||||
if (granted.indexOf(scp) < 0) {
|
||||
pending.push(scp);
|
||||
} else {
|
||||
accepted.push(scp);
|
||||
}
|
||||
});
|
||||
|
||||
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)
|
||||
requested: requested // all requested, now
|
||||
, granted: granted // all granted, ever
|
||||
, accepted: accepted // intersection of granted (ever) and requested (now)
|
||||
, pending: pending // not yet accepted
|
||||
};
|
||||
});
|
||||
};
|
||||
@ -381,144 +426,406 @@ OAUTH3.authz.grants = function (providerUri, opts) {
|
||||
client_id: providerUri
|
||||
, debug: opts.debug
|
||||
}).then(function (directive) {
|
||||
|
||||
return OAUTH3.request(OAUTH3.urls.grants(directive, opts), opts).then(function (grantsResult) {
|
||||
if ('POST' === opts.method) {
|
||||
// TODO this is clientToken
|
||||
return grantsResult.originalData || grantsResult.data;
|
||||
}
|
||||
|
||||
return OAUTH3.request(OAUTH3.urls.grants(directive, opts), opts);
|
||||
}).then(function (grantsResult) {
|
||||
var grants = grantsResult.originalData || grantsResult.data;
|
||||
// TODO
|
||||
if (grants.error) {
|
||||
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);
|
||||
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 ]);
|
||||
});
|
||||
|
||||
OAUTH3.hooks.grants.set(grants.sub, grants.azp, grants);
|
||||
return {
|
||||
client: OAUTH3.hooks.grants.get(opts.client_id + '-client')
|
||||
, grants: OAUTH3.hooks.grants.get(opts.client_id) || []
|
||||
client: grants.azp
|
||||
, 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) {
|
||||
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);
|
||||
}
|
||||
|
||||
var prom;
|
||||
if (scopes.new) {
|
||||
prom = OAUTH3.authz.grants(providerUri, {
|
||||
session: session
|
||||
, method: 'POST'
|
||||
, client_id: clientParams.client_uri
|
||||
, referrer: clientParams.referrer
|
||||
, scope: scopes.accepted.concat(scopes.new).join(',')
|
||||
});
|
||||
} else {
|
||||
prom = OAUTH3.PromiseA.resolve();
|
||||
}
|
||||
|
||||
return prom.then(function () {
|
||||
return OAUTH3.hooks.keyPairs.get(session.token.sub);
|
||||
}).then(function (keyPair) {
|
||||
if (!keyPair) {
|
||||
return OAUTH3.discover(providerUri, {
|
||||
client_id: providerUri
|
||||
, debug: clientParams.debug
|
||||
}).then(function (directive) {
|
||||
return OAUTH3.request(OAUTH3.urls.clientToken(directive, {
|
||||
method: 'POST'
|
||||
, session: session
|
||||
, referrer: clientParams.referrer
|
||||
, response_type: clientParams.response_type
|
||||
, client_id: clientParams.client_uri
|
||||
, azp: clientParams.client_uri
|
||||
, aud: clientParams.aud
|
||||
, exp: clientParams.exp
|
||||
, refresh_token: clientParams.refresh_token
|
||||
, refresh_exp: clientParams.refresh_exp
|
||||
, debug: clientParams.debug
|
||||
})).then(function (result) {
|
||||
return result.originalData || result.data;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return OAUTH3.hooks.grants.get(keyPair.sub, clientParams.client_uri).then(function (grant) {
|
||||
var now = Math.floor(Date.now()/1000);
|
||||
var payload = {
|
||||
iat: now
|
||||
, iss: providerUri
|
||||
, aud: clientParams.aud || providerUri
|
||||
, azp: clientParams.client_uri
|
||||
, sub: grant.azpSub
|
||||
, scope: OAUTH3.scope.stringify(grant.scope)
|
||||
, };
|
||||
|
||||
var signProms = [];
|
||||
signProms.push(OAUTH3.jwt.sign(Object.assign({
|
||||
exp: calcExpiration(clientParams.exp || '1h', now)
|
||||
}, payload), keyPair));
|
||||
// if (clientParams.refresh_token) {
|
||||
signProms.push(OAUTH3.jwt.sign(Object.assign({
|
||||
exp: calcExpiration(clientParams.refresh_exp, now)
|
||||
}, payload), keyPair));
|
||||
// }
|
||||
return OAUTH3.PromiseA.all(signProms).then(function (tokens) {
|
||||
console.log('created new tokens for client');
|
||||
return {
|
||||
access_token: tokens[0]
|
||||
, refresh_token: tokens[1]
|
||||
, scope: OAUTH3.scope.stringify(grant.scope)
|
||||
, token_type: 'bearer'
|
||||
};
|
||||
});
|
||||
});
|
||||
};
|
||||
OAUTH3.authz.redirectWithToken = function (providerUri, session, clientParams, scopes) {
|
||||
|
||||
scopes.new = scopes.new || [];
|
||||
|
||||
if ('token' === clientParams.response_type) {
|
||||
// get token and redirect client-side
|
||||
return OAUTH3.authz.grants(providerUri, {
|
||||
method: 'POST'
|
||||
, 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
|
||||
, session: session
|
||||
, debug: clientParams.debug
|
||||
}).then(function (results) {
|
||||
|
||||
OAUTH3.url.redirect(clientParams, scopes, results);
|
||||
}).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});
|
||||
});
|
||||
}
|
||||
else if ('code' === clientParams.response_type) {
|
||||
// get token and redirect server-side
|
||||
// (requires insecure form post as per spec)
|
||||
//OAUTH3.requests.authorizationDecision();
|
||||
window.alert("Authorization Code Redirect NOT IMPLEMENTED");
|
||||
throw new Error("Authorization Code Redirect NOT IMPLEMENTED");
|
||||
}
|
||||
};
|
||||
|
||||
OAUTH3.requests = {};
|
||||
//OAUTH3.accounts = {};
|
||||
OAUTH3.requests.accounts = {};
|
||||
OAUTH3.requests.accounts.update = function (directive, session, opts) {
|
||||
var dir = directive.update_account || {
|
||||
method: 'POST'
|
||||
, url: 'https://' + directive.provider_url + '/api/org.oauth3.provider/accounts/:accountId'
|
||||
OAUTH3.urls.accounts = {};
|
||||
OAUTH3.urls.accounts._ = function (directives, directive, session, opts) {
|
||||
opts = opts || {};
|
||||
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'
|
||||
};
|
||||
var url = dir.url
|
||||
.replace(/:accountId/, opts.accountId)
|
||||
.replace(/:accountId/, opts.accountId || '')
|
||||
.replace(/\/$/, '')
|
||||
;
|
||||
|
||||
return OAUTH3.request({
|
||||
method: dir.method || 'POST'
|
||||
, url: url
|
||||
return {
|
||||
url: url
|
||||
//, method: dir.method || 'POST'
|
||||
, session: session
|
||||
/*
|
||||
, 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
|
||||
, comment: opts.comment
|
||||
, displayName: opts.displayName
|
||||
, priority: opts.priority
|
||||
}
|
||||
});
|
||||
};
|
||||
OAUTH3.requests.accounts.create = function (directive, session, account) {
|
||||
var dir = directive.create_account || {
|
||||
method: 'POST'
|
||||
, url: 'https://' + directive.issuer + '/api/org.oauth3.provider/accounts'
|
||||
, bearer: 'Bearer'
|
||||
};
|
||||
var data = {
|
||||
return urlObj;
|
||||
};
|
||||
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
|
||||
// account = { nick, self: { comment, username } }
|
||||
// account = { name, comment, display_name, priority }
|
||||
account: {
|
||||
nick: account.display_name
|
||||
, name: account.name
|
||||
, comment: account.comment
|
||||
, display_name: account.display_name
|
||||
, priority: account.priority
|
||||
, self: {
|
||||
nick: account.display_name
|
||||
, name: account.name
|
||||
, comment: account.comment
|
||||
, display_name: account.display_name
|
||||
, priority: account.priority
|
||||
}
|
||||
}
|
||||
, logins: [
|
||||
{
|
||||
token: session.access_token
|
||||
}
|
||||
]
|
||||
credentials: credentials
|
||||
, profile: profile
|
||||
// 'account' is deprecated in favor of 'profile'
|
||||
, account: profile
|
||||
// 'logins' is deprecated in favor of 'credentials'
|
||||
, logins: credentials
|
||||
};
|
||||
return urlObj;
|
||||
};
|
||||
OAUTH3.requests.accounts.get = function (directives, session) {
|
||||
var urlObj = OAUTH3.urls.accounts.get(directives, session);
|
||||
return OAUTH3.request(urlObj);
|
||||
};
|
||||
OAUTH3.requests.accounts.update = function (directives, session, opts) {
|
||||
var urlObj = OAUTH3.urls.accounts.update(directives, session, opts);
|
||||
return OAUTH3.request(urlObj);
|
||||
};
|
||||
OAUTH3.requests.accounts.create = function (directive, session, account) {
|
||||
var urlObj = OAUTH3.urls.accounts.create(directives, session, account);
|
||||
return OAUTH3.request(urlObj);
|
||||
};
|
||||
|
||||
return OAUTH3.request({
|
||||
method: dir.method || 'POST'
|
||||
, url: dir.url
|
||||
, session: session
|
||||
, data: data
|
||||
OAUTH3.hooks.grants = {
|
||||
get: function (id, clientUri) {
|
||||
OAUTH3.hooks._checkStorage('grants', 'get');
|
||||
|
||||
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
|
||||
set: function (clientUri, newGrants) {
|
||||
clientUri = OAUTH3.uri.normalize(clientUri);
|
||||
console.warn('[oauth3.hooks.setGrants] PLEASE IMPLEMENT -- Your Fault');
|
||||
console.warn(newGrants);
|
||||
if (!this._cache) { this._cache = {}; }
|
||||
console.log('clientUri, newGrants');
|
||||
console.log(clientUri, newGrants);
|
||||
this._cache[clientUri] = newGrants;
|
||||
return newGrants;
|
||||
|
||||
OAUTH3._defaultStorage.grants = {
|
||||
prefix: 'grants-'
|
||||
, get: function (id, clientUri) {
|
||||
var key = this.prefix + id+'/'+clientUri;
|
||||
var result = JSON.parse(window.localStorage.getItem(key) || 'null');
|
||||
return OAUTH3.PromiseA.resolve(result);
|
||||
}
|
||||
, get: function (clientUri) {
|
||||
clientUri = OAUTH3.uri.normalize(clientUri);
|
||||
console.warn('[oauth3.hooks.getGrants] PLEASE IMPLEMENT -- Your Fault');
|
||||
if (!this._cache) { this._cache = {}; }
|
||||
console.log('clientUri, existingGrants');
|
||||
console.log(clientUri, this._cache[clientUri]);
|
||||
return this._cache[clientUri];
|
||||
, set: function (id, clientUri, grants) {
|
||||
var key = this.prefix + id+'/'+clientUri;
|
||||
window.localStorage.setItem(key, JSON.stringify(grants));
|
||||
return this.get(clientUri);
|
||||
}
|
||||
, all: function () {
|
||||
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 () {
|
||||
return OAUTH3.PromiseA.resolve({
|
||||
pending: ['oauth3_authn'] // not yet accepted
|
||||
pending: [ 'authn@oauth3.org' ] // not yet accepted
|
||||
, granted: [] // all granted, ever
|
||||
, requested: ['oauth3_authn'] // all requested, now
|
||||
, requested: [ 'authn@oauth3.org' ] // all requested, now
|
||||
, accepted: [] // granted (ever) and requested (now)
|
||||
});
|
||||
};
|
||||
|
22
oauth3.ng.js
22
oauth3.ng.js
@ -1,13 +1,12 @@
|
||||
;(function () {
|
||||
'use strict';
|
||||
|
||||
angular
|
||||
.module('org.oauth3', [])
|
||||
.service('Oauth3', [
|
||||
var modules = {
|
||||
azp: [
|
||||
'$timeout'
|
||||
, '$q'
|
||||
, function Oauth3($timeout, $q) {
|
||||
|
||||
, '$rootScope'
|
||||
, function Oauth3($timeout, $q, $rootScope) {
|
||||
var OAUTH3 = window.OAUTH3;
|
||||
|
||||
// We need to make angular's $q appear to be a standard Promise/A+
|
||||
@ -30,9 +29,20 @@ angular
|
||||
PromiseAngularQ.all = $q.all;
|
||||
|
||||
OAUTH3.PromiseA = PromiseAngularQ;
|
||||
OAUTH3._digest = function () {
|
||||
$rootScope.$digest();
|
||||
};
|
||||
|
||||
window.ngOauth3 = OAUTH3;
|
||||
|
||||
return OAUTH3;
|
||||
}]);
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
angular
|
||||
.module('oauth3.org', [])
|
||||
.service('azp@oauth3.org', modules.azp)
|
||||
.service('AzpOauth3', modules.azp)
|
||||
;
|
||||
}());
|
||||
|
@ -28,6 +28,7 @@ OAUTH3._base64.atob = function (base64) {
|
||||
OAUTH3._base64.btoa = function (text) {
|
||||
return new Buffer(text, 'utf8').toString('base64');
|
||||
};
|
||||
OAUTH3._defaultStorage = require('./oauth3.node.storage');
|
||||
|
||||
OAUTH3._node = {};
|
||||
OAUTH3._node.discover = function(providerUri/*, opts*/) {
|
||||
@ -43,6 +44,7 @@ OAUTH3._node.request = function(preq/*, _sys*/) {
|
||||
method: preq.method
|
||||
, url: preq.url || preq.uri
|
||||
, headers: preq.headers
|
||||
, timeout: preq.timeout || undefined
|
||||
, json: preq.data || preq.body || preq.json || undefined // TODO which to use?
|
||||
, formData: preq.formData || undefined
|
||||
};
|
||||
@ -59,10 +61,7 @@ OAUTH3._node._parseJson = function (resp) {
|
||||
|
||||
// TODO toCamelCase
|
||||
if (!(resp.statusCode >= 200 && resp.statusCode < 400)) {
|
||||
// console.log('[A3] DEBUG', resp.body);
|
||||
err = new Error("bad response code: " + resp.statusCode);
|
||||
err.result = resp.body;
|
||||
return PromiseA.reject(err);
|
||||
}
|
||||
|
||||
//console.log('resp.body', typeof resp.body);
|
||||
@ -70,15 +69,16 @@ OAUTH3._node._parseJson = function (resp) {
|
||||
try {
|
||||
json = JSON.parse(json);
|
||||
} catch(e) {
|
||||
err = new Error('response not parsable:' + resp.body);
|
||||
err.result = resp.body;
|
||||
return PromiseA.reject(err);
|
||||
err = err || (new Error('response not parsable: ' + resp.body));
|
||||
}
|
||||
}
|
||||
|
||||
// handle both Oauth2- and node-style errors
|
||||
if (json.error) {
|
||||
err = new Error(json.error && json.error.message || json.error_description || json.error);
|
||||
if (json && json.error) {
|
||||
err = new Error(json.error.message || json.error_description || JSON.stringify(json.error));
|
||||
}
|
||||
|
||||
if (err) {
|
||||
err.result = json;
|
||||
return PromiseA.reject(err);
|
||||
}
|
||||
|
@ -1,46 +1,137 @@
|
||||
'use strict';
|
||||
|
||||
var fs = require('fs');
|
||||
var PromiseA = require('bluebird');
|
||||
var fs = PromiseA.promisifyAll(require('fs'));
|
||||
var path = require('path');
|
||||
//var oauth3dir = process.cwd();
|
||||
var oauth3dir = path.join(require('os').homedir(), '.oauth3', 'v1');
|
||||
var sessionsdir = path.join(oauth3dir, 'sessions');
|
||||
var directivesdir = path.join(oauth3dir, 'directives');
|
||||
var metadir = path.join(oauth3dir, 'meta');
|
||||
|
||||
// We can reasonably assume the existence of the home directory, but we can't assume
|
||||
// that there will already be a `.oauth3` directory or anything inside of it.
|
||||
if (!fs.existsSync(path.join(oauth3dir, '..'))) {
|
||||
fs.mkdirSync(path.join(oauth3dir, '..'));
|
||||
}
|
||||
if (!fs.existsSync(oauth3dir)) {
|
||||
fs.mkdirSync(oauth3dir);
|
||||
}
|
||||
if (!fs.existsSync(directivesdir)) {
|
||||
fs.mkdirSync(directivesdir);
|
||||
}
|
||||
if (!fs.existsSync(sessionsdir)) {
|
||||
fs.mkdirSync(sessionsdir);
|
||||
}
|
||||
if (!fs.existsSync(metadir)) {
|
||||
fs.mkdirSync(metadir);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
directives: {
|
||||
get: function (providerUri) {
|
||||
all: function () {
|
||||
return fs.readdirAsync(directivesdir).then(function (nodes) {
|
||||
return nodes.map(function (node) {
|
||||
try {
|
||||
return require(path.join(directivesdir, node));
|
||||
} catch(e) {
|
||||
return null;
|
||||
}
|
||||
}).filter(Boolean);
|
||||
});
|
||||
}
|
||||
, get: function (providerUri) {
|
||||
// TODO make safe
|
||||
try {
|
||||
return require(path.join(process.cwd(), providerUri + '.directives.json'));
|
||||
return require(path.join(directivesdir, providerUri + '.json'));
|
||||
} catch(e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
, set: function (providerUri, directives) {
|
||||
fs.writeFileSync(path.join(process.cwd(), providerUri + '.directives.json'), JSON.stringify(directives, null, 2));
|
||||
return fs.writeFileAsync(
|
||||
path.join(directivesdir, providerUri + '.json')
|
||||
, JSON.stringify(directives, null, 2)
|
||||
).then(function () {
|
||||
return directives;
|
||||
});
|
||||
}
|
||||
, clear: function () {
|
||||
return fs.readdirAsync(directivesdir).then(function (nodes) {
|
||||
return PromiseA.all(nodes.map(function (node) {
|
||||
return fs.unlinkAsync(path.join(directivesdir, node)).then(function () { }, function () { });
|
||||
}));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
, sessions: {
|
||||
get: function (providerUri, id) {
|
||||
// TODO make safe
|
||||
try {
|
||||
if (id) {
|
||||
return require(path.join(process.cwd(), providerUri + '.' + id + '.session.json'));
|
||||
}
|
||||
else {
|
||||
return require(path.join(process.cwd(), providerUri + '.session.json'));
|
||||
}
|
||||
} catch(e) {
|
||||
all: function (providerUri) {
|
||||
return fs.readdirAsync(sessionsdir).then(function (nodes) {
|
||||
return nodes.map(function (node) {
|
||||
var result = require(path.join(sessionsdir, node));
|
||||
if (result.link) {
|
||||
return null;
|
||||
}
|
||||
}).filter(Boolean).filter(function (result) {
|
||||
if (!providerUri || providerUri === result.issuer) {
|
||||
return result;
|
||||
}
|
||||
, set: function (providerUri, session, id) {
|
||||
});
|
||||
});
|
||||
}
|
||||
, get: function (providerUri, id) {
|
||||
var result;
|
||||
try {
|
||||
if (id) {
|
||||
fs.writeFileSync(path.join(process.cwd(), providerUri + '.' + id + '.session.json'), JSON.stringify(session, null, 2));
|
||||
return PromiseA.resolve(require(path.join(sessionsdir, providerUri + '.' + id + '.json')));
|
||||
}
|
||||
else {
|
||||
fs.writeFileSync(path.join(process.cwd(), providerUri + '.session.json'), JSON.stringify(session, null, 2));
|
||||
result = require(path.join(sessionsdir, providerUri + '.json'));
|
||||
// TODO make safer
|
||||
if (result.link && '/' !== result.link[0] && !/\.\./.test(result.link)) {
|
||||
result = require(path.join(sessionsdir, result.link));
|
||||
}
|
||||
}
|
||||
} catch(e) {
|
||||
return PromiseA.resolve(null);
|
||||
}
|
||||
return PromiseA.resolve(result);
|
||||
}
|
||||
, set: function (providerUri, session, id) {
|
||||
var p;
|
||||
|
||||
if (id) {
|
||||
p = fs.writeFileAsync(path.join(sessionsdir, providerUri + '.' + id + '.json'), JSON.stringify(session, null, 2));
|
||||
}
|
||||
else {
|
||||
p = fs.writeFileAsync(path.join(sessionsdir, providerUri + '.json'), JSON.stringify(session, null, 2));
|
||||
}
|
||||
return p.then(function () {
|
||||
return session;
|
||||
});
|
||||
}
|
||||
, clear: function () {
|
||||
return fs.readdirAsync(sessionsdir).then(function (nodes) {
|
||||
return PromiseA.all(nodes.map(function (node) {
|
||||
return fs.unlinkAsync(path.join(sessionsdir, node));
|
||||
}));
|
||||
});
|
||||
}
|
||||
}
|
||||
, meta: {
|
||||
get: function (key) {
|
||||
// TODO make safe
|
||||
try {
|
||||
return PromiseA.resolve(require(path.join(metadir, key + '.json')));
|
||||
} catch(e) {
|
||||
return PromiseA.resolve(null);
|
||||
}
|
||||
}
|
||||
, set: function (key, value) {
|
||||
return fs.writeFileAsync(path.join(metadir, key + '.json'), JSON.stringify(value, null, 2)).then(function () {
|
||||
return value;
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -1,66 +0,0 @@
|
||||
{
|
||||
"provider_uri": "https://oauth3.org",
|
||||
"client_uri": "oauth3.org",
|
||||
"token_type": "bearer",
|
||||
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJqdGkiOiIzOTZjMzJlNzE1NmIzNGI1ZWY1ZWYwZWU4Zjk3Y2NiMyIsImlhdCI6MTQ5MDE5NzUyNywiaXNzIjoib2F1dGgzLm9yZyIsImF1ZCI6Im9hdXRoMy5vcmciLCJhenAiOiJvYXV0aDMub3JnIiwic3ViIjoiIiwia2lkIjoib2F1dGgzLm9yZyIsInNjcCI6IiIsImFzIjoibG9naW4iLCJncnQiOiJwYXNzd29yZCIsInNydiI6ZmFsc2UsImsiOiJvYXV0aDMub3JnIiwiYXBwIjoib2F1dGgzLm9yZyIsImFjeCI6eyJpZCI6IjE1LUxhM3JnZXBFelBCR0xITHlrdEZOT1NDZFNVOXZJdjlKc2EzTkMxYVJUc3ZmUTZ5cDJuVFFfZWxmdkhzYTEifSwiYXhzIjpbXSwidXNyIjoiYTM3YWVkYTk5ZDQ5MThhMDM0YzM0MmQ2NGNkZjRiN2VkMjM0ZGZlNSIsImFjYyI6eyJpZCI6IjUzZTUwMTk2LTE4ZTMtNGJlNi04NDcyLTQ1ZDBjNDMxZjdhZCJ9LCJhY3MiOltdLCJpZHgiOiJuZnZ1bHRETE0tT0EzVUV3dVJHTDE3RFY1UXpIbWhac005Z2xMdnFLVGJacGh1T0NqMnBEUzByRk9XSXhaRjZLIiwidG9rZW5UeXBlIjoiYmVhcmVyIiwiZXhwIjoxNDkwMTk5MzI3LCJpcCI6IjIwNy4xNzMuMTY1LjUwIn0.qQu6NdsU4oVucv4uV_jusfL2HKgnPpfwF6iVG0H-P08akDtGgDoXcyVfl6hQdpVL9DGYVwvCPPUkLT0bJztM08lWhg69dVs-2e2I2BhjClsKeLsrFDBrUMwWVqqzCNVj8WBzcULLtl_mEgZc1qwVpZvXXiu0vmrRl3gtzVRaLL0",
|
||||
"scope": "",
|
||||
"expires_in": 1800,
|
||||
"refresh_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJqdGkiOiI0OGJhNjJhNTQxNGFlODQ3OWJhMzA0MGQ1Mzc5NmY3MiIsImlhdCI6MTQ5MDE5NzUyNywiaXNzIjoib2F1dGgzLm9yZyIsImF1ZCI6Im9hdXRoMy5vcmciLCJhenAiOiJvYXV0aDMub3JnIiwic3ViIjoiIiwia2lkIjoib2F1dGgzLm9yZyIsInNjcCI6IiIsImFzIjoibG9naW4iLCJncnQiOiJwYXNzd29yZCIsInNydiI6ZmFsc2UsImsiOiJvYXV0aDMub3JnIiwiYXBwIjoib2F1dGgzLm9yZyIsImFjeCI6eyJpZCI6IjE1LUxhM3JnZXBFelBCR0xITHlrdEZOT1NDZFNVOXZJdjlKc2EzTkMxYVJUc3ZmUTZ5cDJuVFFfZWxmdkhzYTEifSwiYXhzIjpbXSwidXNyIjoiYTM3YWVkYTk5ZDQ5MThhMDM0YzM0MmQ2NGNkZjRiN2VkMjM0ZGZlNSIsImFjYyI6eyJpZCI6IjUzZTUwMTk2LTE4ZTMtNGJlNi04NDcyLTQ1ZDBjNDMxZjdhZCJ9LCJhY3MiOltdLCJpZHgiOiJuZnZ1bHRETE0tT0EzVUV3dVJHTDE3RFY1UXpIbWhac005Z2xMdnFLVGJacGh1T0NqMnBEUzByRk9XSXhaRjZLIiwicmVmcmVzaCI6dHJ1ZX0.QfufVyAGit2YOy9Hs9mv4eoCuyCYb9FDT_UXGd3JaFZe6MwqxLLnq2fWkkV2jgzDAK5t0JMu2Vk91jPP2IBXMkpZSzjaEKJ3-Eokb14Mo5GIrp54ndM20gWVZc-ReQtOUtSVG28bfnOBT5ceUM6SBrTxfz1ENOfmAiWl5591roQ",
|
||||
"token": {
|
||||
"jti": "396c32e7156b34b5ef5ef0ee8f97ccb3",
|
||||
"iat": 1490197527,
|
||||
"iss": "oauth3.org",
|
||||
"aud": "oauth3.org",
|
||||
"azp": "oauth3.org",
|
||||
"sub": "15-La3rgepEzPBGLHLyktFNOSCdSU9vIv9Jsa3NC1aRTsvfQ6yp2nTQ_elfvHsa1",
|
||||
"kid": "oauth3.org",
|
||||
"scp": "",
|
||||
"as": "login",
|
||||
"grt": "password",
|
||||
"srv": false,
|
||||
"k": "oauth3.org",
|
||||
"app": "oauth3.org",
|
||||
"acx": {
|
||||
"id": "15-La3rgepEzPBGLHLyktFNOSCdSU9vIv9Jsa3NC1aRTsvfQ6yp2nTQ_elfvHsa1"
|
||||
},
|
||||
"axs": [],
|
||||
"usr": "a37aeda99d4918a034c342d64cdf4b7ed234dfe5",
|
||||
"acc": {
|
||||
"id": "53e50196-18e3-4be6-8472-45d0c431f7ad"
|
||||
},
|
||||
"acs": [],
|
||||
"idx": "nfvultDLM-OA3UEwuRGL17DV5QzHmhZsM9glLvqKTbZphuOCj2pDS0rFOWIxZF6K",
|
||||
"tokenType": "bearer",
|
||||
"exp": 1490199327,
|
||||
"ip": "207.173.165.50",
|
||||
"client_uri": "oauth3.org",
|
||||
"provider_uri": "https://oauth3.org"
|
||||
},
|
||||
"refresh": {
|
||||
"jti": "48ba62a5414ae8479ba3040d53796f72",
|
||||
"iat": 1490197527,
|
||||
"iss": "oauth3.org",
|
||||
"aud": "oauth3.org",
|
||||
"azp": "oauth3.org",
|
||||
"sub": "15-La3rgepEzPBGLHLyktFNOSCdSU9vIv9Jsa3NC1aRTsvfQ6yp2nTQ_elfvHsa1",
|
||||
"kid": "oauth3.org",
|
||||
"scp": "",
|
||||
"as": "login",
|
||||
"grt": "password",
|
||||
"srv": false,
|
||||
"k": "oauth3.org",
|
||||
"app": "oauth3.org",
|
||||
"acx": {
|
||||
"id": "15-La3rgepEzPBGLHLyktFNOSCdSU9vIv9Jsa3NC1aRTsvfQ6yp2nTQ_elfvHsa1"
|
||||
},
|
||||
"axs": [],
|
||||
"usr": "a37aeda99d4918a034c342d64cdf4b7ed234dfe5",
|
||||
"acc": {
|
||||
"id": "53e50196-18e3-4be6-8472-45d0c431f7ad"
|
||||
},
|
||||
"acs": [],
|
||||
"idx": "nfvultDLM-OA3UEwuRGL17DV5QzHmhZsM9glLvqKTbZphuOCj2pDS0rFOWIxZF6K",
|
||||
"refresh": true,
|
||||
"provider_uri": "https://oauth3.org"
|
||||
}
|
||||
}
|
@ -1,66 +0,0 @@
|
||||
{
|
||||
"provider_uri": "https://oauth3.org",
|
||||
"client_uri": "oauth3.org",
|
||||
"token_type": "bearer",
|
||||
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJqdGkiOiI4MmU5MDhlMTljMmIxM2IxNmM3N2JlMTNkMTljZmEzOSIsImlhdCI6MTQ5MDEzODg2MiwiaXNzIjoib2F1dGgzLm9yZyIsImF1ZCI6Im9hdXRoMy5vcmciLCJhenAiOiJvYXV0aDMub3JnIiwic3ViIjoiIiwia2lkIjoib2F1dGgzLm9yZyIsInNjcCI6IiIsImFzIjoibG9naW4iLCJncnQiOiJwYXNzd29yZCIsInNydiI6ZmFsc2UsImsiOiJvYXV0aDMub3JnIiwiYXBwIjoib2F1dGgzLm9yZyIsImFjeCI6eyJpZCI6IjE1LUxhM3JnZXBFelBCR0xITHlrdEZOT1NDZFNVOXZJdjlKc2EzTkMxYVJUc3ZmUTZ5cDJuVFFfZWxmdkhzYTEifSwiYXhzIjpbXSwidXNyIjoiYTM3YWVkYTk5ZDQ5MThhMDM0YzM0MmQ2NGNkZjRiN2VkMjM0ZGZlNSIsImFjYyI6eyJpZCI6IjUzZTUwMTk2LTE4ZTMtNGJlNi04NDcyLTQ1ZDBjNDMxZjdhZCJ9LCJhY3MiOltdLCJpZHgiOiJuZnZ1bHRETE0tT0EzVUV3dVJHTDE3RFY1UXpIbWhac005Z2xMdnFLVGJacGh1T0NqMnBEUzByRk9XSXhaRjZLIiwidG9rZW5UeXBlIjoiYmVhcmVyIiwiZXhwIjoxNDkwMTQwNjYyLCJpcCI6IjIwNy4xNzMuMTY1LjUwIn0.luWnALBIv9TD_mGHUIddRpOnbVAhkYO-DJtEQitODQX2IEC7cIcbSrvJBKI3i_djeMj69fm-ctr6XFUU7WiEVnBsMh55WK1gkdFqFzImo67apQ5kAV8GTGGbG___kisjl12AMvL09_shU1Sp8F8cHayTZTmSbyyWbbFKT3cZCsg",
|
||||
"scope": "",
|
||||
"expires_in": 1800,
|
||||
"refresh_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJqdGkiOiI1ZDBjMGMyNTkyNmZhNmMxOTM0MjlhZWRkMjJjOTEyYiIsImlhdCI6MTQ5MDEzODg2MiwiaXNzIjoib2F1dGgzLm9yZyIsImF1ZCI6Im9hdXRoMy5vcmciLCJhenAiOiJvYXV0aDMub3JnIiwic3ViIjoiIiwia2lkIjoib2F1dGgzLm9yZyIsInNjcCI6IiIsImFzIjoibG9naW4iLCJncnQiOiJwYXNzd29yZCIsInNydiI6ZmFsc2UsImsiOiJvYXV0aDMub3JnIiwiYXBwIjoib2F1dGgzLm9yZyIsImFjeCI6eyJpZCI6IjE1LUxhM3JnZXBFelBCR0xITHlrdEZOT1NDZFNVOXZJdjlKc2EzTkMxYVJUc3ZmUTZ5cDJuVFFfZWxmdkhzYTEifSwiYXhzIjpbXSwidXNyIjoiYTM3YWVkYTk5ZDQ5MThhMDM0YzM0MmQ2NGNkZjRiN2VkMjM0ZGZlNSIsImFjYyI6eyJpZCI6IjUzZTUwMTk2LTE4ZTMtNGJlNi04NDcyLTQ1ZDBjNDMxZjdhZCJ9LCJhY3MiOltdLCJpZHgiOiJuZnZ1bHRETE0tT0EzVUV3dVJHTDE3RFY1UXpIbWhac005Z2xMdnFLVGJacGh1T0NqMnBEUzByRk9XSXhaRjZLIiwicmVmcmVzaCI6dHJ1ZX0.XljVX_QXgnYw8gyIZjQdxfyDlAAwWtU-kLitibw2_3xo9muLFPCL_dAk5XnMRygyyh5B9H4p4qB2Gb5BEJKJRfAtQ6TeZadTBtxwoY7zcns9f4Nx59VNii4k_Xp3uhJ6fQp8ERvkMgBy52Sj5ag0PFnuIwk35wLdSfiikDGnwKo",
|
||||
"token": {
|
||||
"jti": "82e908e19c2b13b16c77be13d19cfa39",
|
||||
"iat": 1490138862,
|
||||
"iss": "oauth3.org",
|
||||
"aud": "oauth3.org",
|
||||
"azp": "oauth3.org",
|
||||
"sub": "15-La3rgepEzPBGLHLyktFNOSCdSU9vIv9Jsa3NC1aRTsvfQ6yp2nTQ_elfvHsa1",
|
||||
"kid": "oauth3.org",
|
||||
"scp": "",
|
||||
"as": "login",
|
||||
"grt": "password",
|
||||
"srv": false,
|
||||
"k": "oauth3.org",
|
||||
"app": "oauth3.org",
|
||||
"acx": {
|
||||
"id": "15-La3rgepEzPBGLHLyktFNOSCdSU9vIv9Jsa3NC1aRTsvfQ6yp2nTQ_elfvHsa1"
|
||||
},
|
||||
"axs": [],
|
||||
"usr": "a37aeda99d4918a034c342d64cdf4b7ed234dfe5",
|
||||
"acc": {
|
||||
"id": "53e50196-18e3-4be6-8472-45d0c431f7ad"
|
||||
},
|
||||
"acs": [],
|
||||
"idx": "nfvultDLM-OA3UEwuRGL17DV5QzHmhZsM9glLvqKTbZphuOCj2pDS0rFOWIxZF6K",
|
||||
"tokenType": "bearer",
|
||||
"exp": 1490140662,
|
||||
"ip": "207.173.165.50",
|
||||
"client_uri": "oauth3.org",
|
||||
"provider_uri": "https://oauth3.org"
|
||||
},
|
||||
"refresh": {
|
||||
"jti": "5d0c0c25926fa6c193429aedd22c912b",
|
||||
"iat": 1490138862,
|
||||
"iss": "oauth3.org",
|
||||
"aud": "oauth3.org",
|
||||
"azp": "oauth3.org",
|
||||
"sub": "15-La3rgepEzPBGLHLyktFNOSCdSU9vIv9Jsa3NC1aRTsvfQ6yp2nTQ_elfvHsa1",
|
||||
"kid": "oauth3.org",
|
||||
"scp": "",
|
||||
"as": "login",
|
||||
"grt": "password",
|
||||
"srv": false,
|
||||
"k": "oauth3.org",
|
||||
"app": "oauth3.org",
|
||||
"acx": {
|
||||
"id": "15-La3rgepEzPBGLHLyktFNOSCdSU9vIv9Jsa3NC1aRTsvfQ6yp2nTQ_elfvHsa1"
|
||||
},
|
||||
"axs": [],
|
||||
"usr": "a37aeda99d4918a034c342d64cdf4b7ed234dfe5",
|
||||
"acc": {
|
||||
"id": "53e50196-18e3-4be6-8472-45d0c431f7ad"
|
||||
},
|
||||
"acs": [],
|
||||
"idx": "nfvultDLM-OA3UEwuRGL17DV5QzHmhZsM9glLvqKTbZphuOCj2pDS0rFOWIxZF6K",
|
||||
"refresh": true,
|
||||
"provider_uri": "https://oauth3.org"
|
||||
}
|
||||
}
|
@ -9,7 +9,7 @@ OAUTH3.api['tunnel.token'] = function (providerUri, opts) {
|
||||
return OAUTH3.request({
|
||||
method: 'POST'
|
||||
, url: OAUTH3.url.normalize(providerUri)
|
||||
+ '/api/org.oauth3.tunnel/accounts/' + session.token.sub + '/token'
|
||||
+ '/api/tunnel@oauth3.org/accounts/' + session.token.sub + '/token'
|
||||
, session: session
|
||||
, data: {
|
||||
domains: opts.data.domains
|
||||
|
11
package.json
11
package.json
@ -1,15 +1,15 @@
|
||||
{
|
||||
"name": "oauth3.js",
|
||||
"version": "1.0.1",
|
||||
"version": "1.2.2",
|
||||
"description": "The world's smallest, fastest, and most secure OAuth3 (and OAuth2) JavaScript implementation.",
|
||||
"main": "oauth3.node.js",
|
||||
"scripts": {
|
||||
"install": "./node_modules/.bin/gulp",
|
||||
"prepare": "gulp",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@git.daplie.com:OAuth3/oauth3.js.git"
|
||||
"url": "git@git.oauth3.org:OAuth3/oauth3.js.git"
|
||||
},
|
||||
"keywords": [
|
||||
"oauth",
|
||||
@ -32,7 +32,10 @@
|
||||
"sign"
|
||||
],
|
||||
"dependencies": {
|
||||
"elliptic": "^6.4.0"
|
||||
"bluebird": "^3.5.0",
|
||||
"elliptic": "^6.4.0",
|
||||
"request": "^2.81.0",
|
||||
"terminal-forms.js": "git+https://git.oauth3.org/OAuth3/terminal-forms.js.git#v1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"browserify-aes": "^1.0.6",
|
||||
|
@ -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