@@ -4,6 +4,29 @@ const config = require("./config");
44const { decodeOpaqueId } = require ( "./lib/utils/decoding" ) ;
55const logger = require ( "./lib/logger" ) ;
66
7+ let baseUrl = config . CANONICAL_URL ;
8+ if ( ! baseUrl . endsWith ( "/" ) ) baseUrl = `${ baseUrl } /` ;
9+
10+ const oauthRedirectUrl = `${ baseUrl } callback` ;
11+ const oauthPostLogoutRedirectUrl = `${ baseUrl } post-logout-callback` ;
12+
13+ /* eslint-disable camelcase */
14+ const storefrontHydraClient = {
15+ client_id : config . OAUTH2_CLIENT_ID ,
16+ client_secret : config . OAUTH2_CLIENT_SECRET ,
17+ grant_types : [
18+ "authorization_code" ,
19+ "refresh_token"
20+ ] ,
21+ post_logout_redirect_uris : [ oauthPostLogoutRedirectUrl ] ,
22+ redirect_uris : [ oauthRedirectUrl ] ,
23+ response_types : [ "code" , "id_token" , "token" ] ,
24+ scope : "offline openid" ,
25+ subject_type : "public" ,
26+ token_endpoint_auth_method : "client_secret_post"
27+ } ;
28+ /* eslint-enable camelcase */
29+
730// This is needed to allow custom parameters (e.g. loginActions) to be included
831// when requesting authorization. This is setup to allow only loginAction to pass through
932OAuth2Strategy . prototype . authorizationParams = function ( options = { } ) {
@@ -18,12 +41,12 @@ passport.use(
1841 tokenURL : config . OAUTH2_TOKEN_URL ,
1942 clientID : config . OAUTH2_CLIENT_ID ,
2043 clientSecret : config . OAUTH2_CLIENT_SECRET ,
21- callbackURL : config . OAUTH2_REDIRECT_URL ,
44+ callbackURL : oauthRedirectUrl ,
2245 state : true ,
23- scope : [ "offline" ]
46+ scope : [ "offline" , "openid" ]
2447 } ,
25- ( accessToken , refreshToken , profile , cb ) => {
26- cb ( null , { accessToken } ) ;
48+ ( accessToken , refreshToken , params , profile , cb ) => {
49+ cb ( null , { accessToken, idToken : params . id_token } ) ;
2750 }
2851 )
2952) ;
@@ -89,36 +112,25 @@ function configureAuthForServer(server) {
89112 res . redirect ( url ) ;
90113 } ) ;
91114
92- server . get ( "/logout/:userId" , ( req , res , next ) => {
93- const { userId } = req . params ;
94- if ( ! userId ) {
95- next ( ) ;
96- return ;
115+ server . get ( "/logout" , ( req , res , next ) => {
116+ req . session . redirectTo = req . get ( "Referer" ) ;
117+
118+ const { idToken } = req . user || { } ;
119+
120+ // Clear storefront session auth
121+ req . logout ( ) ;
122+
123+ if ( idToken ) {
124+ // Request log out of OAuth2 session
125+ res . redirect ( `${ config . OAUTH2_PUBLIC_LOGOUT_URL } ?post_logout_redirect_uri=${ oauthPostLogoutRedirectUrl } &id_token_hint=${ idToken } ` ) ;
126+ } else {
127+ res . redirect ( req . session . redirectTo || config . CANONICAL_URL ) ;
97128 }
98- const { id } = decodeOpaqueId ( req . params . userId ) ;
99-
100- let urlBase = config . OAUTH2_IDP_HOST_URL ;
101- if ( ! urlBase . endsWith ( "/" ) ) urlBase = `${ urlBase } /` ;
102-
103- // Ask IDP to log us out
104- fetch ( `${ urlBase } logout-user?userId=${ id } ` )
105- . then ( ( logoutResponse ) => {
106- if ( logoutResponse . status >= 400 ) {
107- const message = `Error from OAUTH2_IDP_HOST_URL logout endpoint: ${ logoutResponse . status } . Check the HOST server settings` ;
108-
109- logger . error ( message ) ;
110- res . status ( logoutResponse . status ) . send ( message ) ;
111- return ;
112- }
113- // If IDP confirmed logout, clear login info on this side
114- req . logout ( ) ;
115- res . redirect ( req . get ( "Referer" ) || "/" ) ;
116- return ; // appease eslint consistent-return
117- } )
118- . catch ( ( error ) => {
119- logger . error ( `Error while logging out: ${ error } ` ) ;
120- res . status ( 500 ) . send ( `Error while logging out: ${ error . message } ` ) ;
121- } ) ;
129+ } ) ;
130+
131+ server . get ( "/post-logout-callback" , ( req , res ) => {
132+ // After success, redirect to the page we came from originally
133+ res . redirect ( req . session . redirectTo || "/" ) ;
122134 } ) ;
123135}
124136
@@ -130,47 +142,58 @@ function configureAuthForServer(server) {
130142 * @returns {Promise<undefined> } Nothing
131143 */
132144async function createHydraClientIfNecessary ( ) {
133- /* eslint-disable camelcase */
134- const bodyEncoded = JSON . stringify ( {
135- client_id : config . OAUTH2_CLIENT_ID ,
136- client_secret : config . OAUTH2_CLIENT_SECRET ,
137- grant_types : [
138- "authorization_code" ,
139- "refresh_token"
140- ] ,
141- jwks : { } ,
142- redirect_uris : [ config . OAUTH2_REDIRECT_URL ] ,
143- response_types : [ "token" , "code" ] ,
144- scope : "offline" ,
145- subject_type : "public" ,
146- token_endpoint_auth_method : "client_secret_post"
147- } ) ;
148- /* eslint-enable camelcase */
149-
150145 let adminUrl = config . OAUTH2_ADMIN_URL ;
151146 if ( ! adminUrl . endsWith ( "/" ) ) adminUrl = `${ adminUrl } /` ;
152147
153- logger . info ( "Creating Hydra client..." ) ;
154-
155- const response = await fetch ( `${ adminUrl } clients` , {
156- method : "POST" ,
157- headers : { "Content-Type" : "application/json" } ,
158- body : bodyEncoded
148+ const getClientResponse = await fetch ( `${ adminUrl } clients/${ config . OAUTH2_CLIENT_ID } ` , {
149+ method : "GET" ,
150+ headers : { "Content-Type" : "application/json" }
159151 } ) ;
160152
161- switch ( response . status ) {
162- case 200 :
163- // intentional fallthrough!
164- // eslint-disable-line no-fallthrough
165- case 201 :
166- logger . info ( "OK: Hydra client created" ) ;
167- break ;
168- case 409 :
169- logger . info ( "OK: Hydra client already exists" ) ;
170- break ;
171- default :
172- logger . error ( await response . text ( ) ) ;
173- throw new Error ( `Could not create Hydra client [${ response . status } ]` ) ;
153+ if ( ! [ 200 , 404 ] . includes ( getClientResponse . status ) ) {
154+ logger . error ( await getClientResponse . text ( ) ) ;
155+ throw new Error ( `Could not get Hydra client [${ getClientResponse . status } ]` ) ;
156+ }
157+
158+ if ( getClientResponse . status === 200 ) {
159+ // Update the client to be sure it has the latest config
160+ logger . info ( "Updating Hydra client..." ) ;
161+
162+ const updateClientResponse = await fetch ( `${ adminUrl } clients/${ config . OAUTH2_CLIENT_ID } ` , {
163+ method : "PUT" ,
164+ headers : { "Content-Type" : "application/json" } ,
165+ body : JSON . stringify ( storefrontHydraClient )
166+ } ) ;
167+
168+ if ( updateClientResponse . status === 200 ) {
169+ logger . info ( "OK: Hydra client updated" ) ;
170+ } else {
171+ logger . error ( await updateClientResponse . text ( ) ) ;
172+ throw new Error ( `Could not update Hydra client [${ updateClientResponse . status } ]` ) ;
173+ }
174+ } else {
175+ logger . info ( "Creating Hydra client..." ) ;
176+
177+ const response = await fetch ( `${ adminUrl } clients` , {
178+ method : "POST" ,
179+ headers : { "Content-Type" : "application/json" } ,
180+ body : JSON . stringify ( storefrontHydraClient )
181+ } ) ;
182+
183+ switch ( response . status ) {
184+ case 200 :
185+ // intentional fallthrough!
186+ // eslint-disable-line no-fallthrough
187+ case 201 :
188+ logger . info ( "OK: Hydra client created" ) ;
189+ break ;
190+ case 409 :
191+ logger . info ( "OK: Hydra client already exists" ) ;
192+ break ;
193+ default :
194+ logger . error ( await response . text ( ) ) ;
195+ throw new Error ( `Could not create Hydra client [${ response . status } ]` ) ;
196+ }
174197 }
175198}
176199
0 commit comments