Beispiel #1
0
def set_auth_info_from_token(token):
    """Set the authentication by providing a TimeTagger webtoken."""
    payload_base64 = token.split(".")[1].replace("_", "/")
    auth_info = JSON.parse(
        window.decodeURIComponent(window.escape(window.atob(payload_base64))))
    auth_info.token = token
    localStorage.setItem("timetagger_auth_info", JSON.stringify(auth_info))
Beispiel #2
0
    async def _pull(self, authtoken):

        # Fetch and wait for response
        url = tools.build_api_url("updates?since=" + self._server_time)
        init = dict(method="GET", headers={"authtoken": authtoken})
        try:
            res = await window.fetch(url, init)
        except Exception as err:
            res = dict(status=0, statusText=str(err), text=lambda: "")
        self._pull_statuses.append(res.status)
        self._pull_statuses = self._pull_statuses[-5:]

        # Process response
        if res.status != 200:
            text = await res.text()
            console.warn(res.status + " (" + res.statusText + ") " + text)
            self._set_state("error")  # E.g. Wifi or server down, or 500
            if res.status == 401:
                # Our token is probably expired. There may be local
                # changes that have not yet been pushed, which would
                # be lost if we logout. On the other hand, this may be
                # a lost/stolen device and the user revoked the token.
                # We can distinguish between these cases by determining
                # that the 401 is due to a token seed mismatch (revoked).
                self._auth_cantuse = text
                if "revoked" in text:
                    window.location.href = "../logout"
        else:
            ob = JSON.parse(await res.text())
            if ob.server_time:
                self._log_load("server", ob)
                # Reset?
                if ob.reset:
                    await self._clear_cache()
                    self.reset()
                self._server_time = ob.server_time
                # The odds of something going wrong here are tiny ...
                # but if they happen, we're out of sync with the server :(
                try:
                    self.settings._put_received(*ob.settings)
                except Exception as err:
                    self._set_state("warning")
                    console.error(err)
                    window.alert(
                        "Sync error (settings), see dev console for details.")
                try:
                    self.records._put_received(*ob.records)
                except Exception as err:
                    self._set_state("warning")
                    console.error(err)
                    window.alert(
                        "Sync error (records), see dev console for details.")

                # Set state to ok if we got new items, and if there were no errors
                if ob.settings or ob.records:
                    if self.state != "warning":
                        self._set_state("ok")
Beispiel #3
0
    async def _pull(self, authtoken):

        # Build url
        url = location.protocol + "//" + location.hostname + ":" + location.port
        url = url.rstrip(":") + "/api/v1/updates?since=" + self._server_time

        # Fetch and wait for response
        init = dict(method="GET", headers={"authtoken": authtoken})
        try:
            res = await window.fetch(url, init)
        except Exception as err:
            res = dict(status=0, statusText=str(err), text=lambda: "")
        self._pull_statuses.append(res.status)
        self._pull_statuses = self._pull_statuses[-5:]

        # Process response
        if res.status != 200:
            console.warn(res.status + " (" + res.statusText + ") " +
                         await res.text())
            self._set_state("error")  # E.g. Wifi or server down, or 500
            if res.status == 403:
                if self._pull_statuses[-2] != 403 and self._pull_statuses[
                        -3] != 403:
                    # We do have internet, but token is invalid: renew and sync sooner
                    await window.auth.renew_maybe()
                    self.sync_soon(4)
        else:
            ob = JSON.parse(await res.text())
            if ob.server_time:
                self._log_load("server", ob)
                # Reset?
                if ob.reset:
                    await self._clear_cache()
                    self.reset()
                self._server_time = ob.server_time
                # The odds of something going wrong here are tiny ...
                # but if they happen, we're out of sync with the server :(
                try:
                    self.settings._put_received(*ob.settings)
                except Exception as err:
                    self._set_state("warning")
                    console.error(err)
                    window.alert(
                        "Sync error (settings), see dev console for details.")
                try:
                    self.records._put_received(*ob.records)
                except Exception as err:
                    self._set_state("warning")
                    console.error(err)
                    window.alert(
                        "Sync error (records), see dev console for details.")

                # Set state to ok if we got new items, and if there were no errors
                if ob.settings or ob.records:
                    if self.state != "warning":
                        self._set_state("ok")
Beispiel #4
0
def get_auth_info():
    """Get the authentication info or None."""
    x = localStorage.getItem("timetagger_auth_info")
    if x:
        try:
            return JSON.parse(x)
        except Exception as err:
            console.warn("Cannot parse JSON auth info: " + str(err))
            return None
    else:
        return None
Beispiel #5
0
 def _load_settings(self):
     x = localStorage.getItem("timetagger_local_settings")
     if x:
         try:
             return JSON.parse(x)
         except Exception as err:
             window.console.warn("Cannot parse local settings JSON: " +
                                 str(err))
             return {}
     else:
         return {}
Beispiel #6
0
    async def _push(self, kind, authtoken):

        # Build url
        url = location.protocol + "//" + location.hostname + ":" + location.port
        url = url.rstrip(":") + "/api/v1/" + kind

        # Take items, only proceed if nonempty
        items = self._to_push[kind]
        if len(items.keys()) == 0:
            return
        self._to_push[kind] = {}

        # Fetch and wait for response
        init = dict(
            method="PUT",
            body=JSON.stringify(items.values()),
            headers={"authtoken": authtoken},
        )
        try:
            res = await window.fetch(url, init)
        except Exception as err:
            res = dict(status=0, statusText=str(err), text=lambda: "")

        # Process response
        if res.status != 200:
            # The server was not able to process the request, maybe the
            # wifi is down or the server is restarting.
            # Put items back, but don't overwrite if the item was updated again.
            for key, item in items.items():
                self._to_push[kind].setdefault(key, item)
            self._set_state(
                "error")  # is usually less bad than the fail's below
            text = await res.text()
            console.warn(res.status + " (" + res.statusText + ") " + text)
            # Also notify the user for 402 errors
            if res.status == 402 and window.canvas:
                window.canvas.notify_once(text)

        else:
            # Success, but it can still mean that some records failed. In this
            # case these records are likely corrupt, so we delete them, and
            # will get the server's version back when we pull.
            d = JSON.parse(await res.text())
            # d.accepted -> list of ok keys
            for key in d.fail:
                self[kind]._drop(key)
            for err in d.errors:
                self._set_state("warning")
                console.warn(f"Server dropped a {kind}: {err}")
Beispiel #7
0
async def renew_webtoken(verbose=True, reset=False):
    """Renew the webtoken. Each webtoken expires after 14 days. But
    while valid, it can be exhcanged for a new one. By doing this while
    the app is active, users won't be logged out unless this device
    does not use the app for 14 days.

    If reset is True, the token seed is reset, causing all issued web
    tokens to become invalid. In other words: all sessions on other
    devices will be logged out.
    """
    # Get current auth info
    auth = get_auth_info()
    if not auth:
        if verbose:
            console.warn("Could not renew token - not logged in")
        return

    # Make request and wait for response
    url = build_api_url("webtoken")
    if reset:
        url += "?reset=1"
    init = dict(method="GET", headers={"authtoken": auth.token})
    res = await fetch(url, init)

    # Handle
    if res.status != 200:
        text = await res.text()
        console.warn("Could not renew token: " + text)
        if res.status == 401 and "revoked" in text:
            # When revoked, we logout to drop local changes.
            # See notes in stores.py where we do the same.
            if "/app/" in location.pathname:
                location.href = "../logout"
            else:
                location.href = "./logout"
        return

    # Are we still logged in. User may have logged out in the mean time.
    auth = get_auth_info()
    if not auth:
        return

    # Apply
    d = JSON.parse(await res.text())
    set_auth_info_from_token(d.token)
    if verbose:
        console.warn("webtoken renewed")
Beispiel #8
0
    def __init__(self, app_name, id, ws_url=None):
        self.app = None  # the root component (can be a PyComponent)
        self.app_name = app_name
        self.id = id
        self.status = 1
        self.ws_url = ws_url
        self._component_counter = 0
        self._disposed_ob = {'_disposed': True}

        # Maybe this is JLab
        if not self.id:
            jconfig = window.document.getElementById('jupyter-config-data')
            if jconfig:
                try:
                    config = JSON.parse(jconfig.innerText)
                    self.id = config.flexx_session_id
                    self.app_name = config.flexx_app_name
                except Exception as err:
                    print(err)

        # Init internal variables
        self._init_time = time()
        self._pending_commands = []  # to pend raw commands during init
        self._asset_count = 0
        self._ws = None
        self.last_msg = None
        # self.classes = {}
        self.instances = {}
        self.instances_to_check_size = {}

        if not window.flexx.is_exported:
            self.init_socket()

        # Initiate service to track resize
        # Note that only toplevel widgets are tracked, and only once per sec
        window.addEventListener('resize', self._check_size_of_objects, False)
        window.setInterval(self._check_size_of_objects, 1000)
Beispiel #9
0
    def __init__(self, app_name, id, ws_url=None):
        self.app = None  # the root component (can be a PyComponent)
        self.app_name = app_name
        self.id = id
        self.status = 1
        self.ws_url = ws_url
        self._component_counter = 0
        self._disposed_ob = {'_disposed': True}

        # Maybe this is JLab
        if not self.id:
            jconfig = window.document.getElementById('jupyter-config-data')
            if jconfig:
                try:
                    config = JSON.parse(jconfig.innerText)
                    self.id = config.flexx_session_id
                    self.app_name = config.flexx_app_name
                except Exception as err:
                    print(err)

        # Init internal variables
        self._init_time = time()
        self._pending_commands = []  # to pend raw commands during init
        self._asset_count = 0
        self._ws = None
        self.last_msg = None
        # self.classes = {}
        self.instances = {}
        self.instances_to_check_size = {}

        if not window.flexx.is_exported:
            self.init_socket()

        # Initiate service to track resize
        # Note that only toplevel widgets are tracked, and only once per sec
        window.addEventListener('resize', self._check_size_of_objects, False)
        window.setInterval(self._check_size_of_objects, 1000)