renamed
This commit is contained in:
		
							parent
							
								
									db284fbf91
								
							
						
					
					
						commit
						dedd851ff9
					
				
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -1,3 +1,5 @@ | ||||
| prefactor | ||||
| .well-known | ||||
| node_modules/ | ||||
| DS_Store | ||||
| .vscode | ||||
							
								
								
									
										10
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								README.md
									
									
									
									
									
								
							| @ -19,13 +19,13 @@ 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/` | ||||
| 3. Inside of the folder `example.com/assets` a folder called `oauth3.org/` | ||||
| 4. Download [oauth3.js-v1.zip](https://git.daplie.com/OAuth3/oauth3.js/repository/archive.zip?ref=v1) | ||||
| 5. Double-click to unzip the folder. | ||||
| 6. Copy the file `oauth3.core.js` into the folder `example.com/assets/org.oauth3/` | ||||
| 6. Copy the file `oauth3.core.js` into the folder `example.com/assets/oauth3.org/` | ||||
| 7. Copy the folder `well-known` into the folder `example.com/` | ||||
| 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` | ||||
| 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 +44,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> | ||||
| @ -81,7 +81,7 @@ function onClickLogin() { | ||||
|     console.info('Secure PPID (aka subject):', session.token.sub); | ||||
| 
 | ||||
|     return oauth3.request({ | ||||
|       url: 'https://oauth3.org/api/issuer@oauth3.org/inspect_token' | ||||
|       url: 'https://oauth3.org/api/issuer@oauth3.org/inspect' | ||||
|     , session: session | ||||
|     }).then(function (resp) { | ||||
| 
 | ||||
|  | ||||
| @ -131,8 +131,8 @@ parseArgs(process.argv, { | ||||
| 
 | ||||
|     // authn / authz
 | ||||
|   , [ 'devices', 'manages devices for your account(s)' ] | ||||
|   , [ 'devices:new', 'create a new device (default name is hostname, default ip is the result of :provider/api/org.oauth3.tunnel/checkip)'.replace(/\b:provider\b/, defaults.provider) ] | ||||
|   , [ 'devices:set', 'set the ip address of the device (defaults ip is the result of :provider/api/org.oauth3.tunnel/checkip)'.replace(/\b:provider\b/, defaults.provider) ] | ||||
|   , [ 'devices:new', 'create a new device (default name is hostname, default ip is the result of :provider/api/tunnel@oauth3.org/checkip)'.replace(/\b:provider\b/, defaults.provider) ] | ||||
|   , [ 'devices:set', 'set the ip address of the device (defaults ip is the result of :provider/api/tunnel@oauth3.org/checkip)'.replace(/\b:provider\b/, defaults.provider) ] | ||||
|   , [ 'devices:attach', "attach a device to a domain's DNS record" ] | ||||
|   , [ 'devices:detach', "detach an account from a domain's DNS record" ] | ||||
|   , [ 'devices:select', '(re)claim the specified device as this device (i.e. you re-installed your OS or deleted your ~/.oauth3)' ] | ||||
|  | ||||
							
								
								
									
										342
									
								
								oauth3.core.js
									
									
									
									
									
								
							
							
						
						
									
										342
									
								
								oauth3.core.js
									
									
									
									
									
								
							| @ -78,7 +78,7 @@ | ||||
|   , uri: { | ||||
|       normalize: function (uri) { | ||||
|         if ('string' !== typeof uri) { | ||||
|           console.error((new Error('stack')).stack); | ||||
|           throw new Error("attempted to normalize non-string URI: "+JSON.stringify(uri)); | ||||
|         } | ||||
|         // tested with
 | ||||
|         //   example.com
 | ||||
| @ -94,7 +94,7 @@ | ||||
|   , url: { | ||||
|       normalize: function (url) { | ||||
|         if ('string' !== typeof url) { | ||||
|           console.error((new Error('stack')).stack); | ||||
|           throw new Error("attempted to normalize non-string URL: "+JSON.stringify(url)); | ||||
|         } | ||||
|         // tested with
 | ||||
|         //   example.com
 | ||||
| @ -168,9 +168,12 @@ | ||||
|       } | ||||
|     } | ||||
|   , scope: { | ||||
|       stringify: function (scope) { | ||||
|       parse: function (scope) { | ||||
|         return (scope||'').split(/[+, ]+/g); | ||||
|       } | ||||
|     , stringify: function (scope) { | ||||
|         if (Array.isArray(scope)) { | ||||
|           scope = scope.join(' '); | ||||
|           scope = scope.join(','); | ||||
|         } | ||||
|         return scope; | ||||
|       } | ||||
| @ -204,40 +207,90 @@ | ||||
|     } | ||||
|   , jwt: { | ||||
|       // decode only (no verification)
 | ||||
|       decode: function (str) { | ||||
|       decode: function (token, opts) { | ||||
| 
 | ||||
|         // 'abc.qrs.xyz'
 | ||||
|         // [ 'abc', 'qrs', 'xyz' ]
 | ||||
|         // [ {}, {}, 'foo' ]
 | ||||
|         // { header: {}, payload: {}, signature: '' }
 | ||||
|         var parts = str.split(/\./g); | ||||
|         var jsons = parts.slice(0, 2).map(function (urlsafe64) { | ||||
|           return JSON.parse(OAUTH3._base64.decodeUrlSafe(urlsafe64)); | ||||
|         }); | ||||
|         // {}
 | ||||
|         var parts = token.split(/\./g); | ||||
|         var err; | ||||
|         if (parts.length !== 3) { | ||||
|           err = new Error("Invalid JWT: required 3 '.' separated components not "+parts.length); | ||||
|           err.code = 'E_INVALID_JWT'; | ||||
|           throw err; | ||||
|         } | ||||
| 
 | ||||
|         return { header: jsons[0], payload: jsons[1] }; | ||||
|         if (!opts || !opts.complete) { | ||||
|           return JSON.parse(OAUTH3._base64.decodeUrlSafe(parts[1])); | ||||
|         } | ||||
|         return { | ||||
|           header:  JSON.parse(OAUTH3._base64.decodeUrlSafe(parts[0])) | ||||
|         , payload: JSON.parse(OAUTH3._base64.decodeUrlSafe(parts[1])) | ||||
|         }; | ||||
|       } | ||||
|     , verify: function (jwk, token) { | ||||
|     , verify: function (token, jwk) { | ||||
|         if (!OAUTH3.crypto) { | ||||
|           return OAUTH3.PromiseA.reject(new Error("OAuth3 crypto library unavailable")); | ||||
|         } | ||||
|         jwk = jwk.publicKey || jwk; | ||||
| 
 | ||||
|         var parts = token.split(/\./g); | ||||
|         var data = OAUTH3._binStr.binStrToBuffer(parts.slice(0, 2).join('.')); | ||||
|         var signature = OAUTH3._base64.urlSafeToBuffer(parts[2]); | ||||
| 
 | ||||
|         return OAUTH3.crypto.core.verify(jwk, data, signature); | ||||
|         return OAUTH3.crypto.core.verify(jwk, data, signature).then(function () { | ||||
|           return OAUTH3.jwt.decode(token); | ||||
|         }); | ||||
|       } | ||||
|     , freshness: function (tokenMeta, staletime, _now) { | ||||
|         staletime = staletime || (15 * 60); | ||||
|         var now = _now || Date.now(); | ||||
|         var fresh = ((parseInt(tokenMeta.exp, 10) || 0) - Math.round(now / 1000)); | ||||
|     , sign: function (payload, jwk) { | ||||
|         if (!OAUTH3.crypto) { | ||||
|           return OAUTH3.PromiseA.reject(new Error("OAuth3 crypto library unavailable")); | ||||
|         } | ||||
|         jwk = jwk.private_key || jwk.privateKey || jwk; | ||||
| 
 | ||||
|         if (fresh >= staletime) { | ||||
|         var prom; | ||||
|         if (jwk.kid) { | ||||
|           prom = OAUTH3.PromiseA.resolve(jwk.kid); | ||||
|         } else { | ||||
|           prom = OAUTH3.crypto.thumbprintJwk(jwk); | ||||
|         } | ||||
| 
 | ||||
|         return prom.then(function (kid) { | ||||
|           // Currently the crypto part of the OAuth3 library only supports ES256
 | ||||
|           var header = {type: 'JWT', alg: 'ES256', kid: kid}; | ||||
|           var input = [ | ||||
|             OAUTH3._base64.encodeUrlSafe(JSON.stringify(header, null)) | ||||
|           , OAUTH3._base64.encodeUrlSafe(JSON.stringify(payload, null)) | ||||
|           ].join('.'); | ||||
| 
 | ||||
|           return OAUTH3.crypto.core.sign(jwk, OAUTH3._binStr.binStrToBuffer(input)) | ||||
|             .then(OAUTH3._base64.bufferToUrlSafe) | ||||
|             .then(function (signature) { | ||||
|               return input + '.' + signature; | ||||
|             }); | ||||
|         }); | ||||
|       } | ||||
|     , freshness: function (tokenMeta, staletime, now) { | ||||
|         // If the token doesn't expire then it's always fresh.
 | ||||
|         if (!tokenMeta.exp) { | ||||
|           return 'fresh'; | ||||
|         } | ||||
| 
 | ||||
|         if (fresh <= 0) { | ||||
|           return 'expired'; | ||||
|         staletime = staletime || (15 * 60); | ||||
|         now = now || Date.now(); | ||||
|         // This particular number used to check if time is in milliseconds or seconds will work
 | ||||
|         // for any date between the years 1973 and 5138.
 | ||||
|         if (now > 1e11) { | ||||
|           now = Math.round(now / 1000); | ||||
|         } | ||||
|         var exp = parseInt(tokenMeta.exp, 10) || 0; | ||||
|         if (exp < now) { | ||||
|           return 'expired'; | ||||
|         } else if (exp < now + staletime) { | ||||
|           return 'stale'; | ||||
|         } else { | ||||
|           return 'fresh'; | ||||
|         } | ||||
| 
 | ||||
|         return 'stale'; | ||||
|       } | ||||
|     } | ||||
|   , urls: { | ||||
| @ -275,7 +328,7 @@ | ||||
|         // 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
 | ||||
|         // GET https://example.com/api/issuer@oauth3.org/authorization_dialog
 | ||||
|         //  ?response_type=token
 | ||||
|         //  &scope=`encodeURIComponent('profile.login profile.email')`
 | ||||
|         //  &state=`cryptoutil.random().toString('hex')`
 | ||||
| @ -341,29 +394,36 @@ | ||||
|         //    , "username": "<<username>>", "password": "password" }
 | ||||
|         //
 | ||||
|         opts = opts || {}; | ||||
|         var type = 'access_token'; | ||||
|         var grantType = 'refresh_token'; | ||||
|         var refresh_token = opts.refresh_token || (opts.session && opts.session.refresh_token); | ||||
|         var err; | ||||
|         if (!refresh_token) { | ||||
|           err = new Error('refreshing a token requires a refresh token'); | ||||
|           err.code = 'E_NO_TOKEN'; | ||||
|           throw err; | ||||
|         } | ||||
|         if (OAUTH3.jwt.freshness(OAUTH3.jwt.decode(refresh_token)) === 'expired') { | ||||
|           err = new Error('refresh token has also expired, login required again'); | ||||
|           err.code = 'E_EXPIRED_TOKEN'; | ||||
|           throw err; | ||||
|         } | ||||
| 
 | ||||
|         var scope = opts.scope || directive.authn_scope; | ||||
|         var clientSecret = opts.client_secret; | ||||
|         var args = directive[type]; | ||||
|         var args = directive.access_token; | ||||
|         var params = { | ||||
|           "grant_type": grantType | ||||
|         , "refresh_token": opts.refresh_token || (opts.session && opts.session.refresh_token) | ||||
|           "grant_type": 'refresh_token' | ||||
|         , "refresh_token": refresh_token | ||||
|         , "response_type": 'token' | ||||
|         , "client_id": opts.client_id || opts.client_uri | ||||
|         , "client_uri": opts.client_uri | ||||
|         //, "scope": undefined
 | ||||
|         //, "client_secret": undefined
 | ||||
|         , debug: opts.debug || undefined | ||||
|         }; | ||||
|         var uri = args.url; | ||||
|         var body; | ||||
| 
 | ||||
|         if (clientSecret) { | ||||
|         if (opts.client_secret) { | ||||
|           // TODO not allowed in the browser
 | ||||
|           console.warn("if this is a browser, you must not use client_secret"); | ||||
|           params.client_secret = clientSecret; | ||||
|           params.client_secret = opts.client_secret; | ||||
|         } | ||||
| 
 | ||||
|         if (scope) { | ||||
| @ -430,42 +490,44 @@ | ||||
|       } | ||||
|     } | ||||
|   , hooks: { | ||||
|       directives: { | ||||
|         get: function (providerUri) { | ||||
|           providerUri = OAUTH3.uri.normalize(providerUri); | ||||
|           return OAUTH3.PromiseA.resolve(OAUTH3.hooks.directives._getCached(providerUri) | ||||
|             || OAUTH3.hooks.directives._get(providerUri)) | ||||
|             .then(function (directives) { | ||||
|               // or do .then(this._set) to keep DRY?
 | ||||
|             OAUTH3.hooks.directives._cache[providerUri] = directives; | ||||
|             return directives; | ||||
|           }); | ||||
|       _checkStorage: function (grpName, funcName) { | ||||
|         if (!OAUTH3._hooks) { | ||||
|           OAUTH3._hooks = {}; | ||||
|         } | ||||
|       , _getCached: function (providerUri) { | ||||
|           providerUri = OAUTH3.uri.normalize(providerUri); | ||||
|           if (!OAUTH3.hooks.directives._cache) { OAUTH3.hooks.directives._cache = {}; } | ||||
|           return OAUTH3.hooks.directives._cache[providerUri]; | ||||
|         if (!OAUTH3._hooks[grpName]) { | ||||
|           console.log('using default storage for '+grpName+', set OAUTH3._hooks.'+grpName+' for custom storage'); | ||||
|           OAUTH3._hooks[grpName] = OAUTH3._defaultStorage[grpName]; | ||||
|         } | ||||
|         if (!OAUTH3._hooks[grpName][funcName]) { | ||||
|           throw new Error("'"+funcName+"' is not defined for custom "+grpName+" storage"); | ||||
|         } | ||||
|       } | ||||
|     , directives: { | ||||
|         get: function (providerUri) { | ||||
|           OAUTH3.hooks._checkStorage('directives', 'get'); | ||||
| 
 | ||||
|           if (!providerUri) { | ||||
|             throw new Error("providerUri is not set"); | ||||
|           } | ||||
|           return OAUTH3.PromiseA.resolve(OAUTH3._hooks.directives.get(OAUTH3.uri.normalize(providerUri))); | ||||
|         } | ||||
|       , set: function (providerUri, directives) { | ||||
|           providerUri = OAUTH3.uri.normalize(providerUri); | ||||
|           if (!OAUTH3.hooks.directives._cache) { OAUTH3.hooks.directives._cache = {}; } | ||||
|           OAUTH3.hooks.directives._cache[providerUri] = directives; | ||||
|           return OAUTH3.PromiseA.resolve(OAUTH3.hooks.directives._set(providerUri, directives)); | ||||
|         } | ||||
|       , _get: function (providerUri) { | ||||
|           if (!OAUTH3._hooks || !OAUTH3._hooks.directives || !OAUTH3._hooks.directives.get) { | ||||
|             console.warn('[Warn] Please implement OAUTH3._hooks.directives.get = function (providerUri) { return PromiseA<directives>; }'); | ||||
|             return JSON.parse(window.localStorage.getItem('directives-' + providerUri) || '{}'); | ||||
|           OAUTH3.hooks._checkStorage('directives', 'set'); | ||||
| 
 | ||||
|           if (!providerUri) { | ||||
|             throw new Error("providerUri is not set"); | ||||
|           } | ||||
|           return OAUTH3._hooks.directives.get(providerUri); | ||||
|           return OAUTH3.PromiseA.resolve(OAUTH3._hooks.directives.set(OAUTH3.uri.normalize(providerUri), directives)); | ||||
|         } | ||||
|       , _set: function (providerUri, directives) { | ||||
|           if (!OAUTH3._hooks || !OAUTH3._hooks.directives || !OAUTH3._hooks.directives.set) { | ||||
|             console.warn('[Warn] Please implement OAUTH3._hooks.directives.set = function (providerUri, directives) { return PromiseA<directives>; }'); | ||||
|             window.localStorage.setItem('directives-' + providerUri, JSON.stringify(directives)); | ||||
|             return directives; | ||||
|           } | ||||
|           return OAUTH3._hooks.directives.set(providerUri, directives); | ||||
|       , all: function () { | ||||
|           OAUTH3.hooks._checkStorage('directives', 'all'); | ||||
| 
 | ||||
|           return OAUTH3.PromiseA.resolve(OAUTH3._hooks.directives.all()); | ||||
|         } | ||||
|       , clear: function () { | ||||
|           OAUTH3.hooks._checkStorage('directives', 'clear'); | ||||
| 
 | ||||
|           return OAUTH3.PromiseA.resolve(OAUTH3._hooks.directives.clear()); | ||||
|         } | ||||
|       } | ||||
|     , session: { | ||||
| @ -485,7 +547,7 @@ | ||||
|           oldSession.client_uri = clientUri;      // azp
 | ||||
| 
 | ||||
|           // info about the newly-discovered token
 | ||||
|           oldSession.token = OAUTH3.jwt.decode(oldSession.access_token).payload; | ||||
|           oldSession.token = OAUTH3.jwt.decode(oldSession.access_token); | ||||
| 
 | ||||
|           oldSession.token.sub = oldSession.token.sub | ||||
|             || (oldSession.token.acx||{}).id | ||||
| @ -496,7 +558,7 @@ | ||||
|           oldSession.token.provider_uri = providerUri; | ||||
| 
 | ||||
|           if (oldSession.refresh_token) { | ||||
|             oldSession.refresh = OAUTH3.jwt.decode(oldSession.refresh_token).payload; | ||||
|             oldSession.refresh = OAUTH3.jwt.decode(oldSession.refresh_token); | ||||
|             oldSession.refresh.sub = oldSession.refresh.sub | ||||
|               || (oldSession.refresh.acx||{}).id | ||||
|               || ((oldSession.refresh.axs||[])[0]||{}).appScopedId | ||||
| @ -506,7 +568,7 @@ | ||||
|           } | ||||
| 
 | ||||
|           // set for a set of audiences
 | ||||
|           return OAUTH3.PromiseA.resolve(OAUTH3.hooks.session.set(providerUri, oldSession)); | ||||
|           return OAUTH3.hooks.session.set(providerUri, oldSession); | ||||
|         } | ||||
|       , check: function (preq, opts) { | ||||
|           opts = opts || {}; | ||||
| @ -559,62 +621,40 @@ | ||||
|             return newSession; // oauth3.hooks.refreshSession(expiredSession, newSession);
 | ||||
|           }); | ||||
|         } | ||||
|       , _getCached: function (providerUri, id) { | ||||
|           providerUri = OAUTH3.uri.normalize(providerUri); | ||||
|           if (!OAUTH3.hooks.session._cache) { OAUTH3.hooks.session._cache = {}; } | ||||
|           if (id) { | ||||
|             return OAUTH3.hooks.session._cache[providerUri + id]; | ||||
|           } | ||||
|           return OAUTH3.hooks.session._cache[providerUri]; | ||||
|         } | ||||
|       , set: function (providerUri, newSession, id) { | ||||
|           OAUTH3.hooks._checkStorage('sessions', 'set'); | ||||
| 
 | ||||
|           if (!providerUri) { | ||||
|             console.error(new Error('no providerUri').stack); | ||||
|             throw new Error("providerUri is not set"); | ||||
|           } | ||||
|           providerUri = OAUTH3.uri.normalize(providerUri); | ||||
|           if (!OAUTH3.hooks.session._cache) { OAUTH3.hooks.session._cache = {}; } | ||||
|           OAUTH3.hooks.session._cache[providerUri + (id || newSession.id || newSession.token.id || '')] = newSession; | ||||
|           if (!id) { | ||||
|             OAUTH3.hooks.session._cache[providerUri] = newSession; | ||||
|           } | ||||
|           return OAUTH3.PromiseA.resolve(OAUTH3.hooks.session._set(providerUri, newSession)); | ||||
|           return OAUTH3.PromiseA.resolve(OAUTH3._hooks.sessions.set(providerUri, newSession, id)); | ||||
|         } | ||||
|       , get: function (providerUri, id) { | ||||
|           providerUri = OAUTH3.uri.normalize(providerUri); | ||||
|           OAUTH3.hooks._checkStorage('sessions', 'get'); | ||||
| 
 | ||||
|           if (!providerUri) { | ||||
|             throw new Error("providerUri is not set"); | ||||
|           } | ||||
|           providerUri = OAUTH3.uri.normalize(providerUri); | ||||
|           return OAUTH3.PromiseA.resolve(OAUTH3._hooks.sessions.get(providerUri, id)); | ||||
|         } | ||||
|       , all: function (providerUri) { | ||||
|           OAUTH3.hooks._checkStorage('sessions', 'all'); | ||||
| 
 | ||||
|           return OAUTH3.PromiseA.resolve( | ||||
|             OAUTH3.hooks.session._getCached(providerUri, id) || OAUTH3.hooks.session._get(providerUri, id) | ||||
|           ).then(function (session) { | ||||
|             var s = session || { token: {} }; | ||||
|             OAUTH3.hooks.session._cache[providerUri + (id || s.id || s.token.id || '')] = session; | ||||
|             if (!id) { | ||||
|               OAUTH3.hooks.session._cache[providerUri] = session; | ||||
|             } | ||||
|             return session; | ||||
|           }); | ||||
|           if (providerUri) { | ||||
|             providerUri = OAUTH3.uri.normalize(providerUri); | ||||
|           } | ||||
|           return OAUTH3.PromiseA.resolve(OAUTH3._hooks.sessions.all(providerUri)); | ||||
|         } | ||||
|       , _get: function (providerUri, id) { | ||||
|           if (!OAUTH3._hooks || !OAUTH3._hooks.sessions || !OAUTH3._hooks.sessions.all) { | ||||
|             console.warn('[Warn] Please implement OAUTH3._hooks.sessions.all = function ([providerUri]) { return PromiseA<sessions>; }'); | ||||
|       , clear: function (providerUri) { | ||||
|           OAUTH3.hooks._checkStorage('sessions', 'clear'); | ||||
| 
 | ||||
|           if (providerUri) { | ||||
|             providerUri = OAUTH3.uri.normalize(providerUri); | ||||
|           } | ||||
|           if (!OAUTH3._hooks || !OAUTH3._hooks.sessions || !OAUTH3._hooks.sessions.get) { | ||||
|             console.warn('[Warn] Please implement OAUTH3._hooks.sessions.get = function (providerUri[, id]) { return PromiseA<session>; }'); | ||||
|             return JSON.parse(window.sessionStorage.getItem('session-' + providerUri + (id || '')) || 'null'); | ||||
|           } | ||||
|           return OAUTH3._hooks.sessions.get(providerUri, id); | ||||
|         } | ||||
|       , _set: function (providerUri, newSession, id) { | ||||
|           if (!OAUTH3._hooks || !OAUTH3._hooks.sessions || !OAUTH3._hooks.sessions.set) { | ||||
|             console.warn('[Warn] Please implement OAUTH3._hooks.sessions.set = function (providerUri, newSession[, id]) { return PromiseA<newSession>; }'); | ||||
|             window.sessionStorage.setItem('session-' + providerUri, JSON.stringify(newSession)); | ||||
|             window.sessionStorage.setItem('session-' + providerUri + (id || newSession.id || newSession.token.id || ''), JSON.stringify(newSession)); | ||||
|             return newSession; | ||||
|           } | ||||
|           return OAUTH3._hooks.sessions.set(providerUri, newSession, id); | ||||
|           return OAUTH3.PromiseA.resolve(OAUTH3._hooks.sessions.clear(providerUri)); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
| @ -803,7 +843,7 @@ | ||||
|           return OAUTH3.PromiseA.reject(OAUTH3.error.parse(directives.issuer /*providerUri*/, params)); | ||||
|         } | ||||
| 
 | ||||
|         OAUTH3.hooks.session._cache = {}; | ||||
|         OAUTH3.hooks.session.clear(); | ||||
|         return params; | ||||
|       }); | ||||
|     } | ||||
| @ -1123,6 +1163,82 @@ | ||||
|   }; | ||||
|   OAUTH3.login = OAUTH3.implicitGrant; | ||||
| 
 | ||||
|   OAUTH3._defaultStorage = { | ||||
|     _getStorageKeys: function (prefix, storage) { | ||||
|       storage = storage || window.localStorage; | ||||
|       var matching = []; | ||||
|       var ind, key; | ||||
|       for (ind = 0; ind < storage.length; ind++) { | ||||
|         key = storage.key(ind); | ||||
|         if (key.indexOf(prefix || '') === 0) { | ||||
|           matching.push(key); | ||||
|         } | ||||
|       } | ||||
|       return matching; | ||||
|     } | ||||
|   , directives: { | ||||
|       prefix: 'directives-' | ||||
|     , get: function (providerUri) { | ||||
|         var result = JSON.parse(window.localStorage.getItem(this.prefix + providerUri) || '{}'); | ||||
|         return OAUTH3.PromiseA.resolve(result); | ||||
|       } | ||||
|     , set: function (providerUri, directives) { | ||||
|         window.localStorage.setItem(this.prefix + providerUri, JSON.stringify(directives)); | ||||
|         return this.get(providerUri); | ||||
|       } | ||||
|     , all: function () { | ||||
|         var prefix = this.prefix; | ||||
|         var result = {}; | ||||
|         OAUTH3._defaultStorage._getStorageKeys(prefix).forEach(function (key) { | ||||
|           result[key.replace(prefix, '')] = JSON.parse(window.localStorage.getItem(key) || '{}'); | ||||
|         }); | ||||
|         return OAUTH3.PromiseA.resolve(result); | ||||
|       } | ||||
|     , clear: function () { | ||||
|         OAUTH3._defaultStorage._getStorageKeys(this.prefix).forEach(function (key) { | ||||
|           window.localStorage.removeItem(key); | ||||
|         }); | ||||
|         return OAUTH3.PromiseA.resolve(); | ||||
|       } | ||||
|     } | ||||
|   , sessions: { | ||||
|       prefix: 'session-' | ||||
|     , get: function (providerUri, id) { | ||||
|         var result; | ||||
|         if (id) { | ||||
|           result = JSON.parse(window.sessionStorage.getItem(this.prefix + providerUri+id) || 'null'); | ||||
|         } else { | ||||
|           result = JSON.parse(window.sessionStorage.getItem(this.prefix + providerUri) || 'null'); | ||||
|         } | ||||
|         return OAUTH3.PromiseA.resolve(result); | ||||
|       } | ||||
|     , set: function (providerUri, newSession, id) { | ||||
|         var str = JSON.stringify(newSession); | ||||
|         window.sessionStorage.setItem(this.prefix + providerUri, str); | ||||
|         id = id || newSession.id || newSession.token.sub || newSession.token.id; | ||||
|         if (id) { | ||||
|           window.sessionStorage.setItem(this.prefix + providerUri + id, str); | ||||
|         } | ||||
|         return this.get(providerUri, id); | ||||
|       } | ||||
|     , all: function (providerUri) { | ||||
|         var prefix = this.prefix + (providerUri || ''); | ||||
|         var result = {}; | ||||
|         OAUTH3._defaultStorage._getStorageKeys(prefix, window.sessionStorage).forEach(function (key) { | ||||
|           result[key.replace(prefix, '')] = JSON.parse(window.sessionStorage.getItem(key) || 'null'); | ||||
|         }); | ||||
|         return OAUTH3.PromiseA.resolve(result); | ||||
|       } | ||||
|     , clear: function (providerUri) { | ||||
|         var prefix = this.prefix + (providerUri || ''); | ||||
|         OAUTH3._defaultStorage._getStorageKeys(prefix, window.sessionStorage).forEach(function (key) { | ||||
|           window.sessionStorage.removeItem(key); | ||||
|         }); | ||||
|         return OAUTH3.PromiseA.resolve(); | ||||
|       } | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   // TODO get rid of these
 | ||||
|   OAUTH3.utils = { | ||||
|     clientUri: OAUTH3.clientUri | ||||
| @ -1154,7 +1270,7 @@ | ||||
|     , _resourceProviderUri: null | ||||
|     , _identityProviderDirectives: null | ||||
|     , _resourceProviderDirectives: null | ||||
|     //, _resourceProviderMap: null // map between xyz.com and org.oauth3.domains
 | ||||
|     //, _resourceProviderMap: null // map between xyz.com and domains@oauth3.org
 | ||||
|     , _init: function (location, opts) { | ||||
|         var me = this; | ||||
|         if (!opts) { | ||||
| @ -1223,7 +1339,7 @@ | ||||
|         var me = this; | ||||
|         return me._initClient().then(function () { | ||||
|           return me.setIdentityProvider(providerUri).then(function () { | ||||
|             // TODO how to say "Use xyz.com for org.oauth3.domains, but abc.com for org.oauth3.dns"?
 | ||||
|             // TODO how to say "Use xyz.com for domains@oauth3.org, but abc.com for dns@oauth3.org"?
 | ||||
|             return me.setResourceProvider(providerUri); | ||||
|           }); | ||||
|         }); | ||||
|  | ||||
							
								
								
									
										100
									
								
								oauth3.crypto.js
									
									
									
									
									
								
							
							
						
						
									
										100
									
								
								oauth3.crypto.js
									
									
									
									
									
								
							| @ -1,5 +1,5 @@ | ||||
| ;(function (exports) { | ||||
| 'use strict'; | ||||
|   'use strict'; | ||||
| 
 | ||||
|   var OAUTH3 = exports.OAUTH3 = exports.OAUTH3 || require('./oauth3.core.js').OAUTH3; | ||||
| 
 | ||||
| @ -82,6 +82,7 @@ | ||||
|     }; | ||||
| 
 | ||||
|     function checkWebCrypto() { | ||||
|       /* global OAUTH3_crypto_fallback */ | ||||
|       var loadFallback = function() { | ||||
|         var prom; | ||||
|         loadFallback = function () { return prom; }; | ||||
| @ -96,17 +97,16 @@ | ||||
|               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()); }) | ||||
|         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 () { | ||||
|               OAUTH3.crypto.core[name] = OAUTH3_crypto_fallback[name]; | ||||
| @ -195,101 +195,61 @@ | ||||
|       .then(OAUTH3._base64.bufferToUrlSafe); | ||||
|   }; | ||||
| 
 | ||||
|   OAUTH3.crypto._createKey = function (ppid) { | ||||
|     var saltProm = OAUTH3.crypto.core.randomBytes(16); | ||||
|     var kekProm = saltProm.then(function (salt) { | ||||
|       return OAUTH3.crypto.core.pbkdf2(ppid, salt); | ||||
|     }); | ||||
| 
 | ||||
|     var ecdsaProm = OAUTH3.crypto.core.genEcdsaKeyPair() | ||||
|     .then(function (keyPair) { | ||||
|   OAUTH3.crypto.createKeyPair = function () { | ||||
|     // TODO: maybe support other types of key pairs, not just ECDSA P-256
 | ||||
|     return OAUTH3.crypto.core.genEcdsaKeyPair().then(function (keyPair) { | ||||
|       return OAUTH3.crypto.thumbprintJwk(keyPair.publicKey).then(function (kid) { | ||||
|         keyPair.privateKey.alg = keyPair.publicKey.alg = 'ES256'; | ||||
|         keyPair.privateKey.kid = keyPair.publicKey.kid = kid; | ||||
|         return keyPair; | ||||
|       }); | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|   OAUTH3.crypto.encryptKeyPair = function (keyPair, password) { | ||||
|     var saltProm = OAUTH3.crypto.core.randomBytes(16); | ||||
|     var kekProm = saltProm.then(function (salt) { | ||||
|       return OAUTH3.crypto.core.pbkdf2(password, salt); | ||||
|     }); | ||||
| 
 | ||||
|     return OAUTH3.PromiseA.all([ | ||||
|       kekProm | ||||
|     , ecdsaProm | ||||
|     , saltProm | ||||
|     , OAUTH3.crypto.core.randomBytes(16) | ||||
|     , OAUTH3.crypto.core.randomBytes(12) | ||||
|     , OAUTH3.crypto.core.randomBytes(12) | ||||
|     ]).then(function (results) { | ||||
|   , ]).then(function (results) { | ||||
|       var kek        = results[0]; | ||||
|       var keyPair    = results[1]; | ||||
|       var salt       = results[2]; | ||||
|       var userSecret = results[3]; | ||||
|       var ecdsaIv    = results[4]; | ||||
|       var secretIv   = results[5]; | ||||
|       var salt       = results[1]; | ||||
|       var ecdsaIv    = results[2]; | ||||
| 
 | ||||
|       return OAUTH3.PromiseA.all([ | ||||
|         OAUTH3.crypto.core.encrypt(kek, ecdsaIv, OAUTH3._binStr.binStrToBuffer(JSON.stringify(keyPair.privateKey))) | ||||
|       , OAUTH3.crypto.core.encrypt(kek, secretIv, userSecret) | ||||
|       ]) | ||||
|       .then(function (encrypted) { | ||||
|       var privKeyBuf = OAUTH3._binStr.binStrToBuffer(JSON.stringify(keyPair.privateKey)); | ||||
|       return OAUTH3.crypto.core.encrypt(kek, ecdsaIv, privKeyBuf).then(function (encrypted) { | ||||
|         return { | ||||
|           publicKey:  keyPair.publicKey | ||||
|         , privateKey: OAUTH3._base64.bufferToUrlSafe(encrypted[0]) | ||||
|         , userSecret: OAUTH3._base64.bufferToUrlSafe(encrypted[1]) | ||||
|         , privateKey: OAUTH3._base64.bufferToUrlSafe(encrypted) | ||||
|         , salt:       OAUTH3._base64.bufferToUrlSafe(salt) | ||||
|         , ecdsaIv:    OAUTH3._base64.bufferToUrlSafe(ecdsaIv) | ||||
|         , secretIv:   OAUTH3._base64.bufferToUrlSafe(secretIv) | ||||
|         }; | ||||
|       , }; | ||||
|       }); | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|   OAUTH3.crypto._decryptKey = function (ppid, storedObj) { | ||||
|   OAUTH3.crypto.decryptKeyPair = function (storedObj, password) { | ||||
|     var salt   = OAUTH3._base64.urlSafeToBuffer(storedObj.salt); | ||||
|     var encJwk = OAUTH3._base64.urlSafeToBuffer(storedObj.privateKey); | ||||
|     var iv     = OAUTH3._base64.urlSafeToBuffer(storedObj.ecdsaIv); | ||||
| 
 | ||||
|     return OAUTH3.crypto.core.pbkdf2(ppid, salt) | ||||
|     return OAUTH3.crypto.core.pbkdf2(password, salt) | ||||
|       .then(function (key) { | ||||
|         return OAUTH3.crypto.core.decrypt(key, iv, encJwk); | ||||
|       }) | ||||
|       .then(OAUTH3._binStr.bufferToBinStr) | ||||
|       .then(JSON.parse); | ||||
|   }; | ||||
| 
 | ||||
|   OAUTH3.crypto._getKey = function (ppid) { | ||||
|     return OAUTH3.crypto.core.sha256(OAUTH3._binStr.binStrToBuffer(ppid)) | ||||
|     .then(function (hash) { | ||||
|       var name = 'kek-' + OAUTH3._base64.bufferToUrlSafe(hash); | ||||
|       var promise; | ||||
| 
 | ||||
|       if (window.localStorage.getItem(name) === null) { | ||||
|         promise = OAUTH3.crypto._createKey(ppid).then(function (key) { | ||||
|           window.localStorage.setItem(name, JSON.stringify(key)); | ||||
|           return key; | ||||
|         }); | ||||
|       } else { | ||||
|         promise = OAUTH3.PromiseA.resolve(JSON.parse(window.localStorage.getItem(name))); | ||||
|       } | ||||
| 
 | ||||
|       return promise.then(function (storedObj) { | ||||
|         return OAUTH3.crypto._decryptKey(ppid, storedObj); | ||||
|       .then(JSON.parse) | ||||
|       .then(function (privateKey) { | ||||
|         return { | ||||
|           privateKey: privateKey | ||||
|         , publicKey:  storedObj.publicKey | ||||
|       , }; | ||||
|       }); | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|   OAUTH3.crypto._signPayload = function (payload) { | ||||
|     return OAUTH3.crypto._getKey('some PPID').then(function (key) { | ||||
|       var header = {type: 'JWT', alg: key.alg, kid: key.kid}; | ||||
|       var input = [ | ||||
|         OAUTH3._base64.encodeUrlSafe(JSON.stringify(header, null)) | ||||
|       , OAUTH3._base64.encodeUrlSafe(JSON.stringify(payload, null)) | ||||
|       ].join('.'); | ||||
| 
 | ||||
|       return OAUTH3.crypto.core.sign(key, OAUTH3._binStr.binStrToBuffer(input)) | ||||
|         .then(OAUTH3._base64.bufferToUrlSafe) | ||||
|         .then(function (signature) { | ||||
|           return input + '.' + signature; | ||||
|         }); | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
| }('undefined' !== typeof exports ? exports : window)); | ||||
|  | ||||
							
								
								
									
										711
									
								
								oauth3.issuer.js
									
									
									
									
									
								
							
							
						
						
									
										711
									
								
								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 { | ||||
| @ -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,18 +179,19 @@ 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.api, directive.grants.url) | ||||
|   var url = OAUTH3.url.resolve(directive.api, grantsDir.url) | ||||
|     .replace(/(:azp|:client_id)/g, OAUTH3.uri.normalize(opts.client_id || opts.client_uri)) | ||||
|     .replace(/(:sub|:account_id)/g, opts.session.token.sub || 'ISSUER:GRANT:TOKEN_SUB:UNDEFINED') | ||||
|     ; | ||||
| @ -214,16 +199,14 @@ OAUTH3.urls.grants = function (directive, opts) { | ||||
|     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,6 +217,76 @@ OAUTH3.urls.grants = function (directive, opts) { | ||||
|   , session: opts.session | ||||
|   }; | ||||
| }; | ||||
| OAUTH3.urls.clientToken = function (directive, opts) { | ||||
|   var tokenDir = directive.access_token; | ||||
|   if (!tokenDir) { | ||||
|     throw new Error("provider doesn't support getting access tokens"); | ||||
|   } | ||||
| 
 | ||||
|   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.authn = {}; | ||||
| OAUTH3.authn.loginMeta = function (directive, opts) { | ||||
| @ -267,112 +320,95 @@ OAUTH3.authn.otp = function (directive, opts) { | ||||
| 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); | ||||
|   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)); | ||||
|     } | ||||
| 
 | ||||
|     // TODO return not the raw request?
 | ||||
|     return OAUTH3.request(prequest).then(function (req) { | ||||
|       var data = req.data; | ||||
|       data.provider_uri = providerUri; | ||||
|       if (data.error) { | ||||
|         return OAUTH3.PromiseA.reject(OAUTH3.error.parse(providerUri, data)); | ||||
|     return OAUTH3.hooks.session.refresh( | ||||
|       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.hooks.session.refresh( | ||||
|         opts.session || { provider_uri: providerUri, client_uri: opts.client_uri || opts.clientUri } | ||||
|       , data | ||||
|       ); | ||||
|       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; | ||||
| 
 | ||||
|   if (!scope) { | ||||
|     scope = 'oauth3_authn'; | ||||
|   var scope = clientParams.scope || 'oauth3_authn'; | ||||
|   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);
 | ||||
|   } | ||||
| 
 | ||||
|   return OAUTH3.authz.grants(providerUri, { | ||||
|     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; | ||||
| 
 | ||||
|     // 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."); | ||||
|   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; | ||||
|       } | ||||
|       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 OAUTH3.authz.grants(providerUri, { | ||||
|       method: 'GET' | ||||
|     , client_id: clientUri | ||||
|     , client_uri: clientUri | ||||
|     , session: session | ||||
|     }).then(function (results) { | ||||
|       return results.grants; | ||||
|     }, function (err) { | ||||
|       if (!/no .*grants .*found/i.test(err.message)) { | ||||
|         throw err; | ||||
|       } | ||||
|       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,73 +417,153 @@ 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) { | ||||
|     var grants = grantsResult.originalData || grantsResult.data; | ||||
|     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 ('GET' !== opts.method && 'POST' !== opts.method) { | ||||
|       return grants; | ||||
|     } | ||||
| 
 | ||||
|     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; | ||||
|       } | ||||
| 
 | ||||
|       var grants = grantsResult.originalData || grantsResult.data; | ||||
|       // TODO
 | ||||
|       if (grants.error) { | ||||
|         return OAUTH3.PromiseA.reject(OAUTH3.error.parse(providerUri, 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 ]); | ||||
|       }); | ||||
| 
 | ||||
|       return { | ||||
|         client: OAUTH3.hooks.grants.get(opts.client_id + '-client') | ||||
|       , grants: OAUTH3.hooks.grants.get(opts.client_id) || [] | ||||
|       }; | ||||
|     }); | ||||
|     OAUTH3.hooks.grants.set(grants.sub, grants.azp, grants); | ||||
|     return { | ||||
|       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); | ||||
|   } | ||||
| 
 | ||||
|   scopes.new = scopes.new || []; | ||||
| 
 | ||||
|   if ('token' === clientParams.response_type) { | ||||
|     // get token and redirect client-side
 | ||||
|     return OAUTH3.authz.grants(providerUri, { | ||||
|       method: 'POST' | ||||
|   var prom; | ||||
|   if (scopes.new) { | ||||
|     prom = OAUTH3.authz.grants(providerUri, { | ||||
|       session: session | ||||
|     , 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 | ||||
|     , subject: clientParams.subject | ||||
|     , debug: clientParams.debug | ||||
|     }).then(function (results) { | ||||
| 
 | ||||
|       // TODO limit refresh token to an expirable token
 | ||||
|       // TODO inform client not to persist token
 | ||||
|       /* | ||||
|       if (clientParams.dnsTxt) { | ||||
|         Object.keys(results).forEach(function (key) { | ||||
|           if (/refresh/.test(key)) { | ||||
|             results[key] = undefined; | ||||
|           } | ||||
|         }); | ||||
|       } | ||||
|       */ | ||||
|       OAUTH3.url.redirect(clientParams, scopes, results); | ||||
|     , scope: scopes.accepted.concat(scopes.new).join(',') | ||||
|     }); | ||||
|   } else { | ||||
|     prom = OAUTH3.PromiseA.resolve(); | ||||
|   } | ||||
|   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"); | ||||
|   } | ||||
| 
 | ||||
|   return prom.then(function () { | ||||
|     return OAUTH3.hooks.keyPairs.get(session.token.sub); | ||||
|   }).then(function (keyPair) { | ||||
|     if (!keyPair) { | ||||
|       return OAUTH3.discover(providerUri, { | ||||
|         client_id: providerUri | ||||
|       , debug: clientParams.debug | ||||
|       }).then(function (directive) { | ||||
|         return OAUTH3.request(OAUTH3.urls.clientToken(directive, { | ||||
|           method: 'POST' | ||||
|         , session: session | ||||
|         , referrer: clientParams.referrer | ||||
|         , response_type: clientParams.response_type | ||||
|         , client_id:  clientParams.client_uri | ||||
|         , azp: clientParams.client_uri | ||||
|         , aud: clientParams.aud | ||||
|         , exp: clientParams.exp | ||||
|         , refresh_token: clientParams.refresh_token | ||||
|         , refresh_exp: clientParams.refresh_exp | ||||
|         , debug: clientParams.debug | ||||
|         })).then(function (result) { | ||||
|           return result.originalData || result.data; | ||||
|         }); | ||||
|       }); | ||||
|     } | ||||
| 
 | ||||
|     return OAUTH3.hooks.grants.get(keyPair.sub, clientParams.client_uri).then(function (grant) { | ||||
|       var now = Math.floor(Date.now()/1000); | ||||
|       var payload = { | ||||
|         iat: now | ||||
|       , iss: providerUri | ||||
|       , aud: clientParams.aud || providerUri | ||||
|       , azp: clientParams.client_uri | ||||
|       , sub: grant.azpSub | ||||
|       , scope: OAUTH3.scope.stringify(grant.scope) | ||||
|     , }; | ||||
| 
 | ||||
|       var signProms = []; | ||||
|       signProms.push(OAUTH3.jwt.sign(Object.assign({ | ||||
|         exp: calcExpiration(clientParams.exp || '1h', now) | ||||
|       }, payload), keyPair)); | ||||
|       // if (clientParams.refresh_token) {
 | ||||
|         signProms.push(OAUTH3.jwt.sign(Object.assign({ | ||||
|           exp: calcExpiration(clientParams.refresh_exp, now) | ||||
|         }, payload), keyPair)); | ||||
|       // }
 | ||||
|       return OAUTH3.PromiseA.all(signProms).then(function (tokens) { | ||||
|         console.log('created new tokens for client'); | ||||
|         return { | ||||
|           access_token: tokens[0] | ||||
|         , refresh_token: tokens[1] | ||||
|         , scope: OAUTH3.scope.stringify(grant.scope) | ||||
|         , token_type: 'bearer' | ||||
|         }; | ||||
|       }); | ||||
|     }); | ||||
|   }).then(function (session) { | ||||
|     // TODO limit refresh token to an expirable token
 | ||||
|     // TODO inform client not to persist token
 | ||||
|     OAUTH3.url.redirect(clientParams, scopes, session); | ||||
|   }, function (err) { | ||||
|     console.error('unexpected error creating client tokens', err); | ||||
|     OAUTH3.url.redirect(clientParams, scopes, {error: err}); | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| OAUTH3.requests = {}; | ||||
| OAUTH3.requests.accounts = {}; | ||||
| OAUTH3.requests.accounts.update = function (directive, session, opts) { | ||||
| @ -512,25 +628,178 @@ OAUTH3.requests.accounts.create = function (directive, session, account) { | ||||
|   , data: data | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| 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; | ||||
|   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))); | ||||
|   } | ||||
| , 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) { | ||||
|     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._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); | ||||
|   } | ||||
| , 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(); | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
|  | ||||
| @ -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*/) { | ||||
|  | ||||
| @ -67,10 +67,9 @@ module.exports = { | ||||
| 
 | ||||
| , sessions: { | ||||
|     all: function (providerUri) { | ||||
|       var dirname = path.join(oauth3dir, 'sessions'); | ||||
|       return fs.readdirAsync(dirname).then(function (nodes) { | ||||
|       return fs.readdirAsync(sessionsdir).then(function (nodes) { | ||||
|         return nodes.map(function (node) { | ||||
|           var result = require(path.join(dirname, node)); | ||||
|           var result = require(path.join(sessionsdir, node)); | ||||
|           if (result.link) { | ||||
|             return null; | ||||
|           } | ||||
| @ -91,7 +90,7 @@ module.exports = { | ||||
|           result = require(path.join(sessionsdir, providerUri + '.json')); | ||||
|           // TODO make safer
 | ||||
|           if (result.link && '/' !== result.link[0] && !/\.\./.test(result.link)) { | ||||
|             result = require(path.join(oauth3dir, 'sessions', result.link)); | ||||
|             result = require(path.join(sessionsdir, result.link)); | ||||
|           } | ||||
|         } | ||||
|       } catch(e) { | ||||
| @ -113,10 +112,9 @@ module.exports = { | ||||
|       }); | ||||
|     } | ||||
|   , clear: function () { | ||||
|       var dirname = path.join(oauth3dir, 'sessions'); | ||||
|       return fs.readdirAsync(dirname).then(function (nodes) { | ||||
|       return fs.readdirAsync(sessionsdir).then(function (nodes) { | ||||
|         return PromiseA.all(nodes.map(function (node) { | ||||
|           return fs.unlinkAsync(path.join(dirname, node)); | ||||
|           return fs.unlinkAsync(path.join(sessionsdir, node)); | ||||
|         })); | ||||
|       }); | ||||
|     } | ||||
|  | ||||
| @ -9,7 +9,7 @@ OAUTH3.api['tunnel.token'] = function (providerUri, opts) { | ||||
|   return OAUTH3.request({ | ||||
|     method: 'POST' | ||||
|   , url: OAUTH3.url.normalize(providerUri) | ||||
|       + '/api/org.oauth3.tunnel/accounts/' + session.token.sub + '/token' | ||||
|       + '/api/tunnel@oauth3.org/accounts/' + session.token.sub + '/token' | ||||
|   , session: session | ||||
|   , data: { | ||||
|       domains: opts.data.domains | ||||
|  | ||||
| @ -1,13 +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/otp" } | ||||
| , "credential_otp": { "method": "POST", "url": "api/issuer@oauth3.org/otp" } | ||||
| , "credential_meta": { "url": "api/issuer@oauth3.org/logins/meta/:type/:id" } | ||||
| , "credential_create": { "method": "POST", "url": "api/issuer@oauth3.org/logins" } | ||||
| , "grants": { "method": "GET", "url": "api/issuer@oauth3.org/grants/:azp/:sub" } | ||||
| , "authorization_decision": { "method": "POST", "url": "api/issuer@oauth3.org/authorization_decision" } | ||||
| , "callback": { "method": "GET", "url": ".well-known/oauth3/callback.html#/" } | ||||
| , "logout": { "method": "GET", "url": "#/logout/" } | ||||
| , "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/" } | ||||
| } | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user