mirror of
https://github.com/goauthentik/authentik.git
synced 2026-06-17 19:09:11 +03:00
web: Fix server-side message race condition, type mismatch.
This commit is contained in:
@@ -16,18 +16,14 @@
|
|||||||
relBase: "{{ base_url_rel }}",
|
relBase: "{{ base_url_rel }}",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
window.addEventListener("DOMContentLoaded", function () {
|
|
||||||
{% for message in messages %}
|
|
||||||
window.dispatchEvent(
|
|
||||||
new CustomEvent("ak-message", {
|
|
||||||
bubbles: true,
|
|
||||||
composed: true,
|
|
||||||
detail: {
|
|
||||||
level: "{{ message.tags|escapejs }}",
|
|
||||||
message: "{{ message.message|escapejs }}",
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
{% endfor %}
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<script data-id="authentik-messages" type="application/json">
|
||||||
|
[{% for message in messages %}
|
||||||
|
{
|
||||||
|
"level": "{{ message.tags|escapejs }}",
|
||||||
|
"message": "{{ message.message|escapejs }}"
|
||||||
|
}{% if not forloop.last %},{% endif %}
|
||||||
|
{% endfor %}]
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -57,3 +57,23 @@ export function trimMany<T extends object, K extends keyof T>(target: T, ...keys
|
|||||||
|
|
||||||
return output as Pick<T, K>;
|
return output as Pick<T, K>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to parse a JSON string, returning a fallback value if parsing fails or if the input is not a string.
|
||||||
|
*
|
||||||
|
* @param input The input to parse.
|
||||||
|
* @param fallback The fallback value to return if parsing fails or if the input is not a string. Defaults to `null`.
|
||||||
|
*
|
||||||
|
* @returns The parsed value, or the fallback value if parsing fails or if the input is not a string.
|
||||||
|
*/
|
||||||
|
export function tryParsingJSON<T = unknown>(input: unknown, fallback?: undefined): T | undefined;
|
||||||
|
export function tryParsingJSON<T = unknown>(input: unknown, fallback: null): T | null;
|
||||||
|
export function tryParsingJSON<T = unknown, F = null>(input: unknown, fallback?: F): T | F {
|
||||||
|
if (typeof input !== "string") return (fallback ?? null) as F;
|
||||||
|
|
||||||
|
try {
|
||||||
|
return JSON.parse(input);
|
||||||
|
} catch {
|
||||||
|
return (fallback ?? null) as F;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import "#elements/messages/Message";
|
|||||||
|
|
||||||
import { parseAPIResponseError, pluckErrorDetail } from "#common/errors/network";
|
import { parseAPIResponseError, pluckErrorDetail } from "#common/errors/network";
|
||||||
import { APIMessage, MessageLevel } from "#common/messages";
|
import { APIMessage, MessageLevel } from "#common/messages";
|
||||||
|
import { tryParsingJSON } from "#common/objects";
|
||||||
|
|
||||||
import { AKElement } from "#elements/Base";
|
import { AKElement } from "#elements/Base";
|
||||||
import Styles from "#elements/messages/styles.css";
|
import Styles from "#elements/messages/styles.css";
|
||||||
@@ -106,6 +107,8 @@ export type MessageContainerAlignment = "top-left" | "top-right" | "bottom-left"
|
|||||||
|
|
||||||
@customElement("ak-message-container")
|
@customElement("ak-message-container")
|
||||||
export class MessageContainer extends AKElement {
|
export class MessageContainer extends AKElement {
|
||||||
|
public static readonly serializedSelector = "script[data-id=authentik-messages]";
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
public messages: APIMessage[] = [];
|
public messages: APIMessage[] = [];
|
||||||
|
|
||||||
@@ -129,8 +132,30 @@ export class MessageContainer extends AKElement {
|
|||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
|
|
||||||
this.popover = "manual";
|
this.popover = "manual";
|
||||||
|
|
||||||
|
requestAnimationFrame(this.drainMessages);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected drainMessages = (): void => {
|
||||||
|
const selector = (this.constructor as typeof MessageContainer).serializedSelector;
|
||||||
|
const container = this.ownerDocument.querySelector<HTMLScriptElement>(selector);
|
||||||
|
|
||||||
|
if (!container) {
|
||||||
|
logger.warn(`Expected to find a script tag with ${selector}, but none was found.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const messages = tryParsingJSON<APIMessage[]>(container.textContent);
|
||||||
|
|
||||||
|
if (!messages?.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const message of messages) {
|
||||||
|
this.addMessage(message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
public updated(changedProperties: PropertyValues<this>) {
|
public updated(changedProperties: PropertyValues<this>) {
|
||||||
super.updated(changedProperties);
|
super.updated(changedProperties);
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,24 @@ body {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ak-c-safe-mode {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 9999999;
|
||||||
|
inset-block-end: 0;
|
||||||
|
inset-inline-end: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: var(--pf-global--FontSize--sm, 0.75rem);
|
||||||
|
font-weight: bold;
|
||||||
|
color: var(--pf-global--Color--100, CanvasText);
|
||||||
|
background-color: var(--pf-global--BackgroundColor--200, Canvas);
|
||||||
|
opacity: 0.75;
|
||||||
|
padding: var(--pf-global--spacer--sm, 0.5rem);
|
||||||
|
border-start-start-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
/* #endregion */
|
/* #endregion */
|
||||||
|
|
||||||
html > form > input {
|
html > form > input {
|
||||||
|
|||||||
Reference in New Issue
Block a user