@ -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 ( clientU ri) {
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 ) ;
// 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 . 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 ) ) ;
}
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 [ ] ;
} ) ;
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 ;
}
} ) ;
} ) . 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 ) ;
}
} ) ;
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 )
} ;
} ) ;
} ;
OAUTH3 . authz . redirectWithToken = function ( providerUri , session , clientParams , scopes ) {
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 ) ;
}
scopes . new = scopes . new || [ ] ;
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 ) ;
}
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 ) {
, scope : scopes . accepted . concat ( scopes . new ) . join ( ',' )
} ) ;
} else {
prom = OAUTH3 . PromiseA . resolve ( ) ;
}
// 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 ;
}
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 ;
} ) ;
}
* /
OAUTH3 . url . redirect ( clientParams , scopes , results ) ;
} ) ;
}
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'
} ;
} ) ;
} ) ;
}
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" ) ;
}
} ) . 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 ( 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 ] ;
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 . _ 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 ( ) ;
}
} ;