2024-07-30 21:24:20 +00:00
# include loading . jsx
# include app / info . jsx
# include app / scope . jsx
# include header / header . js
# include header / user . jsx
const HanyuuOAuth2AuthoriseErrors = Object . freeze ( {
'invalid_request' : {
description : 'The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed.' ,
} ,
'unauthorized_client' : {
description : 'The client is not authorised to request an authorisation code using this method.' ,
} ,
'access_denied' : {
description : 'The resource owner or authorization server denied the request.' ,
} ,
'unsupported_response_type' : {
description : 'The authorisation server does not support obtaining an authorisation code using this method.' ,
} ,
'invalid_scope' : {
description : 'The requested scope is invalid, unknown, or malformed.' ,
} ,
'server_error' : {
description : 'The authorisation server encountered an unexpected condition that prevented it from fulfilling the request.' ,
} ,
'temporarily_unavailable' : {
description : 'The authorization server is currently unable to handle the request due to a temporary overloading or maintenance of the server.' ,
} ,
} ) ;
const HanyuuOAuth2Authorise = async ( ) => {
const queryParams = new URLSearchParams ( window . location . search ) ;
const loading = new HanyuuOAuth2Loading ( '.js-loading' ) ;
const header = new HanyuuOAuth2Header ;
const fAuths = document . querySelector ( '.js-authorise-form' ) ;
const eAuthsInfo = document . querySelector ( '.js-authorise-form-info' ) ;
const eAuthsScope = document . querySelector ( '.js-authorise-form-scope' ) ;
const dError = document . querySelector ( '.js-authorise-error' ) ;
const dErrorText = dError ? . querySelector ( '.js-authorise-error-text' ) ;
let scope ;
let state ;
let redirectUri ;
let redirectUriRaw ;
const displayError = ( error , description , documentation ) => {
if ( redirectUri === undefined ) {
if ( error in HanyuuOAuth2AuthoriseErrors ) {
const errInfo = HanyuuOAuth2AuthoriseErrors [ error ] ;
description ? ? = errInfo . description ;
} else
description = ` An unknown error occurred: ${ error } ` ;
dErrorText . textContent = description ;
header . setSimpleData ( 'error' , 'An error occurred!' ) ;
header . removeElement ( ) ;
loading . visible = false ;
fAuths . classList . add ( 'hidden' ) ;
dError . classList . remove ( 'hidden' ) ;
return ;
}
const errorUri = new URL ( redirectUri ) ;
errorUri . searchParams . set ( 'error' , error ? . toString ( ) ? ? 'invalid_request' ) ;
if ( description )
errorUri . searchParams . set ( 'error_description' , description . toString ( ) ) ;
if ( documentation )
errorUri . searchParams . set ( 'error_uri' , documentation . toString ( ) ) ;
if ( state !== undefined )
errorUri . searchParams . set ( 'state' , state . toString ( ) ) ;
window . location . assign ( errorUri ) ;
} ;
2024-09-03 19:59:31 +00:00
const translateError = ( serverError , detail ) => {
2024-07-30 21:24:20 +00:00
if ( serverError === 'auth' )
return displayError ( 'access_denied' ) ;
if ( serverError === 'csrf' )
return displayError ( 'invalid_request' , 'Request verification failed.' ) ;
if ( serverError === 'client' )
return displayError ( 'invalid_request' , 'There is no application associated with the specified Client ID.' ) ;
if ( serverError === 'format' )
return displayError ( 'invalid_request' , 'Redirect URI specified is not registered with this application.' ) ;
if ( serverError === 'method' )
return displayError ( 'invalid_request' , 'Requested code challenge method is not supported.' ) ;
if ( serverError === 'length' )
return displayError ( 'invalid_request' , 'Code challenge length is not acceptable.' ) ;
if ( serverError === 'required' )
return displayError ( 'invalid_request' , 'A registered redirect URI must be specified.' ) ;
if ( serverError === 'scope' )
2024-09-03 19:59:31 +00:00
return displayError ( 'invalid_scope' , detail === undefined ? undefined : ` Requested scope " ${ detail . scope } " is ${ detail . reason } . ` ) ;
2024-07-30 21:24:20 +00:00
if ( serverError === 'authorise' )
return displayError ( 'server_error' , 'Server was unable to complete authorisation.' ) ;
return displayError ( 'invalid_request' , ` An unknown error occurred: ${ serverError } . ` ) ;
} ;
if ( queryParams . has ( 'redirect_uri' ) )
try {
const qRedirectUriRaw = queryParams . get ( 'redirect_uri' ) ;
const qRedirectUri = new URL ( qRedirectUriRaw ) ;
if ( qRedirectUri . protocol !== 'https:' )
throw 'protocol must be https' ;
redirectUri = qRedirectUri ;
redirectUriRaw = qRedirectUriRaw ;
} catch ( ex ) {
return displayError ( 'invalid_request' , 'Invalid redirect URI specified.' ) ;
}
if ( queryParams . has ( 'state' ) ) {
const qState = queryParams . get ( 'state' ) ;
if ( qState . length > 1000 )
return displayError ( 'invalid_request' , 'State parameter may not be longer than 255 characters.' ) ;
state = qState ;
}
if ( queryParams . get ( 'response_type' ) !== 'code' )
return displayError ( 'unsupported_response_type' ) ;
let codeChallengeMethod = 'plain' ;
if ( queryParams . has ( 'code_challenge_method' ) ) {
codeChallengeMethod = queryParams . get ( 'code_challenge_method' ) ;
if ( ! [ 'plain' , 'S256' ] . includes ( codeChallengeMethod ) )
return translateError ( 'method' ) ;
}
if ( ! queryParams . has ( 'code_challenge' ) )
return displayError ( 'invalid_request' , 'code_challenge must be specified.' ) ;
const codeChallenge = queryParams . get ( 'code_challenge' ) ;
if ( codeChallengeMethod === 'S256' ) {
if ( codeChallenge . length !== 43 )
return displayError ( 'invalid_request' , 'Specified code challenge is not a valid SHA-256 hash.' ) ;
} else {
if ( codeChallenge . length < 43 )
return displayError ( 'invalid_request' , 'Code challenge must be at least 43 characters long.' ) ;
if ( codeChallenge . length > 128 )
return displayError ( 'invalid_request' , 'Code challenge may not be longer than 128 characters.' ) ;
}
if ( ! queryParams . has ( 'client_id' ) )
return displayError ( 'invalid_request' , 'client_id must be specified.' ) ;
const resolveParams = new URLSearchParams ;
resolveParams . set ( 'csrfp' , HanyuuCSRFP . getToken ( ) ) ;
resolveParams . set ( 'client' , queryParams . get ( 'client_id' ) ) ;
if ( redirectUriRaw !== undefined )
resolveParams . set ( 'redirect' , redirectUriRaw ) ;
if ( queryParams . has ( 'scope' ) ) {
scope = queryParams . get ( 'scope' ) ;
resolveParams . set ( 'scope' , scope ) ;
}
try {
const resolved = ( await $x . get ( ` /oauth2/resolve-authorise-app? ${ resolveParams } ` , { type : 'json' } ) ) . body ( ) ;
if ( ! resolved )
throw 'authorisation resolve failed' ;
if ( typeof resolved . error === 'string' )
2024-09-03 19:59:31 +00:00
return translateError ( resolved . error , resolved ) ;
2024-07-30 21:24:20 +00:00
const userHeader = new HanyuuOAuth2UserHeader ( resolved . user ) ;
header . setElement ( userHeader ) ;
const verifyAuthsRequest = async ( ) => {
const params = {
_csrfp : HanyuuCSRFP . getToken ( ) ,
client : queryParams . get ( 'client_id' ) ,
cc : codeChallenge ,
ccm : codeChallengeMethod ,
} ;
if ( redirectUriRaw !== undefined )
params . redirect = redirectUriRaw ;
if ( scope !== undefined )
params . scope = scope ;
try {
const response = ( await $x . post ( '/oauth2/authorise' , { type : 'json' } , params ) ) . body ( ) ;
if ( ! response )
throw 'authorisation failed' ;
if ( typeof response . error === 'string' )
2024-09-03 19:59:31 +00:00
return translateError ( response . error , resolved ) ;
2024-07-30 21:24:20 +00:00
const authoriseUri = new URL ( response . redirect ) ;
authoriseUri . searchParams . set ( 'code' , response . code ) ;
if ( state !== undefined )
authoriseUri . searchParams . set ( 'state' , state . toString ( ) ) ;
window . location . assign ( authoriseUri ) ;
} catch ( ex ) {
console . error ( ex ) ;
translateError ( 'authorise' ) ;
}
} ;
if ( resolved . app . trusted && resolved . user . guise === undefined ) {
if ( userHeader )
userHeader . guiseVisible = false ;
verifyAuthsRequest ( ) ;
return ;
}
2024-09-03 19:59:31 +00:00
eAuthsInfo . replaceWith ( new HanyuuOAuth2AppInfo ( resolved . app ) . element ) ;
eAuthsScope . replaceWith ( new HanyuuOAuth2AppScopeList ( resolved . scope ) . element ) ;
2024-07-30 21:24:20 +00:00
fAuths . onsubmit = ev => {
ev . preventDefault ( ) ;
loading . visible = true ;
fAuths . classList . add ( 'hidden' ) ;
if ( userHeader )
userHeader . guiseVisible = false ;
if ( ev . submitter ? . value === 'yes' )
verifyAuthsRequest ( ) ;
else
displayError ( 'access_denied' ) ;
} ;
loading . visible = false ;
fAuths . classList . remove ( 'hidden' ) ;
} catch ( ex ) {
console . error ( ex ) ;
displayError ( 'server_error' , 'Server was unable to respond to the client info request.' ) ;
}
} ;