diff --git a/backend/app_tests/api/test_utils.py b/backend/app_tests/api/test_utils.py index f97aba5d5..43ac1b7ca 100644 --- a/backend/app_tests/api/test_utils.py +++ b/backend/app_tests/api/test_utils.py @@ -1,3 +1,4 @@ +from knox.auth import AuthToken import pytest import json import re @@ -28,20 +29,32 @@ def get_object_urn(object_name: str, resolved: bool = True): return f"{reverse(LIBRARIES_ENDPOINT)}{urn}/" if resolved else eval(urn) @pytest.mark.django_db - def get_test_client_and_folder(authenticated_client, role: str, test_folder_name: str, assigned_folder_name: str = "test"): + def get_test_client_and_folder( + authenticated_client, + role: str, + test_folder_name: str, + assigned_folder_name: str = "test", + ): """Get an authenticated client with a specific role and the folder associated to the role""" from iam.models import Folder, User, UserGroup EndpointTestsQueries.Auth.create_object( - authenticated_client, "Folders", Folder, {"name": assigned_folder_name}, - item_search_field="name" + authenticated_client, + "Folders", + Folder, + {"name": assigned_folder_name}, + item_search_field="name", ) assigned_folder = test_folder = Folder.objects.get(name=assigned_folder_name) if test_folder_name != assigned_folder_name: EndpointTestsQueries.Auth.create_object( - authenticated_client, "Folders", Folder, {"name": test_folder_name}, base_count=1, - item_search_field="name" + authenticated_client, + "Folders", + Folder, + {"name": test_folder_name}, + base_count=1, + item_search_field="name", ) test_folder = Folder.objects.get(name=test_folder_name) @@ -51,25 +64,39 @@ def get_test_client_and_folder(authenticated_client, role: str, test_folder_name folder=Folder.objects.get(name=GROUPS_PERMISSIONS[role]["folder"]), ).user_set.add(user) client = APIClient() - client.force_login(user) + _auth_token = AuthToken.objects.create(user=user) + auth_token = _auth_token[1] + client.credentials(HTTP_AUTHORIZATION=f"Token {auth_token}") return client, test_folder, assigned_folder def expected_request_response( - action: str, object: str, scope: str, user_group: str, expected_status: int = status.HTTP_200_OK + action: str, + object: str, + scope: str, + user_group: str, + expected_status: int = status.HTTP_200_OK, ): """Get the expected request response for a specific action on an object for a specific user group""" perm_name = f"{action}_{get_singular_name(object).lower().replace(' ', '')}" if perm_name in GROUPS_PERMISSIONS[user_group]["perms"]: # User has permission to perform the action - if (GROUPS_PERMISSIONS[user_group]["folder"] == "Global") or (scope == GROUPS_PERMISSIONS[user_group]["folder"]) or (scope == "Global"): + if ( + (GROUPS_PERMISSIONS[user_group]["folder"] == "Global") + or (scope == GROUPS_PERMISSIONS[user_group]["folder"]) + or (scope == "Global") + ): # User has access to the domain return False, expected_status, "ok" else: return False, expected_status, "outside_scope" else: # User has not permission to perform the action - if (GROUPS_PERMISSIONS[user_group]["folder"] == "Global") or (scope == GROUPS_PERMISSIONS[user_group]["folder"]) or (scope == "Global"): + if ( + (GROUPS_PERMISSIONS[user_group]["folder"] == "Global") + or (scope == GROUPS_PERMISSIONS[user_group]["folder"]) + or (scope == "Global") + ): # User has access to the domain return True, status.HTTP_403_FORBIDDEN, "permission_denied" else: @@ -101,7 +128,7 @@ def get_object( response = client.get(url) assert ( - response.status_code == status.HTTP_403_FORBIDDEN + response.status_code == status.HTTP_401_UNAUTHORIZED ), f"{verbose_name} are accessible without authentication" assert response.json() == { "detail": "Authentication credentials were not provided." @@ -133,7 +160,7 @@ def get_object( response = client.get(url) assert ( - response.status_code == status.HTTP_403_FORBIDDEN + response.status_code == status.HTTP_401_UNAUTHORIZED ), f"{verbose_name} are accessible without authentication" assert response.json() == { "detail": "Authentication credentials were not provided." @@ -159,7 +186,7 @@ def create_object( # Asserts that the user was not created assert ( - response.status_code == status.HTTP_403_FORBIDDEN + response.status_code == status.HTTP_401_UNAUTHORIZED ), f"{verbose_name} can be created without authentication" assert response.json() == { "detail": "Authentication credentials were not provided." @@ -215,7 +242,7 @@ def update_object( # Asserts that the user was not updated assert ( - response.status_code == status.HTTP_403_FORBIDDEN + response.status_code == status.HTTP_401_UNAUTHORIZED ), f"{verbose_name} can be updated without authentication" assert response.json() == { "detail": "Authentication credentials were not provided." @@ -267,7 +294,7 @@ def delete_object( # Asserts that the user was not deleted assert ( - response.status_code == status.HTTP_403_FORBIDDEN + response.status_code == status.HTTP_401_UNAUTHORIZED ), f"{verbose_name} can be deleted without authentication" assert response.json() == { "detail": "Authentication credentials were not provided." @@ -293,7 +320,7 @@ def import_object(client, verbose_name: str, urn: str = None): # Asserts that the object was imported successfully assert ( - response.status_code == status.HTTP_403_FORBIDDEN + response.status_code == status.HTTP_401_UNAUTHORIZED ), f"{verbose_name} can be imported without authentication" assert response.json() == { "detail": "Authentication credentials were not provided." @@ -330,11 +357,13 @@ def get_object( user_perm_fails, user_perm_expected_status, user_perm_reason = None, 0, None if user_group: - scope = scope or str(build_params.get("folder", None)) # if the scope is not provided, try to get it from the build_params + scope = scope or str( + build_params.get("folder", None) + ) # if the scope is not provided, try to get it from the build_params ( user_perm_fails, user_perm_expected_status, - user_perm_reason + user_perm_reason, ) = EndpointTestsUtils.expected_request_response( "view", verbose_name, scope, user_group, expected_status ) @@ -434,7 +463,11 @@ def get_object( response.json()["count"] == base_count + 1 ), f"{verbose_name} are not accessible with authentication" - if not (fails or user_perm_fails) and user_perm_reason != "outside_scope" and len(response.json()["results"]) != 0: + if ( + not (fails or user_perm_fails) + and user_perm_reason != "outside_scope" + and len(response.json()["results"]) != 0 + ): params = {**build_params, **test_params} if len(response.json()["results"]) > 0 and item_search_field: response_item = [ @@ -479,7 +512,7 @@ def get_object_options( ( user_perm_fails, user_perm_expected_status, - _ + _, ) = EndpointTestsUtils.expected_request_response( "view", verbose_name, scope, user_group, expected_status ) @@ -542,11 +575,13 @@ def create_object( user_perm_fails, user_perm_expected_status, user_perm_reason = None, 0, None if user_group: - scope = scope or str(build_params.get("folder", None)) # if the scope is not provided, try to get it from the build_params + scope = scope or str( + build_params.get("folder", None) + ) # if the scope is not provided, try to get it from the build_params ( user_perm_fails, user_perm_expected_status, - user_perm_reason + user_perm_reason, ) = EndpointTestsUtils.expected_request_response( "add", verbose_name, scope, user_group, expected_status ) @@ -589,8 +624,9 @@ def create_object( if not (fails or user_perm_fails): if user_perm_reason == "outside_scope": assert ( - response.json()['folder'] == 'You do not have permission to create objects in this folder' - ), f"{verbose_name} can be created outside the domain" + response.json()["folder"] + == "You do not have permission to create objects in this folder" + ), f"{verbose_name} can be created outside the domain" else: for key, value in build_params.items(): if key == "attachment": @@ -605,9 +641,9 @@ def create_object( ), f"{verbose_name} {key.replace('_', ' ')} returned by the API after object creation don't match the provided {key.replace('_', ' ')}" # Checks that the object was created in the database - assert ( - object.objects.filter(id=response.json()["id"]).exists() - ), f"{verbose_name} created with the API are not saved in the database" + assert object.objects.filter( + id=response.json()["id"] + ).exists(), f"{verbose_name} created with the API are not saved in the database" # Uses the API endpoint to assert that the created object is accessible response = authenticated_client.get(url) @@ -670,11 +706,13 @@ def update_object( user_perm_fails, user_perm_expected_status, user_perm_reason = None, 0, None if user_group: - scope = scope or str(build_params.get("folder", None)) # if the scope is not provided, try to get it from the build_params + scope = scope or str( + build_params.get("folder", None) + ) # if the scope is not provided, try to get it from the build_params ( user_perm_fails, user_perm_expected_status, - user_perm_reason + user_perm_reason, ) = EndpointTestsUtils.expected_request_response( "change", verbose_name, scope, user_group, expected_status ) @@ -705,14 +743,18 @@ def update_object( response = authenticated_client.get(url) - view_perms = EndpointTestsUtils.expected_request_response("view", verbose_name, scope, user_group) + view_perms = EndpointTestsUtils.expected_request_response( + "view", verbose_name, scope, user_group + ) if not user_group or view_perms[:2] == (False, status.HTTP_200_OK): if view_perms[2] == "outside_scope": assert ( response.status_code == status.HTTP_404_NOT_FOUND ), f"{verbose_name} object detail can be accessed outside the domain" else: - if (verbose_name is not "Users"): # Users don't have permission to view users details + if ( + verbose_name is not "Users" + ): # Users don't have permission to view users details assert ( response.status_code == status.HTTP_200_OK ), f"{verbose_name} object detail can not be accessed with permission" @@ -724,7 +766,8 @@ def update_object( if not (fails or user_perm_fails): if view_perms[2] == "outside_scope": assert ( - response.json() == {'detail': f'No {object.__name__} matches the given query.'} + response.json() + == {"detail": f"No {object.__name__} matches the given query."} ), f"{verbose_name} object detail can be accessed outside the domain" else: for key, value in {**build_params, **test_build_params}.items(): @@ -801,11 +844,13 @@ def delete_object( user_perm_fails, user_perm_expected_status, user_perm_reason = None, 0, None if user_group: - scope = scope or str(build_params.get("folder", None)) # if the scope is not provided, try to get it from the build_params + scope = scope or str( + build_params.get("folder", None) + ) # if the scope is not provided, try to get it from the build_params ( user_perm_fails, user_perm_expected_status, - user_perm_reason + user_perm_reason, ) = EndpointTestsUtils.expected_request_response( "delete", verbose_name, scope, user_group, expected_status ) @@ -838,14 +883,18 @@ def delete_object( # Asserts that the objects exists response = authenticated_client.get(url) - view_perms = EndpointTestsUtils.expected_request_response("view", verbose_name, scope, user_group) + view_perms = EndpointTestsUtils.expected_request_response( + "view", verbose_name, scope, user_group + ) if not user_group or view_perms[:2] == (False, status.HTTP_200_OK): if view_perms[2] == "outside_scope": assert ( response.status_code == status.HTTP_404_NOT_FOUND ), f"{verbose_name} object detail can be accessed outside the domain" else: - if (verbose_name is not "Users"): # Users don't have permission to view users details + if ( + verbose_name is not "Users" + ): # Users don't have permission to view users details assert ( response.status_code == status.HTTP_200_OK ), f"{verbose_name} object detail can not be accessed with permission" @@ -908,7 +957,7 @@ def import_object( ( user_perm_fails, user_perm_expected_status, - user_perm_reason + user_perm_reason, ) = EndpointTestsUtils.expected_request_response( "add", "library", scope, user_group, expected_status ) diff --git a/backend/app_tests/conftest.py b/backend/app_tests/conftest.py index a4048f8fc..d54672292 100644 --- a/backend/app_tests/conftest.py +++ b/backend/app_tests/conftest.py @@ -4,6 +4,7 @@ from test_vars import GROUPS_PERMISSIONS from iam.models import User, UserGroup from core.apps import startup +from knox.auth import AuthToken class Test(dict): @@ -30,14 +31,24 @@ def authenticated_client(app_config): admin = User.objects.create_superuser("admin@tests.com") UserGroup.objects.get(name="BI-UG-ADM").user_set.add(admin) client = APIClient() - client.force_login(admin) + _auth_token = AuthToken.objects.create(user=admin) + auth_token = _auth_token[1] + client.credentials(HTTP_AUTHORIZATION=f"Token {auth_token}") return client @pytest.fixture( - params=[(role, folder) for role in GROUPS_PERMISSIONS.keys() for folder in ["test", "test_outside_domain"]], - ids=[GROUPS_PERMISSIONS[key]["name"]+folder_name for key in GROUPS_PERMISSIONS.keys() for folder_name in ["", "_outside_domain"]] - ) + params=[ + (role, folder) + for role in GROUPS_PERMISSIONS.keys() + for folder in ["test", "test_outside_domain"] + ], + ids=[ + GROUPS_PERMISSIONS[key]["name"] + folder_name + for key in GROUPS_PERMISSIONS.keys() + for folder_name in ["", "_outside_domain"] + ], +) def test(authenticated_client, request) -> Test: """Get the elements used by the tests such as client and associated folder""" client, folder, assigned_folder = EndpointTestsUtils.get_test_client_and_folder( diff --git a/backend/ciso_assistant/settings.py b/backend/ciso_assistant/settings.py index caeb7095c..c682a2237 100644 --- a/backend/ciso_assistant/settings.py +++ b/backend/ciso_assistant/settings.py @@ -12,8 +12,7 @@ from pathlib import Path import os from dotenv import load_dotenv -import subprocess -import json +from datetime import timedelta import logging.config import structlog from django.core.management.utils import get_random_secret_key @@ -30,12 +29,12 @@ CISO_ASSISTANT_URL = os.environ.get("CISO_ASSISTANT_URL", "http://localhost:5173") + def set_ciso_assistant_url(_, __, event_dict): event_dict["ciso_assistant_url"] = CISO_ASSISTANT_URL return event_dict - LOGGING = { "version": 1, "disable_existing_loggers": False, @@ -112,7 +111,6 @@ def set_ciso_assistant_url(_, __, event_dict): PAGINATE_BY = os.environ.get("PAGINATE_BY", default=500) - # Application definition INSTALLED_APPS = [ @@ -130,6 +128,7 @@ def set_ciso_assistant_url(_, __, event_dict): "library", "serdes", "rest_framework", + "knox", "drf_spectacular", ] @@ -149,14 +148,12 @@ def set_ciso_assistant_url(_, __, event_dict): LOGIN_REDIRECT_URL = "home" LOGOUT_REDIRECT_URL = "login" -SESSION_COOKIE_AGE = int( - os.environ.get("SESSION_COOKIE_AGE", default=60 * 15) +AUTH_TOKEN_TTL = int( + os.environ.get("AUTH_TOKEN_TTL", default=60 * 15) ) # defaults to 15 minutes -# prevents session from expiring when user is active -SESSION_SAVE_EVERY_REQUEST = os.environ.get("SESSION_SAVE_EVERY_REQUEST", default=True) -SESSION_EXPIRE_AT_BROWSER_CLOSE = os.environ.get( - "SESSION_EXPIRE_AT_BROWSER_CLOSE", default=True -) +AUTH_TOKEN_AUTO_REFRESH = ( + os.environ.get("AUTH_TOKEN_AUTO_REFRESH", default="True") == "True" +) # prevents token from expiring while user is active CISO_ASSISTANT_SUPERUSER_EMAIL = os.environ.get("CISO_ASSISTANT_SUPERUSER_EMAIL") DEFAULT_FROM_EMAIL = os.environ.get("DEFAULT_FROM_EMAIL") @@ -180,7 +177,7 @@ def set_ciso_assistant_url(_, __, event_dict): "rest_framework.renderers.JSONRenderer", ], "DEFAULT_AUTHENTICATION_CLASSES": [ - "rest_framework.authentication.SessionAuthentication", + "knox.auth.TokenAuthentication", ], "DEFAULT_PERMISSION_CLASSES": [ "rest_framework.permissions.IsAuthenticated", @@ -192,6 +189,15 @@ def set_ciso_assistant_url(_, __, event_dict): "DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema", } +REST_KNOX = { + "SECURE_HASH_ALGORITHM": "cryptography.hazmat.primitives.hashes.SHA512", + "AUTH_TOKEN_CHARACTER_LENGTH": 64, + "TOKEN_TTL": timedelta(seconds=AUTH_TOKEN_TTL), + "TOKEN_LIMIT_PER_USER": None, + "AUTO_REFRESH": AUTH_TOKEN_AUTO_REFRESH, + "MIN_REFRESH_INTERVAL": 60, +} + if DEBUG: REST_FRAMEWORK["DEFAULT_RENDERER_CLASSES"].append( "rest_framework.renderers.BrowsableAPIRenderer" @@ -306,9 +312,9 @@ def set_ciso_assistant_url(_, __, event_dict): ] SPECTACULAR_SETTINGS = { - 'TITLE': 'CISO Assistant API', - 'DESCRIPTION': 'CISO Assistant - API Documentation', - 'VERSION': '1.0.0', - 'SERVE_INCLUDE_SCHEMA': False, + "TITLE": "CISO Assistant API - Experimental", + "DESCRIPTION": "CISO Assistant - API Documentation for automating all your GRC needs", + "VERSION": "0.7.0", + "SERVE_INCLUDE_SCHEMA": False, # OTHER SETTINGS } diff --git a/backend/ciso_assistant/urls.py b/backend/ciso_assistant/urls.py index 62b53e3cf..50e6ce73b 100644 --- a/backend/ciso_assistant/urls.py +++ b/backend/ciso_assistant/urls.py @@ -14,16 +14,26 @@ 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ from django.urls import include, path -from ciso_assistant import settings -from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView, SpectacularSwaggerView - +from drf_spectacular.views import ( + SpectacularAPIView, + SpectacularRedocView, + SpectacularSwaggerView, +) # beware of the order of url patterns, this can change de behavior in case of multiple matches and avoid giving identical paths that could cause conflicts urlpatterns = [ + path("api/schema/", SpectacularAPIView.as_view(), name="schema"), + path( + "api/schema/swagger/", + SpectacularSwaggerView.as_view(url_name="schema"), + name="swagger", + ), + path( + "api/schema/redoc/", + SpectacularRedocView.as_view(url_name="schema"), + name="redoc", + ), path("api/", include("core.urls")), path("serdes/", include("serdes.urls")), path("i18n/", include("django.conf.urls.i18n")), - path('api/schema/', SpectacularAPIView.as_view(), name='schema'), - path('api/schema/swagger/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger'), - path('api/schema/redoc/', SpectacularRedocView.as_view(url_name='schema'), name='redoc'), ] diff --git a/backend/iam/urls.py b/backend/iam/urls.py index 474ef2f2a..37ac4662d 100644 --- a/backend/iam/urls.py +++ b/backend/iam/urls.py @@ -2,11 +2,20 @@ from core.views import FirstConnexionPasswordConfirmView -from .views import * +from .views import ( + LoginView, + ChangePasswordView, + CurrentUserView, + PasswordResetView, + ResetPasswordConfirmView, + SetPasswordView, +) +import knox.views as knox_views urlpatterns = [ - path("login/", LoginView.as_view(), name="login"), - path("logout/", LogoutView.as_view(), name="logout"), + path(r"login/", LoginView.as_view(), name="knox_login"), + path(r"logout/", knox_views.LogoutView.as_view(), name="knox_logout"), + path(r"logoutall/", knox_views.LogoutAllView.as_view(), name="knox_logoutall"), path("current-user/", CurrentUserView.as_view(), name="current-user"), path("change-password/", ChangePasswordView.as_view(), name="change-password"), path("password-reset/", PasswordResetView.as_view(), name="password-reset"), diff --git a/backend/iam/views.py b/backend/iam/views.py index a3e91e4fc..970d31af4 100644 --- a/backend/iam/views.py +++ b/backend/iam/views.py @@ -14,6 +14,8 @@ ) from rest_framework.settings import api_settings from ciso_assistant.settings import EMAIL_HOST, EMAIL_HOST_RESCUE +from rest_framework.authtoken.serializers import AuthTokenSerializer +from knox.views import LoginView as KnoxLoginView from .serializers import ( ChangePasswordSerializer, @@ -31,37 +33,16 @@ User = get_user_model() -class LoginView(views.APIView): - permission_classes = [permissions.AllowAny] +class LoginView(KnoxLoginView): + permission_classes = (permissions.AllowAny,) + serializer_class = LoginSerializer - @method_decorator(ensure_csrf_cookie) - def post(self, request) -> Response: - serializer = LoginSerializer( - data=self.request.data, - context={"request": self.request}, - ) - try: - serializer.is_valid(raise_exception=True) - user = serializer.validated_data["user"] - login(request, user) - logger.info("login succesful", user=user) - except serializers.ValidationError as e: - logger.warning( - "login attempt failed", - error=e, - username=request.data.get("username"), - ) - if isinstance(e.detail, dict): - return Response(data={**e.detail}, status=HTTP_401_UNAUTHORIZED) - else: - return Response( - data={api_settings.NON_FIELD_ERRORS_KEY: [e.detail]}, - status=HTTP_401_UNAUTHORIZED, - ) - - user.first_login = False - user.save() - return Response(None, status=HTTP_202_ACCEPTED) + def post(self, request, format=None): + serializer = AuthTokenSerializer(data=request.data) + serializer.is_valid(raise_exception=True) + user = serializer.validated_data["user"] + login(request, user) + return super(LoginView, self).post(request, format=None) class LogoutView(views.APIView): diff --git a/backend/requirements.txt b/backend/requirements.txt index c3ff8d2aa..217e1fc31 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -17,3 +17,4 @@ django-structlog==8.0.0 structlog==24.1.0 python-dotenv==1.0.1 drf-spectacular==0.27.2 +django-rest-knox==4.2.0 diff --git a/frontend/src/hooks.server.ts b/frontend/src/hooks.server.ts index c61d4c9bc..a9380d8f0 100644 --- a/frontend/src/hooks.server.ts +++ b/frontend/src/hooks.server.ts @@ -22,19 +22,19 @@ async function ensureCsrfToken(event: RequestEvent): Promise { } async function validateUserSession(event: RequestEvent): Promise { - const session = event.cookies.get('sessionid'); - if (!session) return null; + const token = event.cookies.get('token'); + if (!token) return null; const res = await fetch(`${BASE_API_URL}/iam/current-user/`, { credentials: 'include', headers: { 'content-type': 'application/json', - Cookie: `sessionid=${session}` + Authorization: `Token ${token}` } }); if (!res.ok) { - event.cookies.delete('sessionid', { + event.cookies.delete('token', { path: '/' }); redirect(302, `/login?next=${event.url.pathname}`); @@ -61,11 +61,11 @@ export const handleFetch: HandleFetch = async ({ request, fetch, event: { cookie if (request.url.startsWith(BASE_API_URL)) { request.headers.set('Content-Type', 'application/json'); - const sessionid = cookies.get('sessionid'); + const token = cookies.get('token'); const csrfToken = cookies.get('csrftoken'); - if (sessionid) { - request.headers.append('Cookie', `sessionid=${sessionid}`); + if (token) { + request.headers.append('Authorization', `Token ${token}`); } if (unsafeMethods.has(request.method) && csrfToken) { diff --git a/frontend/src/routes/(authentication)/login/+page.server.ts b/frontend/src/routes/(authentication)/login/+page.server.ts index ddabfdddf..1279b5e86 100644 --- a/frontend/src/routes/(authentication)/login/+page.server.ts +++ b/frontend/src/routes/(authentication)/login/+page.server.ts @@ -53,39 +53,15 @@ export const actions: Actions = { return { form }; } - if (res.headers.has('Set-Cookie')) { - const splitted = Object.fromEntries(res.headers) - ['set-cookie'].split(' ') - .filter((string) => string.indexOf('=') >= 0 && string.split('=')[0] === 'sessionid') - .map((string) => string.split('=')[1]); + const data = await res.json(); - if (splitted.length < 1) { - throw fail(500, { - message: - "Failed to create a session, the API returned cookies the 'sessionid' cookie is missing !" - }); - } - - const sessionid = splitted[0]; - cookies.set('sessionid', sessionid, { - httpOnly: true, - sameSite: 'lax', - path: '/', - secure: true - }); + cookies.set('token', data.token, { + httpOnly: true, + sameSite: 'lax', + path: '/', + secure: true + }); - const csrftoken = cookies.get('csrftoken'); - if (csrftoken) { - cookies.set('csrftoken', csrftoken, { - // Setting httpOnly to true for the CSRF token offers no additional security. - // https://docs.djangoproject.com/en/dev/ref/settings/#csrf-cookie-httponly - httpOnly: false, - sameSite: 'lax', - path: '/', - secure: true - }); - } - } redirect(302, url.searchParams.get('next') || '/analytics'); } }; diff --git a/frontend/src/routes/(authentication)/logout/+server.ts b/frontend/src/routes/(authentication)/logout/+server.ts index d23b5fef9..a21213e76 100644 --- a/frontend/src/routes/(authentication)/logout/+server.ts +++ b/frontend/src/routes/(authentication)/logout/+server.ts @@ -22,7 +22,7 @@ export const POST = async ({ fetch, cookies }) => { return fail(400, response.error); } - cookies.delete('sessionid', { path: '/' }); + cookies.delete('token', { path: '/' }); redirect(302, '/login'); };