From be48e13c716d2f2424f5251488ae5f9807ebebb9 Mon Sep 17 00:00:00 2001 From: Elijah Ahianyo Date: Wed, 28 Jun 2023 01:04:29 +0000 Subject: [PATCH] Remove cookies (#1223) --- reflex/.templates/web/utils/state.js | 10 +++++++-- reflex/__init__.py | 1 + reflex/event.py | 26 +++++++++++++++++++---- reflex/state.py | 28 ++++++++++++++++--------- tests/conftest.py | 5 ++++- tests/test_event.py | 31 ++++++++++++++++++++++++++++ tests/test_state.py | 8 ++++++- 7 files changed, 91 insertions(+), 18 deletions(-) diff --git a/reflex/.templates/web/utils/state.js b/reflex/.templates/web/utils/state.js index 51e10a4d2..12dae7130 100644 --- a/reflex/.templates/web/utils/state.js +++ b/reflex/.templates/web/utils/state.js @@ -17,6 +17,9 @@ let token; // Key for the token in the session storage. const TOKEN_KEY = "token"; +// create cookie instance +const cookies = new Cookies(); + // Dictionary holding component references. export const refs = {}; @@ -114,9 +117,12 @@ export const applyEvent = async (event, router, socket) => { } if (event.name == "_set_cookie") { - const cookies = new Cookies(); cookies.set(event.payload.key, event.payload.value); - localStorage.setItem(event.payload.key, event.payload.value); + return false; + } + + if (event.name == "_remove_cookie") { + cookies.remove(event.payload.key, event.payload.options) return false; } diff --git a/reflex/__init__.py b/reflex/__init__.py index 1cf994e99..e4e5c87dd 100644 --- a/reflex/__init__.py +++ b/reflex/__init__.py @@ -23,6 +23,7 @@ from .event import EventChain as EventChain from .event import FileUpload as upload_files from .event import console_log as console_log from .event import redirect as redirect +from .event import remove_cookie as remove_cookie from .event import set_clipboard as set_clipboard from .event import set_cookie as set_cookie from .event import set_focus as set_focus diff --git a/reflex/event.py b/reflex/event.py index 59cd2d026..16a3d1933 100644 --- a/reflex/event.py +++ b/reflex/event.py @@ -240,8 +240,8 @@ def set_cookie(key: str, value: str) -> EventSpec: """Set a cookie on the frontend. Args: - key (str): The key identifying the cookie. - value (str): The value contained in the cookie. + key: The key identifying the cookie. + value: The value contained in the cookie. Returns: EventSpec: An event to set a cookie. @@ -254,12 +254,30 @@ def set_cookie(key: str, value: str) -> EventSpec: ) +def remove_cookie(key: str, options: Dict[str, Any] = {}) -> EventSpec: # noqa: B006 + """Remove a cookie on the frontend. + + Args: + key: The key identifying the cookie to be removed. + options: Support all the cookie options from RFC 6265 + + Returns: + EventSpec: An event to remove a cookie. + """ + return server_side( + "_remove_cookie", + get_fn_signature(remove_cookie), + key=key, + options=options, + ) + + def set_local_storage(key: str, value: str) -> EventSpec: """Set a value in the local storage on the frontend. Args: - key (str): The key identifying the variable in the local storage. - value (str): The value contained in the local storage. + key: The key identifying the variable in the local storage. + value: The value contained in the local storage. Returns: EventSpec: An event to set a key-value in local storage. diff --git a/reflex/state.py b/reflex/state.py index 9ec299ee9..cc66dd088 100644 --- a/reflex/state.py +++ b/reflex/state.py @@ -5,7 +5,9 @@ import asyncio import copy import functools import inspect +import json import traceback +import urllib.parse from abc import ABC from collections import defaultdict from typing import ( @@ -351,7 +353,7 @@ class State(Base, ABC, extra=pydantic.Extra.allow): """Initialize a variable. Args: - prop (BaseVar): The variable to initialize + prop: The variable to initialize Raises: TypeError: if the variable has an incorrect type @@ -496,15 +498,21 @@ class State(Base, ABC, extra=pydantic.Extra.allow): Returns: The dict of cookies. """ - headers = self.get_headers().get(constants.RouteVar.COOKIE) - return ( - { - pair[0].strip(): pair[1].strip() - for pair in (item.split("=") for item in headers.split(";")) - } - if headers - else {} - ) + cookie_dict = {} + cookies = self.get_headers().get(constants.RouteVar.COOKIE, "").split(";") + + cookie_pairs = [cookie.split("=") for cookie in cookies if cookie] + + for pair in cookie_pairs: + key, value = pair[0].strip(), urllib.parse.unquote(pair[1].strip()) + try: + # cast non-string values to the actual types. + value = json.loads(value) + except json.JSONDecodeError: + pass + finally: + cookie_dict[key] = value + return cookie_dict @classmethod def setup_dynamic_args(cls, args: dict[str, str]): diff --git a/tests/conftest.py b/tests/conftest.py index 0cf5f97dc..6c18cbe48 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -452,7 +452,10 @@ def router_data_headers() -> Dict[str, str]: "sec-websocket-version": "13", "accept-encoding": "gzip, deflate, br", "accept-language": "en-US,en;q=0.9", - "cookie": "csrftoken=mocktoken; name=reflex", + "cookie": "csrftoken=mocktoken; " + "name=reflex;" + " list_cookies=%5B%22some%22%2C%20%22random%22%2C%20%22cookies%22%5D;" + " dict_cookies=%7B%22name%22%3A%20%22reflex%22%7D; val=true", "sec-websocket-key": "mock-websocket-key", "sec-websocket-extensions": "permessage-deflate; client_max_window_bits", } diff --git a/tests/test_event.py b/tests/test_event.py index 34a84ecf6..517c0b3a4 100644 --- a/tests/test_event.py +++ b/tests/test_event.py @@ -1,3 +1,5 @@ +import json + import pytest from reflex import event @@ -160,6 +162,35 @@ def test_set_cookie(): ) +def test_remove_cookie(): + """Test the event remove_cookie.""" + spec = event.remove_cookie("testkey") + assert isinstance(spec, EventSpec) + assert spec.handler.fn.__qualname__ == "_remove_cookie" + assert spec.args == (("key", "testkey"), ("options", {})) + assert ( + format.format_event(spec) == 'E("_remove_cookie", {key:"testkey",options:{}})' + ) + + +def test_remove_cookie_with_options(): + """Test the event remove_cookie with options.""" + options = { + "path": "/", + "domain": "example.com", + "secure": True, + "sameSite": "strict", + } + spec = event.remove_cookie("testkey", options) + assert isinstance(spec, EventSpec) + assert spec.handler.fn.__qualname__ == "_remove_cookie" + assert spec.args == (("key", "testkey"), ("options", options)) + assert ( + format.format_event(spec) + == f'E("_remove_cookie", {{key:"testkey",options:{json.dumps(options)}}})' + ) + + def test_set_local_storage(): """Test the event set_local_storage.""" spec = event.set_local_storage("testkey", "testvalue") diff --git a/tests/test_state.py b/tests/test_state.py index a6e743e16..cb12f62c0 100644 --- a/tests/test_state.py +++ b/tests/test_state.py @@ -737,7 +737,13 @@ def test_get_cookies(test_state, mocker, router_data): """ mocker.patch.object(test_state, "router_data", router_data) - assert test_state.get_cookies() == {"csrftoken": "mocktoken", "name": "reflex"} + assert test_state.get_cookies() == { + "csrftoken": "mocktoken", + "name": "reflex", + "list_cookies": ["some", "random", "cookies"], + "dict_cookies": {"name": "reflex"}, + "val": True, + } def test_get_current_page(test_state):