diff --git a/authentik/flows/views.py b/authentik/flows/views.py index d95d68c64..e188d9e73 100644 --- a/authentik/flows/views.py +++ b/authentik/flows/views.py @@ -311,13 +311,15 @@ def to_stage_response(request: HttpRequest, source: HttpResponse) -> HttpRespons """Convert normal HttpResponse into JSON Response""" if isinstance(source, HttpResponseRedirect) or source.status_code == 302: redirect_url = source["Location"] - if request.path != redirect_url: - return HttpChallengeResponse( - RedirectChallenge( - {"type": ChallengeTypes.redirect, "to": str(redirect_url)} - ) + # Redirects to the same URL usually indicate an Error within a form + if request.path == redirect_url: + return source + LOGGER.debug("converting to redirect challenge", to=str(redirect_url)) + return HttpChallengeResponse( + RedirectChallenge( + {"type": ChallengeTypes.redirect, "to": str(redirect_url)} ) - return source + ) if isinstance(source, TemplateResponse): return HttpChallengeResponse( ShellChallenge( diff --git a/authentik/policies/http.py b/authentik/policies/denied.py similarity index 100% rename from authentik/policies/http.py rename to authentik/policies/denied.py diff --git a/authentik/policies/views.py b/authentik/policies/views.py index 36184b0de..ce5969359 100644 --- a/authentik/policies/views.py +++ b/authentik/policies/views.py @@ -13,7 +13,7 @@ from authentik.core.models import Application, Provider, User from authentik.flows.views import SESSION_KEY_APPLICATION_PRE from authentik.lib.sentry import SentryIgnoredException from authentik.policies.engine import PolicyEngine -from authentik.policies.http import AccessDeniedResponse +from authentik.policies.denied import AccessDeniedResponse from authentik.policies.types import PolicyResult LOGGER = get_logger() diff --git a/web/src/flows/FlowExecutor.ts b/web/src/flows/FlowExecutor.ts index abb97b3b1..51583440a 100644 --- a/web/src/flows/FlowExecutor.ts +++ b/web/src/flows/FlowExecutor.ts @@ -40,6 +40,9 @@ import { ifDefined } from "lit-html/directives/if-defined"; import { until } from "lit-html/directives/until"; import { TITLE_SUFFIX } from "../elements/router/RouterOutlet"; import { AccessDeniedChallenge } from "./access_denied/FlowAccessDenied"; +import { getQueryVariables } from "./utils"; + +export const NEXT_ARG = "next"; @customElement("ak-flow-executor") export class FlowExecutor extends LitElement implements StageHost { @@ -123,6 +126,14 @@ export class FlowExecutor extends LitElement implements StageHost { } firstUpdated(): void { + // Check if there is a ?next arg and save it + // this is used for deep linking, if a user tries to access an application, + // but needs to authenticate first + const queryVars = getQueryVariables(); + if (NEXT_ARG in queryVars) { + const next = queryVars[NEXT_ARG]; + localStorage.setItem(NEXT_ARG, next); + } new RootApi(DEFAULT_CONFIG).rootConfigList().then((config) => { this.config = config; }); @@ -171,7 +182,12 @@ export class FlowExecutor extends LitElement implements StageHost { switch (this.challenge.type) { case ChallengeTypeEnum.Redirect: console.debug(`authentik/flows: redirecting to ${(this.challenge as RedirectChallenge).to}`); - window.location.assign((this.challenge as RedirectChallenge).to); + if (localStorage.getItem(NEXT_ARG) === null) { + window.location.assign((this.challenge as RedirectChallenge).to); + } else { + localStorage.clear(); + window.location.assign(localStorage.getItem(NEXT_ARG) || ""); + } return this.renderLoading(); case ChallengeTypeEnum.Shell: return html`${unsafeHTML((this.challenge as ShellChallenge).body)}`; diff --git a/web/src/flows/utils.ts b/web/src/flows/utils.ts new file mode 100644 index 000000000..360560a45 --- /dev/null +++ b/web/src/flows/utils.ts @@ -0,0 +1,10 @@ +export function getQueryVariables(): Record { + const query = window.location.search.substring(1); + const vars = query.split("&"); + const entries: Record = {}; + for (let i = 0; i < vars.length; i++) { + const pair = vars[i].split("="); + entries[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1]); + } + return entries; +}