Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow to disable Service Worker #1018

Closed
Legion2 opened this issue Apr 25, 2021 · 9 comments · Fixed by #1045
Closed

Allow to disable Service Worker #1018

Legion2 opened this issue Apr 25, 2021 · 9 comments · Fixed by #1045
Labels
enhancement New feature or request main ui Main UI

Comments

@Legion2
Copy link
Contributor

Legion2 commented Apr 25, 2021

The problem

There is a bug in chrome and other browsers, related to a problem in the Web Fetch API in combination with Service Workers and Basic Authentication, see whatwg/fetch#1132.
As a result it is not possible to use openHAB main ui behind a HTTPS reverse proxy with Basic Auth. On first page load it works. However one day later, the page is blank, because the Basic Auth prompt is not triggered by the Service Worker and therefore the access to the rest api is denied.

As you can see in the screenshot, the service worker caches the ui components, but the non-cached requests fail, because they don't trigger the Basic Auth prompt.
image

Your suggestion

This is a bug in Browser, not openHAB ui. However, while this bug is present as a workaround, openHAB should allow to disable the service worker.

Your environment

runtimeInfo:
  version: 3.0.2
  buildString: Release Build
locale: de-DE
systemInfo:
  configFolder: /etc/openhab
  userdataFolder: /var/lib/openhab
  logFolder: /var/log/openhab
  javaVersion: 11.0.9.1
  javaVendor: Raspbian
  osName: Linux
  osVersion: 5.10.17-v8+
  osArchitecture: arm
  availableProcessors: 4
  freeMemory: 59561608
  totalMemory: 144703488
bindings:
  - mqtt
  - openweathermap
  - tr064
  - upnpcontrol
clientInfo:
  device:
    ios: false
    android: false
    androidChrome: false
    desktop: true
    iphone: false
    ipod: false
    ipad: false
    edge: false
    ie: false
    firefox: false
    macos: false
    windows: true
    cordova: false
    phonegap: false
    electron: false
    nwjs: false
    webView: false
    webview: false
    standalone: false
    os: windows
    pixelRatio: 1
    prefersColorScheme: dark
  isSecureContext: false
  locationbarVisible: true
  menubarVisible: true
  navigator:
    cookieEnabled: true
    deviceMemory: N/A
    hardwareConcurrency: 16
    language: de-DE
    languages:
      - de-DE
      - de
      - en-US
      - en
    onLine: true
    platform: Win32
  screen:
    width: 2560
    height: 1440
    colorDepth: 24
  support:
    touch: false
    pointerEvents: true
    observer: true
    passiveListener: true
    gestures: false
    intersectionObserver: true
  themeOptions:
    dark: dark
    filled: true
    pageTransitionAnimation: default
    bars: light
    homeNavbar: default
    homeBackground: default
    expandableCardAnimation: default
  userAgent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML,
    like Gecko) Chrome/90.0.4430.85 Safari/537.36
timestamp: 2021-04-25T10:38:46.293Z

Additional information

I use Apache Web Server as Reverse Proxy with TLS termination and Basic Auth.

@Legion2 Legion2 added enhancement New feature or request main ui Main UI labels Apr 25, 2021
@ghys
Copy link
Member

ghys commented Apr 25, 2021

This is supposed to be solved by this piece of code:

.then(() => { return this.$oh.api.get('/rest/') })
.catch((err) => {
if (err === 'Unauthorized') {
if (!useCredentials) {
// try again with credentials
this.loadData(true)
return Promise.reject()
}
this.loginScreenOpened = true
this.$nextTick(() => {
this.$f7.dialog.login(
window.location.host,
'openHAB',
(username, password) => {
this.setBasicCredentials(username, password)
this.$oh.api.get('/rest/').then((rootResponse) => {
this.storeBasicCredentials()
this.loadData()
}).catch((err) => {
if (err === 'Unauthorized') {
this.clearBasicCredentials()
this.loadData()
return Promise.reject()
}
})

When the initial /rest/ call fails with an Unauthorized message, then it is assumed that Basic authentication by a reverse proxy is in place in addition to the openHAB's instance. In that case it will display a login form (not the browser's but a in-app dialog) and offer to save the Basic credentials to the reverse proxy so it will log in automatically the next time.
This was introduced in #812. Maybe you haven't updated the application in a while and don't have that change in your cache.
Try clearing the application data in the developer tools (Application tab > Storage > Clear site data) to make sure you have all the changes applied.
Also please post the details of the initial /rest/ call to figure out why it doesn't trigger the expected behavior.

@Legion2
Copy link
Contributor Author

Legion2 commented Apr 25, 2021

Ok I cleared the cache and will report back in the next days if the problem is resolved.

@Legion2
Copy link
Contributor Author

Legion2 commented Apr 29, 2021

@ghys still doesn't work. here are the details of the /rest/ request:

Request URL: https://***/rest/
Request Method: GET
Status Code: 401 
Remote Address: ***:443
Referrer Policy: strict-origin-when-cross-origin

Response Headers:
content-length: 381
content-type: text/html; charset=iso-8859-1
date: Thu, 29 Apr 2021 11:45:16 GMT
server: Apache
strict-transport-security: max-age=63072000; includeSubDomains
www-authenticate: Basic realm="Restricted Resources"

Request Headers:
:authority: ***
:method: GET
:path: /rest/
:scheme: https
accept: application/json
accept-encoding: gzip, deflate, br
accept-language: de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7
referer: https://***/
sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="90", "Google Chrome";v="90"
sec-ch-ua-mobile: ?0
sec-fetch-dest: empty
sec-fetch-mode: cors
sec-fetch-site: same-origin
user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36
x-requested-with: XMLHttpRequest

@ghys
Copy link
Member

ghys commented Apr 29, 2021

I see, Apache2 may have removed the HTTP Reason phrases like "200 OK" or "401 Unauthorized" at some point, in which case the code above - which only checks those as it is the first argument of the catch function - breaks.
https://bz.apache.org/bugzilla/show_bug.cgi?id=54946

Can you confirm it's the case? for example with curl -v https://***/rest/

Turns out a second argument with access to the actual status code, i.e. 401, is available, but it's ignored. This could probably be an easy fix.

@Legion2
Copy link
Contributor Author

Legion2 commented Apr 29, 2021

Yes, Apache does not send the status message < HTTP/2 401. I think when the status code is 401 it should be checked what information the www-authenticate header provide on how to authenticate.

@Legion2
Copy link
Contributor Author

Legion2 commented May 7, 2021

@ghys I want to create a PR but I don't know how to get the complete error response instead of only the error string.

@Legion2
Copy link
Contributor Author

Legion2 commented May 9, 2021

Turns out a second argument with access to the actual status code, i.e. 401, is available, but it's ignored. This could probably be an easy fix.

@ghys Looking at

get (uri, data) {
const fullUri = prepareRequest(uri)
if (fullUri) {
return new Promise((resolve, reject) => {
cordova.plugin.http.get(fullUri, null, {},
function (response) {
resolve(JSON.parse(response.data))
}, function (response) {
reject(response.error)
})
})
}
},

and the cordova-plugin-http documentation, there is an status property on the error response, which is currently not used. However, using this property and returning it, will break all existing uses of the api functions. So this is not an easy fix.

@ghys
Copy link
Member

ghys commented May 9, 2021

Don't worry about the Cordova API access layer too much, it's broken at the moment!

I don't know how to get the complete error response instead of only the error string.

I believe you'll find it in the second argument in case the promise is rejected:
i.e.

.catch((err) => {

becomes:

.catch((err, status) => {

and you will find the HTTP status in the status object.

.catch((err) => reject(err.message, err.status, err.xhr))

@Legion2
Copy link
Contributor Author

Legion2 commented May 9, 2021

When retrying loadData, shouldn't we return the promise of the second try instead of ignoring its result and returning Promise.reject()?

Also why don't we use async await syntax?

ghys pushed a commit that referenced this issue May 11, 2021
)

Use status code as fallback to the status message for error responses to detect whether the
initial requests are unauthorized (HTTP 401).
(With HTTP2 there is no status message and even in HTTP1 it is optional.)

Fix #1018.

Signed-off-by: Leon Kiefer <leon.k97@gmx.de>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request main ui Main UI
Projects
None yet
2 participants