From 8af3c09458bd806b4cacea43b65c560f11abc6ae Mon Sep 17 00:00:00 2001 From: Denis Arnst Date: Mon, 4 Dec 2023 22:43:14 +0100 Subject: [PATCH 1/3] SSO: Add redirect_uri to oAuth auth request Also: - Add client_id with the name of the app (for possible future use) - Add response_type simply to provide all required oauth2 parameters for reference - For the URL to be opened in Browser: Forward the callback/redirect_uri URL provided by the server - Use a hardcoded variable for HTTPS enforcement, for easier debugging --- components/connection/ServerConnectForm.vue | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/components/connection/ServerConnectForm.vue b/components/connection/ServerConnectForm.vue index d02fbb6f..f700b31c 100644 --- a/components/connection/ServerConnectForm.vue +++ b/components/connection/ServerConnectForm.vue @@ -107,7 +107,8 @@ export default { state: null, verifier: null, challenge: null, - buttonText: 'Login with OpenID' + buttonText: 'Login with OpenID', + enforceHTTPs: true // RFC 6749, Section 10.9 requires https } } }, @@ -155,7 +156,7 @@ export default { */ async clickLoginWithOpenId() { // oauth standard requires https explicitly - if (!this.serverConfig.address.startsWith('https')) { + if (!this.serverConfig.address.startsWith('https') && this.oauth.enforceHTTPs) { console.warn(`[SSO] Oauth2 requires HTTPS`) this.$toast.error(`SSO: The URL to the server must be https:// secured`) return @@ -179,14 +180,15 @@ export default { const client_id = redirectUrl.searchParams.get('client_id') const scope = redirectUrl.searchParams.get('scope') const state = redirectUrl.searchParams.get('state') + const redirect_uri_param = redirectUrl.searchParams.get('redirect_uri') - if (!client_id || !scope || !state) { - console.warn(`[SSO] Invalid OpenID URL - client_id scope or state missing: ${redirectUrl}`) + if (!client_id || !scope || !state || !redirect_uri_param) { + console.warn(`[SSO] Invalid OpenID URL - client_id scope state or redirect_uri missing: ${redirectUrl}`) this.$toast.error(`SSO: Invalid answer`) return } - if (redirectUrl.protocol !== 'https:') { + if (redirectUrl.protocol !== 'https:' && this.oauth.enforceHTTPs) { console.warn(`[SSO] Insecure Redirection by SSO provider: ${redirectUrl.protocol} is not allowed. Use HTTPS`) this.$toast.error(`SSO: The SSO provider must return a HTTPS secured URL`) return @@ -195,8 +197,8 @@ export default { // We need to verify if the state is the same later this.oauth.state = state - const host = `https://${redirectUrl.host}` - const buildUrl = `${host}${redirectUrl.pathname}?response_type=code` + `&client_id=${encodeURIComponent(client_id)}&scope=${encodeURIComponent(scope)}&state=${encodeURIComponent(state)}` + `&redirect_uri=${encodeURIComponent('audiobookshelf://oauth')}` + `&code_challenge=${encodeURIComponent(this.oauth.challenge)}&code_challenge_method=S256` + const host = `${redirectUrl.protocol}//${redirectUrl.host}` + const buildUrl = `${host}${redirectUrl.pathname}?response_type=code` + `&client_id=${encodeURIComponent(client_id)}&scope=${encodeURIComponent(scope)}&state=${encodeURIComponent(state)}` + `&redirect_uri=${encodeURIComponent(redirect_uri_param)}` + `&code_challenge=${encodeURIComponent(this.oauth.challenge)}&code_challenge_method=S256` // example url for authentik // const authURL = "https://authentik/application/o/authorize/?response_type=code&client_id=41cd96f...&redirect_uri=audiobookshelf%3A%2F%2Foauth&scope=openid%20openid%20email%20profile&state=asdds..." @@ -244,7 +246,7 @@ export default { this.oauth.challenge = challenge // set parameter isRest to true, so the backend wont attempt a redirect after we call backend:/callback in exchangeCodeForToken - const backendEndpoint = `${url}/auth/openid?code_challenge=${challenge}&code_challenge_method=S256&isRest=true` + const backendEndpoint = `${url}/auth/openid?code_challenge=${challenge}&code_challenge_method=S256&redirect_uri=${encodeURIComponent('audiobookshelf://oauth')}&client_id=${encodeURIComponent('Audiobookshelf-App')}&response_type=code&isRest=true` try { const response = await CapacitorHttp.get({ From 044a35aacf0b0ce907d752c5278b055ea686bac0 Mon Sep 17 00:00:00 2001 From: Denis Arnst Date: Tue, 5 Dec 2023 09:44:20 +0100 Subject: [PATCH 2/3] SSO: Removed isRest as requirement Removed in the backend to increase compatibility with possible 3rd party oauth libraries people might use --- components/connection/ServerConnectForm.vue | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/components/connection/ServerConnectForm.vue b/components/connection/ServerConnectForm.vue index f700b31c..3be5bfbf 100644 --- a/components/connection/ServerConnectForm.vue +++ b/components/connection/ServerConnectForm.vue @@ -245,8 +245,7 @@ export default { this.oauth.verifier = verifier this.oauth.challenge = challenge - // set parameter isRest to true, so the backend wont attempt a redirect after we call backend:/callback in exchangeCodeForToken - const backendEndpoint = `${url}/auth/openid?code_challenge=${challenge}&code_challenge_method=S256&redirect_uri=${encodeURIComponent('audiobookshelf://oauth')}&client_id=${encodeURIComponent('Audiobookshelf-App')}&response_type=code&isRest=true` + const backendEndpoint = `${url}/auth/openid?code_challenge=${challenge}&code_challenge_method=S256&redirect_uri=${encodeURIComponent('audiobookshelf://oauth')}&client_id=${encodeURIComponent('Audiobookshelf-App')}&response_type=code` try { const response = await CapacitorHttp.get({ From 74fef1d2f4c8a9f390e1ee7052873bf0239edbd5 Mon Sep 17 00:00:00 2001 From: advplyr Date: Thu, 7 Dec 2023 11:07:37 -0600 Subject: [PATCH 3/3] Make backwards compatible with server v2.6.0 --- components/connection/ServerConnectForm.vue | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/components/connection/ServerConnectForm.vue b/components/connection/ServerConnectForm.vue index 3be5bfbf..0f39ab0d 100644 --- a/components/connection/ServerConnectForm.vue +++ b/components/connection/ServerConnectForm.vue @@ -95,6 +95,7 @@ export default { processing: false, serverConfig: { address: null, + version: null, username: null, customHeaders: null }, @@ -180,7 +181,11 @@ export default { const client_id = redirectUrl.searchParams.get('client_id') const scope = redirectUrl.searchParams.get('scope') const state = redirectUrl.searchParams.get('state') - const redirect_uri_param = redirectUrl.searchParams.get('redirect_uri') + let redirect_uri_param = redirectUrl.searchParams.get('redirect_uri') + // Backwards compatability with 2.6.0 + if (this.serverConfig.version === '2.6.0') { + redirect_uri_param = 'audiobookshelf://oauth' + } if (!client_id || !scope || !state || !redirect_uri_param) { console.warn(`[SSO] Invalid OpenID URL - client_id scope state or redirect_uri missing: ${redirectUrl}`) @@ -245,7 +250,11 @@ export default { this.oauth.verifier = verifier this.oauth.challenge = challenge - const backendEndpoint = `${url}/auth/openid?code_challenge=${challenge}&code_challenge_method=S256&redirect_uri=${encodeURIComponent('audiobookshelf://oauth')}&client_id=${encodeURIComponent('Audiobookshelf-App')}&response_type=code` + let backendEndpoint = `${url}/auth/openid?code_challenge=${challenge}&code_challenge_method=S256&redirect_uri=${encodeURIComponent('audiobookshelf://oauth')}&client_id=${encodeURIComponent('Audiobookshelf-App')}&response_type=code` + // Backwards compatability with 2.6.0 + if (this.serverConfig.version === '2.6.0') { + backendEndpoint += '&isRest=true' + } try { const response = await CapacitorHttp.get({ @@ -609,6 +618,7 @@ export default { this.showAuth = true this.authMethods = statusData.data.authMethods || [] this.oauth.buttonText = statusData.data.authFormData?.authOpenIDButtonText || 'Login with OpenID' + this.serverConfig.version = statusData.data.serverVersion if (statusData.data.authFormData?.authOpenIDAutoLaunch) { this.clickLoginWithOpenId()