From 557ec98e19393284f27f7b8d54ea229da3dda4b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sigve=20R=C3=B8kenes?= Date: Fri, 15 Sep 2023 18:06:31 +0200 Subject: [PATCH] Impersonate works nicely woopy --- backend/root/custom_classes/middlewares.py | 13 +++++- backend/samfundet/views.py | 11 +++-- frontend/package.json | 1 + frontend/src/Components/Navbar/Navbar.tsx | 24 ++++++++++- frontend/src/Pages/AdminPage/AdminPage.tsx | 9 +++- .../ImpersonateUserAdminPage.tsx | 9 +--- frontend/src/api.ts | 4 +- frontend/yarn.lock | 41 ++++++++++++++++++- 8 files changed, 92 insertions(+), 20 deletions(-) diff --git a/backend/root/custom_classes/middlewares.py b/backend/root/custom_classes/middlewares.py index 206c114db..03d656fca 100644 --- a/backend/root/custom_classes/middlewares.py +++ b/backend/root/custom_classes/middlewares.py @@ -1,6 +1,7 @@ import logging import secrets from contextvars import ContextVar +from tokenize import Token from django.http import HttpRequest, HttpResponse @@ -56,14 +57,22 @@ def __call__(self, request: HttpRequest) -> HttpResponse: impersonate = request.get_signed_cookie('impersonated_user_id', default=None) if impersonate is not None: from samfundet.models import User + from django.middleware.csrf import get_token request.user = User.objects.get(id=int(impersonate)) - print("EYOO DUDE YOURE NOT YOURSELF") + request._force_auth_user = request.user + request._force_auth_token = get_token(request.user) + print(f"EYOO DUDE YOURE NOT YOURSELF '{request.user.username}'") except: pass response = self.get_response(request) if hasattr(response, 'requested_impersonate_user'): - response.set_signed_cookie('impersonated_user_id', request.user.id) + impersonate_user_id = response.requested_impersonate_user + if impersonate_user_id is not None: + response.set_signed_cookie('impersonated_user_id', impersonate_user_id) + print(f"Now impersonating {impersonate_user_id}") + else: + response.delete_cookie('impersonated_user_id') return response diff --git a/backend/samfundet/views.py b/backend/samfundet/views.py index 2a38b8069..9fe66691b 100644 --- a/backend/samfundet/views.py +++ b/backend/samfundet/views.py @@ -292,11 +292,13 @@ def post(self, request: Request) -> Response: login(request=request, user=user) new_csrf_token = get_token(request=request) - return Response( + response = Response( status=status.HTTP_202_ACCEPTED, data=new_csrf_token, headers={XCSRFTOKEN: new_csrf_token}, ) + response.requested_impersonate_user = None + return response @method_decorator(csrf_protect, 'dispatch') @@ -309,7 +311,9 @@ def post(self, request: Request) -> Response: return Response(status=status.HTTP_400_BAD_REQUEST) logout(request) - return Response(status=status.HTTP_200_OK) + response = Response(status=status.HTTP_200_OK) + response.requested_impersonate_user = None + return response @method_decorator(csrf_protect, 'dispatch') @@ -348,9 +352,8 @@ class ImpersonateView(APIView): permission_classes = [IsAuthenticated] # TODO authentication check def post(self, request: Request) -> Response: - user_id = int(request.data.get('user_id')) if hasattr(request, 'user_id') else None response = Response(status=200) - response.requested_impersonate_user = user_id + response.requested_impersonate_user = request.data.get('user_id', None) return response diff --git a/frontend/package.json b/frontend/package.json index e1400738d..181e509f2 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -39,6 +39,7 @@ "path-to-regexp": "^6.2.1", "postcss": "^8.4.18", "react": "^18.2.0", + "react-cookie": "^6.1.1", "react-dom": "^18.2.0", "react-helmet-async": "^1.3.0", "react-loading-skeleton": "^3.1.0", diff --git a/frontend/src/Components/Navbar/Navbar.tsx b/frontend/src/Components/Navbar/Navbar.tsx index 2a6da5c07..98fbab77e 100644 --- a/frontend/src/Components/Navbar/Navbar.tsx +++ b/frontend/src/Components/Navbar/Navbar.tsx @@ -8,12 +8,13 @@ import { Button, Link, NotificationBadge, ThemeSwitch } from '~/Components'; import { NavbarItem } from '~/Components/Navbar/components'; import { HamburgerMenu } from '~/Components/Navbar/components/HamburgerMenu'; import { useGlobalContext } from '~/GlobalContextProvider'; -import { logout } from '~/api'; +import { impersonateUser, logout } from '~/api'; import { englishFlag, logoBlack, logoWhite, norwegianFlag } from '~/assets'; import { useDesktop, useIsDarkTheme, useScrollY } from '~/hooks'; import { STATUS } from '~/http_status_codes'; import { KEY, LANGUAGES } from '~/i18n/constants'; import { ROUTES } from '~/routes'; +import { useCookies } from 'react-cookie'; import styles from './Navbar.module.scss'; const scrollDistanceForOpaque = 30; @@ -25,6 +26,7 @@ export function Navbar() { const { user, setUser } = useAuthContext(); const navigate = useNavigate(); const isDesktop = useDesktop(); + const [cookies, setCookie, removeCookie] = useCookies(); // Each NavbarItem can have a dropdown menu. // We want only one of them to be extended at any time, therefore this parent component @@ -136,12 +138,30 @@ export function Navbar() { ); + const isImpersonate = cookies.hasOwnProperty('impersonated_user_id'); const userDropdownLinks = ( <> {t(KEY.control_panel_title)} + {isImpersonate && ( + + )} + ); diff --git a/frontend/src/PagesAdmin/ImpersonateUserAdminPage/ImpersonateUserAdminPage.tsx b/frontend/src/PagesAdmin/ImpersonateUserAdminPage/ImpersonateUserAdminPage.tsx index d35ca9ddb..1857a1a3e 100644 --- a/frontend/src/PagesAdmin/ImpersonateUserAdminPage/ImpersonateUserAdminPage.tsx +++ b/frontend/src/PagesAdmin/ImpersonateUserAdminPage/ImpersonateUserAdminPage.tsx @@ -44,12 +44,7 @@ export function ImpersonateUserAdminPage() { function impersonate(user: UserDto) { impersonateUser(user) .then((ok) => { - if (ok) { - getUser() - .then((user) => auth.setUser(user)) - .catch(console.error); - alert('nice, middleware is good, TODO proper handling in frontend'); - } + window.location.reload(); }) .catch((err) => { alert(JSON.stringify(err)); @@ -72,7 +67,7 @@ export function ImpersonateUserAdminPage() { inputClassName={styles.inputClass} placeholder={'Search...'} onChange={setQuery} />
{displayUsers.map((u) => ( - diff --git a/frontend/src/api.ts b/frontend/src/api.ts index 9cd8c7a21..7b57b5322 100644 --- a/frontend/src/api.ts +++ b/frontend/src/api.ts @@ -78,9 +78,9 @@ export async function getUser(): Promise { return response.data; } -export async function impersonateUser(user: UserDto): Promise { +export async function impersonateUser(user?: UserDto): Promise { const url = BACKEND_DOMAIN + ROUTES.backend.samfundet__impersonate; - const response = await axios.post(url, { user_id: user.id }, { withCredentials: true }); + const response = await axios.post(url, { user_id: user?.id }, { withCredentials: true }); return response.status == 200; } diff --git a/frontend/yarn.lock b/frontend/yarn.lock index e2078734b..541d00688 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -3501,6 +3501,11 @@ dependencies: "@types/node" "*" +"@types/cookie@^0.5.1": + version "0.5.2" + resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.5.2.tgz#9bf9d62c838c85a07c92fdf2334c2c14fd9c59a9" + integrity sha512-DBpRoJGKJZn7RY92dPrgoMew8xCWc2P71beqsjyhEI/Ds9mOyVmBwtekyfhpwFIVt1WrxTonFifiOZ62V8CnNA== + "@types/debug@^4.0.0": version "4.1.7" resolved "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz" @@ -3588,6 +3593,14 @@ dependencies: "@types/unist" "*" +"@types/hoist-non-react-statics@^3.3.1": + version "3.3.1" + resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f" + integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA== + dependencies: + "@types/react" "*" + hoist-non-react-statics "^3.3.0" + "@types/html-minifier-terser@^5.0.0": version "5.1.2" resolved "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-5.1.2.tgz" @@ -6077,7 +6090,7 @@ cookie-signature@1.0.6: resolved "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz" integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== -cookie@0.5.0: +cookie@0.5.0, cookie@^0.5.0: version "0.5.0" resolved "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz" integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== @@ -8945,6 +8958,13 @@ hmac-drbg@^1.0.1: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" +hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" + integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== + dependencies: + react-is "^16.7.0" + hoopy@^0.1.4: version "0.1.4" resolved "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz" @@ -13525,6 +13545,15 @@ react-app-polyfill@^3.0.0: regenerator-runtime "^0.13.9" whatwg-fetch "^3.6.2" +react-cookie@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/react-cookie/-/react-cookie-6.1.1.tgz#af61c82ea7f41119d3cf8fd3534f8b6c0408f350" + integrity sha512-fuFRpf8LH6SfmVMowDUIRywJF5jAUDUWrm0EI5VdXfTl5bPcJ7B0zWbuYpT0Tvikx7Gs18MlvAT+P+744dUz2g== + dependencies: + "@types/hoist-non-react-statics" "^3.3.1" + hoist-non-react-statics "^3.3.2" + universal-cookie "^6.0.0" + react-dev-utils@^12.0.1: version "12.0.1" resolved "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz" @@ -13652,7 +13681,7 @@ react-is@17.0.2, react-is@^17.0.1: resolved "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== -react-is@^16.13.1: +react-is@^16.13.1, react-is@^16.7.0: version "16.13.1" resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== @@ -16183,6 +16212,14 @@ unist-util-visit@^4.0.0: unist-util-is "^5.0.0" unist-util-visit-parents "^5.1.1" +universal-cookie@^6.0.0: + version "6.1.1" + resolved "https://registry.yarnpkg.com/universal-cookie/-/universal-cookie-6.1.1.tgz#2d619e21804c93b39ff0c77fe47dafd7a492346d" + integrity sha512-33S9x3CpdUnnjwTNs2Fgc41WGve2tdLtvaK2kPSbZRc5pGpz2vQFbRWMxlATsxNNe/Cy8SzmnmbuBM85jpZPtA== + dependencies: + "@types/cookie" "^0.5.1" + cookie "^0.5.0" + universalify@^0.2.0: version "0.2.0" resolved "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz"