2022-06-09 10:51:19 +00:00
/ *
GoToSocial
Copyright ( C ) 2021 - 2022 GoToSocial Authors admin @ gotosocial . org
This program is free software : you can redistribute it and / or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation , either version 3 of the License , or
( at your option ) any later version .
This program is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU Affero General Public License for more details .
You should have received a copy of the GNU Affero General Public License
along with this program . If not , see < http : //www.gnu.org/licenses/>.
* /
"use strict" ;
const Promise = require ( "bluebird" ) ;
function getCurrentUrl ( ) {
return window . location . origin + window . location . pathname ; // strips ?query=string and #hash
}
module . exports = function oauthClient ( config , initState ) {
/ * c o n f i g :
instance : instance domain ( https : //testingtesting123.xyz)
client _name : "GoToSocial Admin Panel"
scope : [ ]
website :
* /
let state = initState ;
if ( initState == undefined ) {
state = localStorage . getItem ( "oauth" ) ;
if ( state == undefined ) {
state = {
config
} ;
storeState ( ) ;
} else {
state = JSON . parse ( state ) ;
}
}
function storeState ( ) {
localStorage . setItem ( "oauth" , JSON . stringify ( state ) ) ;
}
/ * r e g i s t e r a p p
/ a p i / v 1 / a p p s
* /
function register ( ) {
if ( state . client _id != undefined ) {
return true ; // we already have a registration
}
let url = new URL ( config . instance ) ;
url . pathname = "/api/v1/apps" ;
return fetch ( url . href , {
method : "POST" ,
headers : {
'Content-Type' : 'application/json'
} ,
body : JSON . stringify ( {
client _name : config . client _name ,
redirect _uris : getCurrentUrl ( ) ,
scopes : config . scope . join ( " " ) ,
website : getCurrentUrl ( )
} )
} ) . then ( ( res ) => {
if ( res . status != 200 ) {
throw res ;
}
return res . json ( ) ;
} ) . then ( ( json ) => {
state . client _id = json . client _id ;
state . client _secret = json . client _secret ;
storeState ( ) ;
} ) ;
}
/ * a u t h o r i z e :
/ o a u t h / a u t h o r i z e
? client _id = CLIENT _ID
& redirect _uri = window . location . href
& response _type = code
& scope = admin
* /
function authorize ( ) {
let url = new URL ( config . instance ) ;
url . pathname = "/oauth/authorize" ;
url . searchParams . set ( "client_id" , state . client _id ) ;
url . searchParams . set ( "redirect_uri" , getCurrentUrl ( ) ) ;
url . searchParams . set ( "response_type" , "code" ) ;
url . searchParams . set ( "scope" , config . scope . join ( " " ) ) ;
window . location . assign ( url . href ) ;
}
function callback ( ) {
if ( state . access _token != undefined ) {
return ; // we're already done :)
}
let params = ( new URL ( window . location ) ) . searchParams ;
let token = params . get ( "code" ) ;
if ( token != null ) {
console . log ( "got token callback:" , token ) ;
}
return authorizeToken ( token )
. catch ( ( e ) => {
console . log ( "Error processing oauth callback:" , e ) ;
logout ( ) ; // just to be sure
} ) ;
}
function authorizeToken ( token ) {
let url = new URL ( config . instance ) ;
url . pathname = "/oauth/token" ;
return fetch ( url . href , {
method : "POST" ,
headers : {
"Content-Type" : "application/json"
} ,
body : JSON . stringify ( {
client _id : state . client _id ,
client _secret : state . client _secret ,
redirect _uri : getCurrentUrl ( ) ,
grant _type : "authorization_code" ,
code : token
} )
} ) . then ( ( res ) => {
if ( res . status != 200 ) {
throw res ;
}
return res . json ( ) ;
} ) . then ( ( json ) => {
state . access _token = json . access _token ;
storeState ( ) ;
window . location = getCurrentUrl ( ) ; // clear ?token=
} ) ;
}
function isAuthorized ( ) {
return ( state . access _token != undefined ) ;
}
2022-06-20 08:44:01 +00:00
function apiRequest ( path , method , data , type = "json" , accept = "json" ) {
2022-06-09 10:51:19 +00:00
if ( ! isAuthorized ( ) ) {
throw new Error ( "Not Authenticated" ) ;
}
let url = new URL ( config . instance ) ;
let [ p , s ] = path . split ( "?" ) ;
url . pathname = p ;
if ( s != undefined ) {
url . search = s ;
}
let headers = {
2022-06-20 08:44:01 +00:00
"Authorization" : ` Bearer ${ state . access _token } ` ,
"Accept" : accept == "json" ? "application/json" : "*/*"
2022-06-09 10:51:19 +00:00
} ;
let body = data ;
if ( type == "json" && body != undefined ) {
headers [ "Content-Type" ] = "application/json" ;
body = JSON . stringify ( data ) ;
}
return fetch ( url . href , {
method ,
headers ,
body
} ) . then ( ( res ) => {
return Promise . all ( [ res . json ( ) , res ] ) ;
} ) . then ( ( [ json , res ] ) => {
if ( res . status != 200 ) {
if ( json . error ) {
throw new Error ( json . error ) ;
} else {
throw new Error ( ` ${ res . status } : ${ res . statusText } ` ) ;
}
} else {
return json ;
}
2022-09-05 12:24:51 +00:00
} ) . catch ( e => {
if ( e instanceof SyntaxError ) {
throw new Error ( "Error: The GtS API returned a non-json error. This usually means a network problem, or an issue with your instance's reverse proxy configuration." , { cause : e } ) ;
} else {
throw e ;
}
2022-06-09 10:51:19 +00:00
} ) ;
}
function logout ( ) {
let url = new URL ( config . instance ) ;
url . pathname = "/oauth/revoke" ;
return fetch ( url . href , {
method : "POST" ,
headers : {
"Content-Type" : "application/json"
} ,
body : JSON . stringify ( {
client _id : state . client _id ,
client _secret : state . client _secret ,
token : state . access _token ,
} )
} ) . then ( ( res ) => {
if ( res . status != 200 ) {
// GoToSocial doesn't actually implement this route yet,
// so error is to be expected
return ;
}
return res . json ( ) ;
} ) . catch ( ( ) => {
// see above
} ) . then ( ( ) => {
localStorage . removeItem ( "oauth" ) ;
window . location = getCurrentUrl ( ) ;
} ) ;
}
return {
register , authorize , callback , isAuthorized , apiRequest , logout
} ;
} ;