From 219e592620505aca69921697026a3b9b7b68242b Mon Sep 17 00:00:00 2001 From: MarconLP <13001502+MarconLP@users.noreply.github.com> Date: Thu, 16 Nov 2023 21:08:17 +0100 Subject: [PATCH] add route to generate auth token from logged in cloud users --- posthog/api/user.py | 19 ++++++++++ posthog/templates/authorize_and_link.html | 13 +++++++ posthog/urls.py | 46 +++++++++++++++++++++++ 3 files changed, 78 insertions(+) create mode 100644 posthog/templates/authorize_and_link.html diff --git a/posthog/api/user.py b/posthog/api/user.py index 5aea73715bdd0..2b80a37470713 100644 --- a/posthog/api/user.py +++ b/posthog/api/user.py @@ -454,6 +454,25 @@ def redirect_to_site(request): return redirect("{}#__posthog={}".format(app_url, state)) +@authenticate_secondarily +def redirect_to_website(request): + team = request.user.team + app_url = request.GET.get("appUrl") or (team.app_urls and team.app_urls[0]) + + if not app_url: + return HttpResponse(status=404) + + if not team or not urllib.parse.urlparse(app_url).hostname in ['localhost', 'posthog.com']: + return HttpResponse(f"Can only redirect to a permitted domain.", status=403) + params = { "jwt": "random_auth_token" } + + # pass the empty string as the safe param so that `//` is encoded correctly. + # see https://github.com/PostHog/posthog/issues/9671 + userData = urllib.parse.quote(json.dumps(params), safe="") + + return redirect("{}?userData={}&redirect={}".format('http://localhost:8001/auth', userData, app_url)) + + @require_http_methods(["POST"]) @authenticate_secondarily def test_slack_webhook(request): diff --git a/posthog/templates/authorize_and_link.html b/posthog/templates/authorize_and_link.html new file mode 100644 index 0000000000000..acb9ea10ce623 --- /dev/null +++ b/posthog/templates/authorize_and_link.html @@ -0,0 +1,13 @@ +{% extends 'layout.html' %} + +{% block content %} +
+ Do you want to login on {{ domain }} using your PostHog Cloud ({{ email}}) account? +

+ + Authorize + {{ domain }} + +
+{% endblock %} \ No newline at end of file diff --git a/posthog/urls.py b/posthog/urls.py index c271406c73469..94b20195f1559 100644 --- a/posthog/urls.py +++ b/posthog/urls.py @@ -128,6 +128,50 @@ def authorize_and_redirect(request: HttpRequest) -> HttpResponse: ) +def link_and_redirect(request: HttpRequest) -> HttpResponse: + if not request.GET.get("redirect"): + return HttpResponse("You need to pass a url to ?redirect=", status=400) + if not request.META.get("HTTP_REFERER"): + return HttpResponse('You need to make a request that includes the "Referer" header.', status=400) + + current_team = cast(User, request.user).team + referer_url = urlparse(request.META["HTTP_REFERER"]) + redirect_url = urlparse(request.GET["redirect"]) + + print(redirect_url.hostname) + if not current_team or not redirect_url.hostname in ['localhost', 'posthog.com']: + return HttpResponse(f"Can only redirect to a permitted domain.", status=403) + + if referer_url.hostname != redirect_url.hostname: + return HttpResponse( + f"Can only redirect to the same domain as the referer: {referer_url.hostname}", + status=403, + ) + + if referer_url.scheme != redirect_url.scheme: + return HttpResponse( + f"Can only redirect to the same scheme as the referer: {referer_url.scheme}", + status=403, + ) + + if referer_url.port != redirect_url.port: + return HttpResponse( + f"Can only redirect to the same port as the referer: {referer_url.port or 'no port in URL'}", + status=403, + ) + + return render_template( + "authorize_and_link.html", + request=request, + context={ + "email": request.user, + "domain": redirect_url.hostname, + "redirect_url": request.GET["redirect"], + }, + ) + + + def opt_slash_path(route: str, view: Callable, name: Optional[str] = None) -> URLPattern: """Catches path with or without trailing slash, taking into account query param and hash.""" # Ignoring the type because while name can be optional on re_path, mypy doesn't agree @@ -161,6 +205,7 @@ def opt_slash_path(route: str, view: Callable, name: Optional[str] = None) -> UR path("api/", include(router.urls)), path("", include(tf_urls)), opt_slash_path("api/user/redirect_to_site", user.redirect_to_site), + opt_slash_path("api/user/redirect_to_website", user.redirect_to_website), opt_slash_path("api/user/test_slack_webhook", user.test_slack_webhook), opt_slash_path("api/prompts/webhook", prompt_webhook), opt_slash_path("api/early_access_features", early_access_features), @@ -174,6 +219,7 @@ def opt_slash_path(route: str, view: Callable, name: Optional[str] = None) -> UR ), re_path(r"^api.+", api_not_found), path("authorize_and_redirect/", login_required(authorize_and_redirect)), + path("link_and_redirect/", login_required(link_and_redirect)), path( "shared_dashboard/", sharing.SharingViewerPageViewSet.as_view({"get": "retrieve"}),