Compare commits

...

11 Commits

Author SHA1 Message Date
4aa497346d slight cleanup
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-03-06 17:04:16 +00:00
7f5cfdc3d3 Merge branch 'main' into flow-no-websocket 2025-02-27 20:15:30 +01:00
42501f6d1e only send messages for stuff non-redirecting
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-02-27 17:46:17 +01:00
2759b1c089 gen api for translate
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-02-27 17:32:37 +01:00
ce6d76babe fix-tests
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-02-27 17:28:29 +01:00
5cc2bd5b36 wip
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-02-27 17:10:33 +01:00
bad8a8ead5 wip
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-02-27 17:08:46 +01:00
1f7a2d5194 I WROTE JS AND IT WORKED FIRST TIME
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-02-27 16:33:58 +01:00
5e328403d6 wip
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-02-27 15:31:30 +01:00
f03e56af93 Merge branch 'main' into flow-no-websocket 2025-02-27 14:50:24 +01:00
516aa9d9b1 web/flow: remove websocket connection
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-02-27 14:49:22 +01:00
11 changed files with 174 additions and 20 deletions

View File

@ -8,7 +8,13 @@ from uuid import UUID
from django.core.serializers.json import DjangoJSONEncoder from django.core.serializers.json import DjangoJSONEncoder
from django.db import models from django.db import models
from django.http import JsonResponse from django.http import JsonResponse
from rest_framework.fields import BooleanField, CharField, ChoiceField, DictField from rest_framework.fields import (
BooleanField,
CharField,
ChoiceField,
DictField,
ListField,
)
from rest_framework.request import Request from rest_framework.request import Request
from authentik.core.api.utils import PassiveSerializer from authentik.core.api.utils import PassiveSerializer
@ -39,6 +45,12 @@ class ErrorDetailSerializer(PassiveSerializer):
code = CharField() code = CharField()
class MessageSerializer(PassiveSerializer):
message = CharField()
level = CharField()
tags = ListField(child=CharField())
class ContextualFlowInfo(PassiveSerializer): class ContextualFlowInfo(PassiveSerializer):
"""Contextual flow information for a challenge""" """Contextual flow information for a challenge"""
@ -55,6 +67,7 @@ class Challenge(PassiveSerializer):
flow_info = ContextualFlowInfo(required=False) flow_info = ContextualFlowInfo(required=False)
component = CharField(default="") component = CharField(default="")
messages = ListField(child=MessageSerializer(), allow_empty=True, required=False)
response_errors = DictField( response_errors = DictField(
child=ErrorDetailSerializer(many=True), allow_empty=True, required=False child=ErrorDetailSerializer(many=True), allow_empty=True, required=False
) )
@ -170,7 +183,6 @@ class FrameChallenge(Challenge):
class FrameChallengeResponse(ChallengeResponse): class FrameChallengeResponse(ChallengeResponse):
component = CharField(default="xak-flow-frame") component = CharField(default="xak-flow-frame")

View File

@ -4,6 +4,7 @@ from typing import TYPE_CHECKING
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import AnonymousUser from django.contrib.auth.models import AnonymousUser
from django.contrib.messages import get_messages
from django.http import HttpRequest from django.http import HttpRequest
from django.http.request import QueryDict from django.http.request import QueryDict
from django.http.response import HttpResponse from django.http.response import HttpResponse
@ -21,6 +22,7 @@ from authentik.flows.challenge import (
ChallengeResponse, ChallengeResponse,
ContextualFlowInfo, ContextualFlowInfo,
HttpChallengeResponse, HttpChallengeResponse,
MessageSerializer,
RedirectChallenge, RedirectChallenge,
SessionEndChallenge, SessionEndChallenge,
WithUserInfoChallenge, WithUserInfoChallenge,
@ -191,6 +193,22 @@ class ChallengeStageView(StageView):
) )
flow_info.is_valid() flow_info.is_valid()
challenge.initial_data["flow_info"] = flow_info.data challenge.initial_data["flow_info"] = flow_info.data
if "messages" not in challenge.initial_data and not isinstance(
challenge, RedirectStage
):
messages = MessageSerializer(
data=[
{
"message": message.message,
"level": message.level_tag,
"tags": message.tags,
}
for message in get_messages(self.request)
],
many=True,
)
messages.is_valid()
challenge.initial_data["messages"] = messages.data
if isinstance(challenge, WithUserInfoChallenge): if isinstance(challenge, WithUserInfoChallenge):
# If there's a pending user, update the `username` field # If there's a pending user, update the `username` field
# this field is only used by password managers. # this field is only used by password managers.

View File

@ -55,6 +55,7 @@ class TestFlowInspector(APITestCase):
"layout": "stacked", "layout": "stacked",
}, },
"flow_designation": "authentication", "flow_designation": "authentication",
"messages": [],
"password_fields": False, "password_fields": False,
"primary_action": "Log in", "primary_action": "Log in",
"sources": [], "sources": [],

View File

@ -88,6 +88,7 @@ class TesOAuth2DeviceInit(OAuthTestCase):
"layout": "stacked", "layout": "stacked",
"title": self.device_flow.title, "title": self.device_flow.title,
}, },
"messages": [],
}, },
) )

View File

@ -7,7 +7,6 @@ from django.contrib.messages.storage.session import SessionStorage
from django.core.cache import cache from django.core.cache import cache
from django.http.request import HttpRequest from django.http.request import HttpRequest
SESSION_KEY = "_messages"
CACHE_PREFIX = "goauthentik.io/root/messages_" CACHE_PREFIX = "goauthentik.io/root/messages_"

View File

@ -145,9 +145,8 @@ class EmailStageView(ChallengeStageView):
user.save() user.save()
return self.executor.stage_ok() return self.executor.stage_ok()
if PLAN_CONTEXT_PENDING_USER not in self.executor.plan.context: if PLAN_CONTEXT_PENDING_USER not in self.executor.plan.context:
self.logger.debug("No pending user") self.logger.warning("No pending user")
messages.error(self.request, _("No pending user.")) return self.executor.stage_invalid(_("No pending user"))
return self.executor.stage_invalid()
# Check if we've already sent the initial e-mail # Check if we've already sent the initial e-mail
if PLAN_CONTEXT_EMAIL_SENT not in self.executor.plan.context: if PLAN_CONTEXT_EMAIL_SENT not in self.executor.plan.context:
try: try:

View File

@ -1,6 +1,5 @@
"""Delete stage logic""" """Delete stage logic"""
from django.contrib import messages
from django.contrib.auth import logout from django.contrib.auth import logout
from django.http import HttpRequest, HttpResponse from django.http import HttpRequest, HttpResponse
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
@ -16,10 +15,8 @@ class UserDeleteStageView(StageView):
"""Delete currently pending user""" """Delete currently pending user"""
user = self.get_pending_user() user = self.get_pending_user()
if not user.is_authenticated: if not user.is_authenticated:
message = _("No Pending User.") self.logger.warning("No authenticated user")
messages.error(request, message) return self.executor.stage_invalid(_("No authenticated User."))
self.logger.debug(message)
return self.executor.stage_invalid()
logout(self.request) logout(self.request)
user.delete() user.delete()
self.logger.debug("Deleted user", user=user) self.logger.debug("Deleted user", user=user)

View File

@ -80,10 +80,8 @@ class UserLoginStageView(ChallengeStageView):
def do_login(self, request: HttpRequest, remember: bool = False) -> HttpResponse: def do_login(self, request: HttpRequest, remember: bool = False) -> HttpResponse:
"""Attach the currently pending user to the current session""" """Attach the currently pending user to the current session"""
if PLAN_CONTEXT_PENDING_USER not in self.executor.plan.context: if PLAN_CONTEXT_PENDING_USER not in self.executor.plan.context:
message = _("No Pending user to login.") self.logger.warning("No pending user to login")
messages.error(request, message) return self.executor.stage_invalid(_("No Pending user to login."))
self.logger.debug(message)
return self.executor.stage_invalid()
backend = self.executor.plan.context.get( backend = self.executor.plan.context.get(
PLAN_CONTEXT_AUTHENTICATION_BACKEND, BACKEND_INBUILT PLAN_CONTEXT_AUTHENTICATION_BACKEND, BACKEND_INBUILT
) )

View File

@ -39432,6 +39432,10 @@ components:
component: component:
type: string type: string
default: ak-stage-access-denied default: ak-stage-access-denied
messages:
type: array
items:
$ref: '#/components/schemas/Message'
response_errors: response_errors:
type: object type: object
additionalProperties: additionalProperties:
@ -39546,6 +39550,10 @@ components:
component: component:
type: string type: string
default: ak-source-oauth-apple default: ak-source-oauth-apple
messages:
type: array
items:
$ref: '#/components/schemas/Message'
response_errors: response_errors:
type: object type: object
additionalProperties: additionalProperties:
@ -39873,6 +39881,10 @@ components:
component: component:
type: string type: string
default: ak-stage-authenticator-duo default: ak-stage-authenticator-duo
messages:
type: array
items:
$ref: '#/components/schemas/Message'
response_errors: response_errors:
type: object type: object
additionalProperties: additionalProperties:
@ -40032,6 +40044,10 @@ components:
component: component:
type: string type: string
default: ak-stage-authenticator-email default: ak-stage-authenticator-email
messages:
type: array
items:
$ref: '#/components/schemas/Message'
response_errors: response_errors:
type: object type: object
additionalProperties: additionalProperties:
@ -40288,6 +40304,10 @@ components:
component: component:
type: string type: string
default: ak-stage-authenticator-sms default: ak-stage-authenticator-sms
messages:
type: array
items:
$ref: '#/components/schemas/Message'
response_errors: response_errors:
type: object type: object
additionalProperties: additionalProperties:
@ -40451,6 +40471,10 @@ components:
component: component:
type: string type: string
default: ak-stage-authenticator-static default: ak-stage-authenticator-static
messages:
type: array
items:
$ref: '#/components/schemas/Message'
response_errors: response_errors:
type: object type: object
additionalProperties: additionalProperties:
@ -40572,6 +40596,10 @@ components:
component: component:
type: string type: string
default: ak-stage-authenticator-totp default: ak-stage-authenticator-totp
messages:
type: array
items:
$ref: '#/components/schemas/Message'
response_errors: response_errors:
type: object type: object
additionalProperties: additionalProperties:
@ -40799,6 +40827,10 @@ components:
component: component:
type: string type: string
default: ak-stage-authenticator-validate default: ak-stage-authenticator-validate
messages:
type: array
items:
$ref: '#/components/schemas/Message'
response_errors: response_errors:
type: object type: object
additionalProperties: additionalProperties:
@ -40852,6 +40884,10 @@ components:
component: component:
type: string type: string
default: ak-stage-authenticator-webauthn default: ak-stage-authenticator-webauthn
messages:
type: array
items:
$ref: '#/components/schemas/Message'
response_errors: response_errors:
type: object type: object
additionalProperties: additionalProperties:
@ -41001,6 +41037,10 @@ components:
component: component:
type: string type: string
default: ak-stage-autosubmit default: ak-stage-autosubmit
messages:
type: array
items:
$ref: '#/components/schemas/Message'
response_errors: response_errors:
type: object type: object
additionalProperties: additionalProperties:
@ -41264,6 +41304,10 @@ components:
component: component:
type: string type: string
default: ak-stage-captcha default: ak-stage-captcha
messages:
type: array
items:
$ref: '#/components/schemas/Message'
response_errors: response_errors:
type: object type: object
additionalProperties: additionalProperties:
@ -41663,6 +41707,10 @@ components:
component: component:
type: string type: string
default: ak-stage-consent default: ak-stage-consent
messages:
type: array
items:
$ref: '#/components/schemas/Message'
response_errors: response_errors:
type: object type: object
additionalProperties: additionalProperties:
@ -42464,6 +42512,10 @@ components:
component: component:
type: string type: string
default: ak-stage-dummy default: ak-stage-dummy
messages:
type: array
items:
$ref: '#/components/schemas/Message'
response_errors: response_errors:
type: object type: object
additionalProperties: additionalProperties:
@ -42666,6 +42718,10 @@ components:
component: component:
type: string type: string
default: ak-stage-email default: ak-stage-email
messages:
type: array
items:
$ref: '#/components/schemas/Message'
response_errors: response_errors:
type: object type: object
additionalProperties: additionalProperties:
@ -43593,6 +43649,10 @@ components:
component: component:
type: string type: string
default: ak-stage-flow-error default: ak-stage-flow-error
messages:
type: array
items:
$ref: '#/components/schemas/Message'
response_errors: response_errors:
type: object type: object
additionalProperties: additionalProperties:
@ -43921,6 +43981,10 @@ components:
component: component:
type: string type: string
default: xak-flow-frame default: xak-flow-frame
messages:
type: array
items:
$ref: '#/components/schemas/Message'
response_errors: response_errors:
type: object type: object
additionalProperties: additionalProperties:
@ -44731,6 +44795,10 @@ components:
component: component:
type: string type: string
default: ak-stage-identification default: ak-stage-identification
messages:
type: array
items:
$ref: '#/components/schemas/Message'
response_errors: response_errors:
type: object type: object
additionalProperties: additionalProperties:
@ -46324,6 +46392,22 @@ components:
- strict - strict
- regex - regex
type: string type: string
Message:
type: object
description: Base serializer class which doesn't implement create/update methods
properties:
message:
type: string
level:
type: string
tags:
type: array
items:
type: string
required:
- level
- message
- tags
Metadata: Metadata:
type: object type: object
description: Serializer for blueprint metadata description: Serializer for blueprint metadata
@ -47209,6 +47293,10 @@ components:
component: component:
type: string type: string
default: ak-provider-oauth2-device-code default: ak-provider-oauth2-device-code
messages:
type: array
items:
$ref: '#/components/schemas/Message'
response_errors: response_errors:
type: object type: object
additionalProperties: additionalProperties:
@ -47237,6 +47325,10 @@ components:
component: component:
type: string type: string
default: ak-provider-oauth2-device-code-finish default: ak-provider-oauth2-device-code-finish
messages:
type: array
items:
$ref: '#/components/schemas/Message'
response_errors: response_errors:
type: object type: object
additionalProperties: additionalProperties:
@ -49387,6 +49479,10 @@ components:
component: component:
type: string type: string
default: ak-stage-password default: ak-stage-password
messages:
type: array
items:
$ref: '#/components/schemas/Message'
response_errors: response_errors:
type: object type: object
additionalProperties: additionalProperties:
@ -52942,6 +53038,10 @@ components:
component: component:
type: string type: string
default: ak-source-plex default: ak-source-plex
messages:
type: array
items:
$ref: '#/components/schemas/Message'
response_errors: response_errors:
type: object type: object
additionalProperties: additionalProperties:
@ -53467,6 +53567,10 @@ components:
component: component:
type: string type: string
default: ak-stage-prompt default: ak-stage-prompt
messages:
type: array
items:
$ref: '#/components/schemas/Message'
response_errors: response_errors:
type: object type: object
additionalProperties: additionalProperties:
@ -54663,6 +54767,10 @@ components:
component: component:
type: string type: string
default: xak-flow-redirect default: xak-flow-redirect
messages:
type: array
items:
$ref: '#/components/schemas/Message'
response_errors: response_errors:
type: object type: object
additionalProperties: additionalProperties:
@ -56528,6 +56636,10 @@ components:
component: component:
type: string type: string
default: ak-stage-session-end default: ak-stage-session-end
messages:
type: array
items:
$ref: '#/components/schemas/Message'
response_errors: response_errors:
type: object type: object
additionalProperties: additionalProperties:
@ -56662,6 +56774,10 @@ components:
component: component:
type: string type: string
default: xak-flow-shell default: xak-flow-shell
messages:
type: array
items:
$ref: '#/components/schemas/Message'
response_errors: response_errors:
type: object type: object
additionalProperties: additionalProperties:
@ -57943,6 +58059,10 @@ components:
component: component:
type: string type: string
default: ak-stage-user-login default: ak-stage-user-login
messages:
type: array
items:
$ref: '#/components/schemas/Message'
response_errors: response_errors:
type: object type: object
additionalProperties: additionalProperties:

View File

@ -5,12 +5,13 @@ import {
TITLE_DEFAULT, TITLE_DEFAULT,
} from "@goauthentik/common/constants"; } from "@goauthentik/common/constants";
import { globalAK } from "@goauthentik/common/global"; import { globalAK } from "@goauthentik/common/global";
import { MessageLevel } from "@goauthentik/common/messages";
import { configureSentry } from "@goauthentik/common/sentry"; import { configureSentry } from "@goauthentik/common/sentry";
import { first } from "@goauthentik/common/utils"; import { first } from "@goauthentik/common/utils";
import { WebsocketClient } from "@goauthentik/common/ws";
import { Interface } from "@goauthentik/elements/Interface"; import { Interface } from "@goauthentik/elements/Interface";
import "@goauthentik/elements/LoadingOverlay"; import "@goauthentik/elements/LoadingOverlay";
import "@goauthentik/elements/ak-locale-context"; import "@goauthentik/elements/ak-locale-context";
import { showMessage } from "@goauthentik/elements/messages/MessageContainer";
import { DefaultBrand } from "@goauthentik/elements/sidebar/SidebarBrand"; import { DefaultBrand } from "@goauthentik/elements/sidebar/SidebarBrand";
import { themeImage } from "@goauthentik/elements/utils/images"; import { themeImage } from "@goauthentik/elements/utils/images";
import "@goauthentik/flow/components/ak-brand-footer"; import "@goauthentik/flow/components/ak-brand-footer";
@ -44,6 +45,7 @@ import {
FlowErrorChallenge, FlowErrorChallenge,
FlowLayoutEnum, FlowLayoutEnum,
FlowsApi, FlowsApi,
Message,
ResponseError, ResponseError,
ShellChallenge, ShellChallenge,
UiThemeEnum, UiThemeEnum,
@ -83,8 +85,6 @@ export class FlowExecutor extends Interface implements StageHost {
@state() @state()
flowInfo?: ContextualFlowInfo; flowInfo?: ContextualFlowInfo;
ws: WebsocketClient;
static get styles(): CSSResult[] { static get styles(): CSSResult[] {
return [PFBase, PFLogin, PFDrawer, PFButton, PFTitle, PFList, PFBackgroundImage].concat(css` return [PFBase, PFLogin, PFDrawer, PFButton, PFTitle, PFList, PFBackgroundImage].concat(css`
:host { :host {
@ -174,7 +174,6 @@ export class FlowExecutor extends Interface implements StageHost {
constructor() { constructor() {
super(); super();
this.ws = new WebsocketClient();
const inspector = new URL(window.location.toString()).searchParams.get("inspector"); const inspector = new URL(window.location.toString()).searchParams.get("inspector");
if (inspector === "" || inspector === "open") { if (inspector === "" || inspector === "open") {
this.inspectorOpen = true; this.inspectorOpen = true;
@ -233,6 +232,7 @@ export class FlowExecutor extends Interface implements StageHost {
if (this.challenge.flowInfo) { if (this.challenge.flowInfo) {
this.flowInfo = this.challenge.flowInfo; this.flowInfo = this.challenge.flowInfo;
} }
this.showMessages(this.challenge.messages);
return !this.challenge.responseErrors; return !this.challenge.responseErrors;
} catch (exc: unknown) { } catch (exc: unknown) {
this.errorMessage(exc as Error | ResponseError | FetchError); this.errorMessage(exc as Error | ResponseError | FetchError);
@ -265,6 +265,7 @@ export class FlowExecutor extends Interface implements StageHost {
if (this.challenge.flowInfo) { if (this.challenge.flowInfo) {
this.flowInfo = this.challenge.flowInfo; this.flowInfo = this.challenge.flowInfo;
} }
this.showMessages(this.challenge.messages);
} catch (exc: unknown) { } catch (exc: unknown) {
// Catch JSON or Update errors // Catch JSON or Update errors
this.errorMessage(exc as Error | ResponseError | FetchError); this.errorMessage(exc as Error | ResponseError | FetchError);
@ -273,6 +274,15 @@ export class FlowExecutor extends Interface implements StageHost {
} }
} }
showMessages(messages: Array<Message> | undefined) {
for (const message of (messages ??= [])) {
showMessage({
level: message.level as MessageLevel,
message: message.message,
});
}
}
async errorMessage(error: Error | ResponseError | FetchError): Promise<void> { async errorMessage(error: Error | ResponseError | FetchError): Promise<void> {
let body = ""; let body = "";
if (error instanceof FetchError) { if (error instanceof FetchError) {

View File

@ -1,4 +1,3 @@
import "@goauthentik/elements/messages/MessageContainer";
import "@goauthentik/flow/FlowExecutor"; import "@goauthentik/flow/FlowExecutor";
// Statically import some stages to speed up load speed // Statically import some stages to speed up load speed
import "@goauthentik/flow/stages/access_denied/AccessDeniedStage"; import "@goauthentik/flow/stages/access_denied/AccessDeniedStage";