def device_register(self, path=None): """ EXPECTING A SIGNED REGISTRATION REQUEST RETURN JSON WITH url FOR LOGIN """ now = Date.now() expires = now + parse(self.device.register.session['max-age']) request_body = request.get_data() signed = json2value(request_body.decode("utf8")) command = json2value(base642bytes(signed.data).decode("utf8")) session.public_key = command.public_key rsa_crypto.verify(signed, session.public_key) self.session_manager.create_session(session) session.expires = expires.unix session.state = bytes2base64URL(crypto.bytes(32)) with self.device.db.transaction() as t: t.execute( sql_insert( self.device.table, { "state": session.state, "session_id": session.session_id }, )) body = value2json( Data( session_id=session.session_id, interval="5second", expires=session.expires, url=URL( self.device.home, path=self.device.endpoints.login, query={"state": session.state}, ), )) response = Response(body, headers={"Content-Type": mimetype.JSON}, status=200) response.set_cookie(self.device.register.session.name, session.session_id, path=self.device.login.session.path, domain=self.device.login.session.domain, expires=expires.format(RFC1123), secure=self.device.login.session.secure, httponly=self.device.login.session.httponly) return response
def device_register(self, path=None): """ EXPECTING A SIGNED REGISTRATION REQUEST RETURN JSON WITH url FOR LOGIN """ now = Date.now().unix request_body = request.get_data().strip() signed = json2value(request_body.decode("utf8")) command = json2value(base642bytes(signed.data).decode("utf8")) session.public_key = command.public_key rsa_crypto.verify(signed, session.public_key) self.session_manager.setup_session(session) session.expires = now + parse("10minute").seconds session.state = bytes2base64URL(Random.bytes(32)) with self.device.db.transaction() as t: t.execute( sql_insert( self.device.table, { "state": session.state, "session_id": session.session_id }, )) response = value2json( Data( session_id=session.session_id, interval="5second", expiry=session.expires, url=URL( self.device.home, path=self.device.endpoints.login, query={"state": session.state}, ), )) return Response(response, headers={"Content-Type": "application/json"}, status=200)
def device_status(self, path=None): """ AUTOMATION CAN CALL THIS ENDPOINT TO FIND OUT THE LOGIN STATUS RESPOND WITH {"ok":true} WHEN USER HAS LOGGED IN, AND user IS ASSOCIATED WITH SESSION """ now = Date.now().unix session_id = request.cookies.get(self.device.register.session.name) if not session_id: return Response('{"try_again":false, "status":"no session id"}', status=401) device_session = self.session_manager.get_session(session_id) request_body = request.get_data() signed = json2value(request_body.decode("utf8")) command = rsa_crypto.verify(signed, device_session.public_key) time_sent = parse(command.timestamp).unix if not (now - LEEWAY <= time_sent < now + LEEWAY): return Response( '{"try_again":false, "status":"timestamp is not recent"}', status=401) if device_session.expires < now: return Response( '{"try_again":false, "status":"session is too old"}', status=401) if device_session.user: device_session.public_key = None return Response('{"try_again":false, "status":"verified"}', status=200) state_info = self.device.db.query( sql_query({ "select": "session_id", "from": self.device.table, "where": { "eq": { "state": device_session.state } }, })) if not state_info.data: return Response( '{"try_again":false, "status":"State has been lost"}', status=401) return Response('{"try_again":true, "status":"still waiting"}', status=200)
def login(self, please_stop=None): """ WILL REGISTER THIS DEVICE, AND SHOW A QR-CODE TO LOGIN WILL POLL THE SERVICE ENDPOINT UNTIL LOGIN IS COMPLETED, OR FAILED :param please_stop: SIGNAL TO STOP EARLY :return: SESSION THAT CAN BE USED TO SEND AUTHENTICATED REQUESTS """ # SEND PUBLIC KEY now = Date.now().unix self.session = requests.Session() signed = rsa_crypto.sign( Data(public_key=self.public_key, timestamp=now), self.private_key) DEBUG and Log.note("register (unsigned)\n{{request|json}}", request=rsa_crypto.verify(signed, self.public_key)) DEBUG and Log.note("register (signed)\n{{request|json}}", request=signed) try: response = self.session.request( "POST", str(URL(self.config.service) / self.config.endpoints.register), data=value2json(signed)) except Exception as e: raise Log.error("problem registering device", cause=e) device = wrap(response.json()) DEBUG and Log.note("response:\n{{response}}", response=device) device.interval = parse(device.interval).seconds expires = Till(till=parse(device.expiry).unix) cookie = self.session.cookies.get(self.config.cookie.name) if not cookie: Log.error("expecting a session cookie") # SHOW URL AS QR CODE image = text2QRCode(device.url) sys.stdout.write("\n\nLogin using thie URL:\n") sys.stdout.write(device.url + CR) sys.stdout.write(image) while not please_stop and not expires: Log.note("waiting for login...") try: now = Date.now() signed = rsa_crypto.sign(Data(timestamp=now, session=cookie), self.private_key) url = URL(self.config.service) / self.config.endpoints.status DEBUG and Log.note("ping (unsigned) {{url}}\n{{request|json}}", url=url, request=rsa_crypto.verify( signed, self.public_key)) response = self.session.request("POST", url, data=value2json(signed)) ping = wrap(response.json()) DEBUG and Log.note("response\n{{response|json}}", response=ping) if ping.status == "verified": return self.session if not ping.try_again: Log.note("Failed to login {{reason}}", reason=ping.status) return except Exception as e: Log.warning( "problem calling {{url}}", url=URL(self.config.service) / self.config.endpoints.status, cause=e, ) (Till(seconds=device.interval) | please_stop | expires).wait() return self.session