11const OAuth2Strategy = require ( "passport-oauth2" ) ;
22const passport = require ( "passport" ) ;
33const config = require ( "./config" ) ;
4- const { decodeOpaqueId } = require ( "./lib/utils/decoding" ) ;
54const logger = require ( "./lib/logger" ) ;
65
6+ let baseUrl = config . CANONICAL_URL ;
7+ if ( ! baseUrl . endsWith ( "/" ) ) baseUrl = `${ baseUrl } /` ;
8+
9+ const oauthRedirectUrl = `${ baseUrl } callback` ;
10+ const oauthPostLogoutRedirectUrl = `${ baseUrl } post-logout-callback` ;
11+
12+ /* eslint-disable camelcase */
13+ const storefrontHydraClient = {
14+ client_id : config . OAUTH2_CLIENT_ID ,
15+ client_secret : config . OAUTH2_CLIENT_SECRET ,
16+ grant_types : [
17+ "authorization_code" ,
18+ "refresh_token"
19+ ] ,
20+ post_logout_redirect_uris : [ oauthPostLogoutRedirectUrl ] ,
21+ redirect_uris : [ oauthRedirectUrl ] ,
22+ response_types : [ "code" , "id_token" , "token" ] ,
23+ scope : "offline openid" ,
24+ subject_type : "public" ,
25+ token_endpoint_auth_method : "client_secret_post"
26+ } ;
27+ /* eslint-enable camelcase */
28+
729// This is needed to allow custom parameters (e.g. loginActions) to be included
830// when requesting authorization. This is setup to allow only loginAction to pass through
931OAuth2Strategy . prototype . authorizationParams = function ( options = { } ) {
@@ -18,12 +40,12 @@ passport.use(
1840 tokenURL : config . OAUTH2_TOKEN_URL ,
1941 clientID : config . OAUTH2_CLIENT_ID ,
2042 clientSecret : config . OAUTH2_CLIENT_SECRET ,
21- callbackURL : config . OAUTH2_REDIRECT_URL ,
43+ callbackURL : oauthRedirectUrl ,
2244 state : true ,
23- scope : [ "offline" ]
45+ scope : [ "offline" , "openid" ]
2446 } ,
25- ( accessToken , refreshToken , profile , cb ) => {
26- cb ( null , { accessToken } ) ;
47+ ( accessToken , refreshToken , params , profile , cb ) => {
48+ cb ( null , { accessToken, idToken : params . id_token } ) ;
2749 }
2850 )
2951) ;
@@ -74,36 +96,40 @@ function configureAuthForServer(server) {
7496 res . redirect ( req . session . redirectTo || "/" ) ;
7597 } ) ;
7698
77- server . get ( "/logout/:userId" , ( req , res , next ) => {
78- const { userId } = req . params ;
79- if ( ! userId ) {
80- next ( ) ;
81- return ;
99+ server . get ( "/change-password" , ( req , res ) => {
100+ const { email } = req . query ;
101+
102+ let from = req . get ( "Referer" ) ;
103+ if ( typeof from !== "string" || from . length === 0 ) {
104+ from = config . CANONICAL_URL ;
105+ }
106+
107+ let url = config . OAUTH2_IDP_PUBLIC_CHANGE_PASSWORD_URL ;
108+ url = url . replace ( "EMAIL" , encodeURIComponent ( email || "" ) ) ;
109+ url = url . replace ( "FROM" , encodeURIComponent ( from ) ) ;
110+
111+ res . redirect ( url ) ;
112+ } ) ;
113+
114+ server . get ( "/logout" , ( req , res ) => {
115+ req . session . redirectTo = req . get ( "Referer" ) ;
116+
117+ const { idToken } = req . user || { } ;
118+
119+ // Clear storefront session auth
120+ req . logout ( ) ;
121+
122+ if ( idToken ) {
123+ // Request log out of OAuth2 session
124+ res . redirect ( `${ config . OAUTH2_PUBLIC_LOGOUT_URL } ?post_logout_redirect_uri=${ oauthPostLogoutRedirectUrl } &id_token_hint=${ idToken } ` ) ;
125+ } else {
126+ res . redirect ( req . session . redirectTo || config . CANONICAL_URL ) ;
82127 }
83- const { id } = decodeOpaqueId ( req . params . userId ) ;
84-
85- let urlBase = config . OAUTH2_IDP_HOST_URL ;
86- if ( ! urlBase . endsWith ( "/" ) ) urlBase = `${ urlBase } /` ;
87-
88- // Ask IDP to log us out
89- fetch ( `${ urlBase } logout-user?userId=${ id } ` )
90- . then ( ( logoutResponse ) => {
91- if ( logoutResponse . status >= 400 ) {
92- const message = `Error from OAUTH2_IDP_HOST_URL logout endpoint: ${ logoutResponse . status } . Check the HOST server settings` ;
93-
94- logger . error ( message ) ;
95- res . status ( logoutResponse . status ) . send ( message ) ;
96- return ;
97- }
98- // If IDP confirmed logout, clear login info on this side
99- req . logout ( ) ;
100- res . redirect ( req . get ( "Referer" ) || "/" ) ;
101- return ; // appease eslint consistent-return
102- } )
103- . catch ( ( error ) => {
104- logger . error ( `Error while logging out: ${ error } ` ) ;
105- res . status ( 500 ) . send ( `Error while logging out: ${ error . message } ` ) ;
106- } ) ;
128+ } ) ;
129+
130+ server . get ( "/post-logout-callback" , ( req , res ) => {
131+ // After success, redirect to the page we came from originally
132+ res . redirect ( req . session . redirectTo || "/" ) ;
107133 } ) ;
108134}
109135
@@ -115,47 +141,58 @@ function configureAuthForServer(server) {
115141 * @returns {Promise<undefined> } Nothing
116142 */
117143async function createHydraClientIfNecessary ( ) {
118- /* eslint-disable camelcase */
119- const bodyEncoded = JSON . stringify ( {
120- client_id : config . OAUTH2_CLIENT_ID ,
121- client_secret : config . OAUTH2_CLIENT_SECRET ,
122- grant_types : [
123- "authorization_code" ,
124- "refresh_token"
125- ] ,
126- jwks : { } ,
127- redirect_uris : [ config . OAUTH2_REDIRECT_URL ] ,
128- response_types : [ "token" , "code" ] ,
129- scope : "offline" ,
130- subject_type : "public" ,
131- token_endpoint_auth_method : "client_secret_post"
132- } ) ;
133- /* eslint-enable camelcase */
134-
135144 let adminUrl = config . OAUTH2_ADMIN_URL ;
136145 if ( ! adminUrl . endsWith ( "/" ) ) adminUrl = `${ adminUrl } /` ;
137146
138- logger . info ( "Creating Hydra client..." ) ;
139-
140- const response = await fetch ( `${ adminUrl } clients` , {
141- method : "POST" ,
142- headers : { "Content-Type" : "application/json" } ,
143- body : bodyEncoded
147+ const getClientResponse = await fetch ( `${ adminUrl } clients/${ config . OAUTH2_CLIENT_ID } ` , {
148+ method : "GET" ,
149+ headers : { "Content-Type" : "application/json" }
144150 } ) ;
145151
146- switch ( response . status ) {
147- case 200 :
148- // intentional fallthrough!
149- // eslint-disable-line no-fallthrough
150- case 201 :
151- logger . info ( "OK: Hydra client created" ) ;
152- break ;
153- case 409 :
154- logger . info ( "OK: Hydra client already exists" ) ;
155- break ;
156- default :
157- logger . error ( await response . text ( ) ) ;
158- throw new Error ( `Could not create Hydra client [${ response . status } ]` ) ;
152+ if ( ! [ 200 , 404 ] . includes ( getClientResponse . status ) ) {
153+ logger . error ( await getClientResponse . text ( ) ) ;
154+ throw new Error ( `Could not get Hydra client [${ getClientResponse . status } ]` ) ;
155+ }
156+
157+ if ( getClientResponse . status === 200 ) {
158+ // Update the client to be sure it has the latest config
159+ logger . info ( "Updating Hydra client..." ) ;
160+
161+ const updateClientResponse = await fetch ( `${ adminUrl } clients/${ config . OAUTH2_CLIENT_ID } ` , {
162+ method : "PUT" ,
163+ headers : { "Content-Type" : "application/json" } ,
164+ body : JSON . stringify ( storefrontHydraClient )
165+ } ) ;
166+
167+ if ( updateClientResponse . status === 200 ) {
168+ logger . info ( "OK: Hydra client updated" ) ;
169+ } else {
170+ logger . error ( await updateClientResponse . text ( ) ) ;
171+ throw new Error ( `Could not update Hydra client [${ updateClientResponse . status } ]` ) ;
172+ }
173+ } else {
174+ logger . info ( "Creating Hydra client..." ) ;
175+
176+ const response = await fetch ( `${ adminUrl } clients` , {
177+ method : "POST" ,
178+ headers : { "Content-Type" : "application/json" } ,
179+ body : JSON . stringify ( storefrontHydraClient )
180+ } ) ;
181+
182+ switch ( response . status ) {
183+ case 200 :
184+ // intentional fallthrough!
185+ // eslint-disable-line no-fallthrough
186+ case 201 :
187+ logger . info ( "OK: Hydra client created" ) ;
188+ break ;
189+ case 409 :
190+ logger . info ( "OK: Hydra client already exists" ) ;
191+ break ;
192+ default :
193+ logger . error ( await response . text ( ) ) ;
194+ throw new Error ( `Could not create Hydra client [${ response . status } ]` ) ;
195+ }
159196 }
160197}
161198
0 commit comments