async def renew_token(self, scope: Scope, info: Info, matches: RouteMatches, content: Content) -> HttpResponse: try: token = header.cookie(scope['headers']).get(self.cookie_name) if not token: # Unauthorised return text_response(401, [], 'Client requires authentication') payload = jwt.decode(token, key=self.secret, options={'verify_exp': False}) user = payload['sub'] issued_at = datetime.utcfromtimestamp(payload['iat']) logger.debug(f'Token renewal request: user={user}, iat={issued_at}') utc_now = datetime.utcnow() authentication_expiry = issued_at + self.login_expiry if utc_now > authentication_expiry: # Unauthenticated logger.debug(f'Token expired for user {user} issued at {issued_at} expired at {authentication_expiry}') return text_response(401, [], 'Authentication expired') if not self.auth_service.is_valid(user): return 403, None, None # Forbidden logger.debug(f'Token renewed for {user}') token = self._make_token(user, utc_now, issued_at=issued_at) logger.debug(f'Sending token {token}') set_cookie = self._make_cookie(token) return 204, [(b'set-cookie', set_cookie)], None except: return 500, None, None
async def login_post(self, scope: Scope, info: Info, matches: RouteMatches, content: Content) -> HttpResponse: try: query = parse_qs(scope['query_string']) redirect = query.get(b'redirect') if not redirect: logger.debug('No redirect') return text_response(404, [], 'No redirect') redirect = redirect[0] text = await text_reader(content) body = parse_qs(text) username = body['username'][0] password = body['password'][0] if not self.auth_service.is_password_for_user(username, password): raise RuntimeError('Invalid username or password') now = datetime.utcnow() token = self._make_token(body['username'], now, issued_at=int(mktime(now.timetuple()))) logger.debug(f'Sending token: {token}') urlparts = urlparse(redirect) if urlparts.scheme is None or len(urlparts.scheme) == 0: raise RuntimeError('The redirect URL has no scheme') set_cookie = self._make_cookie(token) return 302, [(b'set-cookie', set_cookie), (b'location', redirect)], None except: logger.exception('Failed to log in') return 302, [(b'location', header.find(b'referer', scope['headers']))], None
async def login_post( self, scope: Scope, info: Info, matches: RouteMatches, content: Content ) -> HttpResponse: """A login POST request handler :param scope: The ASGI scope :type scope: Scope :param info: The application shared info :type info: Info :param matches: The route matches :type matches: RouteMatches :param content: The ASGI content :type content: Content :return: A login response :rtype: HttpResponse """ try: query = parse_qs(scope['query_string']) redirect = query.get(b'redirect') if not redirect: logger.debug('No redirect') return text_response(response_code.NOT_FOUND, None, 'No redirect') redirect = redirect[0] text = await text_reader(content) body = parse_qs(text) username = body['username'][0] password = body['password'][0] if not await self.authentication_service.is_password_for_user(username, password): raise RuntimeError('Invalid username or password') now = datetime.utcnow() token = self.token_manager.encode(username, now, now) logger.debug('Sending token: %s', token) urlparts = urlparse(redirect) if urlparts.scheme is None or not urlparts.scheme: raise RuntimeError('The redirect URL has no scheme') set_cookie = self.token_manager.make_cookie(token) return response_code.FOUND, [(b'set-cookie', set_cookie), (b'location', redirect)] except: # pylint: disable=bare-except logger.exception('Failed to log in') location = header.find(b'referer', scope['headers']) return response_code.FOUND, [(b'location', location)]
async def who_am_i(self, scope: Scope, info: Info, matches: RouteMatches, content: Content) -> HttpResponse: try: token = header.cookie((scope['headers'])).get(self.cookie_name) if token is None: return text_response(401, [], 'Client requires authentication') payload = jwt.decode(token, key=self.secret) return json_response(200, [], {'username': payload['sub']}) except (jwt.exceptions.ExpiredSignature, PermissionError) as error: logger.debug(f'JWT encoding failed: {error}') return 401, None, None except: logger.exception(f'Failed to re-sign the token') return 500, None, None
async def who_am_i( self, scope: Scope, info: Info, matches: RouteMatches, content: Content ) -> HttpResponse: """Returns the login status of the user :param scope: The ASGI scope :type scope: Scope :param info: The application shared info :type info: Info :param matches: The route matches :type matches: RouteMatches :param content: The ASGI content :type content: Content :return: A whoami response :rtype: HttpResponse """ try: token = self.token_manager.get_token_from_headers(scope['headers']) if token is None: return text_response( response_code.UNAUTHORIZED, None, 'Client requires authentication' ) payload = self.token_manager.decode(token) return json_response(response_code.OK, None, {'username': payload['sub']}) except (jwt.exceptions.ExpiredSignature, PermissionError): logger.exception('JWT encoding failed') return response_code.UNAUTHORIZED except: # pylint: disable=bare-except logger.exception('Failed to re-sign the token') return response_code.INTERNAL_SERVER_ERROR
def text_response(text, status=200, headers={}): headers = [] return _bareasgi.text_response(status, headers, text)
async def renew_token( self, scope: Scope, info: Info, matches: RouteMatches, content: Content ) -> HttpResponse: """Renew the token :param scope: The ASGI scope :type scope: Scope :param info: The application shared info :type info: Info :param matches: The route matches :type matches: RouteMatches :param content: The ASGI content :type content: Content :return: A no-content response with the cookie in the header. :rtype: HttpResponse """ try: token = self.token_manager.get_token_from_headers(scope['headers']) if not token: return text_response( response_code.UNAUTHORIZED, None, 'Client requires authentication' ) payload = self.token_manager.decode(token) user = payload['sub'] issued_at = payload['iat'] logger.debug( 'Token renewal request: user=%s, iat=%s', user, issued_at ) utc_now = datetime.utcnow() authentication_expiry = issued_at + self.login_expiry if utc_now > authentication_expiry: logger.debug( 'Token expired for user %s issued at %s expired at %s', user, issued_at, authentication_expiry ) return text_response(response_code.UNAUTHORIZED, None, 'Authentication expired') if not self.authentication_service.is_valid(user): return response_code.FORBIDDEN, None, None logger.debug('Token renewed for %s', user) token = self.token_manager.encode(user, utc_now, issued_at) logger.debug('Sending token %s', token) set_cookie = self.token_manager.make_cookie(token) return response_code.NO_CONTENT, [(b'set-cookie', set_cookie)], None except: # pylint: disable=bare-except logger.exception('Failed to renew token') return response_code.INTERNAL_SERVER_ERROR, None, None