Skip to content

Commit f122dd3

Browse files
committed
web: Clean up Sentry usage. Reduce API calls.
1 parent 49bd209 commit f122dd3

File tree

8 files changed

+155
-94
lines changed

8 files changed

+155
-94
lines changed

web/src/admin/AdminInterface/AdminInterface.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ import {
55
EVENT_API_DRAWER_TOGGLE,
66
EVENT_NOTIFICATION_DRAWER_TOGGLE,
77
} from "@goauthentik/common/constants";
8-
import { configureSentry } from "@goauthentik/common/sentry";
8+
import { setSentryPII, tryInitializeSentry } from "@goauthentik/common/sentry";
9+
import { ServerContext } from "@goauthentik/common/server-context";
910
import { me } from "@goauthentik/common/users";
1011
import { WebsocketClient } from "@goauthentik/common/ws";
1112
import { AuthenticatedInterface } from "@goauthentik/elements/Interface";
@@ -107,14 +108,21 @@ export class AdminInterface extends AuthenticatedInterface {
107108
}
108109

109110
async firstUpdated(): Promise<void> {
110-
configureSentry(true);
111+
tryInitializeSentry(ServerContext.config);
111112
this.user = await me();
112113

114+
setSentryPII(this.user.user);
115+
113116
const canAccessAdmin =
114117
this.user.user.isSuperuser ||
115118
// TODO: somehow add `access_admin_interface` to the API schema
116119
this.user.user.systemPermissions.includes("access_admin_interface");
120+
117121
if (!canAccessAdmin && this.user.user.pk > 0) {
122+
console.debug(
123+
"authentik/admin: User does not have access to admin interface. Redirecting...",
124+
);
125+
118126
window.location.assign("/if/user/");
119127
}
120128
}

web/src/admin/providers/ldap/LDAPOptionsAndHelp.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ export const bindModeOptions = [
1515
{
1616
label: msg("Direct binding"),
1717
value: LDAPAPIAccessMode.Direct,
18-
description: html`${msg("Always execute the configured bind flow to authenticate the user")}`,
18+
description: html`${msg(
19+
"Always execute the configured bind flow to authenticate the user",
20+
)}`,
1921
},
2022
];
2123

@@ -31,7 +33,9 @@ export const searchModeOptions = [
3133
{
3234
label: msg("Direct querying"),
3335
value: LDAPAPIAccessMode.Direct,
34-
description: html`${msg("Always returns the latest data, but slower than cached querying")}`,
36+
description: html`${msg(
37+
"Always returns the latest data, but slower than cached querying",
38+
)}`,
3539
},
3640
];
3741

web/src/common/sentry.ts

Lines changed: 105 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -1,101 +1,126 @@
11
import { VERSION } from "@goauthentik/common/constants";
2-
import { ServerContext } from "@goauthentik/common/server-context";
3-
import { me } from "@goauthentik/common/users";
4-
import {
5-
ErrorEvent,
6-
EventHint,
7-
browserTracingIntegration,
8-
init,
9-
setTag,
10-
setUser,
11-
} from "@sentry/browser";
2+
import { RouteInterfaceName, readInterfaceRouteParam } from "@goauthentik/elements/router/utils";
3+
import { BrowserOptions, browserTracingIntegration, init, setTag, setUser } from "@sentry/browser";
124

13-
import { CapabilitiesEnum, ResponseError } from "@goauthentik/api";
5+
import { CapabilitiesEnum, Config, ResponseError, UserSelf } from "@goauthentik/api";
146

157
/**
168
* A generic error that can be thrown without triggering Sentry's reporting.
9+
*
10+
* @category Sentry
1711
*/
1812
export class SentryIgnoredError extends Error {}
1913

20-
export const TAG_SENTRY_COMPONENT = "authentik.component";
21-
export const TAG_SENTRY_CAPABILITIES = "authentik.capabilities";
14+
/**
15+
* Attempt initializing Spotlight.
16+
*
17+
* @see {@link https://spotlightjs.com/ Spotlight}
18+
* @category Sentry
19+
*/
20+
export async function tryInitializingSpotlight() {
21+
return import("@spotlightjs/spotlight").then((Spotlight) =>
22+
Spotlight.init({ injectImmediately: true }),
23+
);
24+
}
2225

23-
export async function configureSentry(canDoPpi = false): Promise<void> {
24-
const { errorReporting, capabilities } = ServerContext.config;
26+
/**
27+
* Default Sentry options for the browser.
28+
*
29+
* @category Sentry
30+
*/
31+
const DEFAULT_SENTRY_BROWSER_OPTIONS = {
32+
ignoreErrors: [
33+
/network/gi,
34+
/fetch/gi,
35+
/module/gi,
36+
// Error on edge on ios,
37+
// https://stackoverflow.com/questions/69261499/what-is-instantsearchsdkjsbridgeclearhighlight
38+
/instantSearchSDKJSBridgeClearHighlight/gi,
39+
// Seems to be an issue in Safari and Firefox
40+
/MutationObserver.observe/gi,
41+
/NS_ERROR_FAILURE/gi,
42+
],
43+
release: `authentik@${VERSION}`,
44+
integrations: [
45+
browserTracingIntegration({
46+
shouldCreateSpanForRequest: (url: string) => {
47+
return url.startsWith(window.location.host);
48+
},
49+
}),
50+
],
51+
beforeSend: (event, hint) => {
52+
if (!hint) {
53+
return event;
54+
}
55+
if (hint.originalException instanceof SentryIgnoredError) {
56+
return null;
57+
}
58+
if (
59+
hint.originalException instanceof ResponseError ||
60+
hint.originalException instanceof DOMException
61+
) {
62+
return null;
63+
}
64+
return event;
65+
},
66+
} as const satisfies BrowserOptions;
2567

26-
if (!errorReporting.enabled) return;
68+
/**
69+
* Include the given user in Sentry events.
70+
*
71+
* @category Sentry
72+
*/
73+
export function setSentryPII(user: UserSelf): void {
74+
console.debug("authentik/config: Sentry with PII enabled.");
2775

76+
setUser({ email: user.email });
77+
}
78+
79+
/**
80+
* Include the given capabilities in Sentry events.
81+
*
82+
* @category Sentry
83+
*/
84+
export function setSentryCapabilities(capabilities: CapabilitiesEnum[]): void {
85+
setTag("authentik.capabilities", capabilities.join(","));
86+
}
87+
88+
/**
89+
* Include the given route interface in Sentry events.
90+
*
91+
* @category Sentry
92+
*/
93+
export function setSentryInterface(interfaceName: RouteInterfaceName) {
94+
setTag("authentik.component", `web/${interfaceName}}`);
95+
}
96+
97+
/**
98+
* Attempt to initialize Sentry with the given configuration.
99+
*
100+
* @see {@linkcode setSentryPII}
101+
* @see {@linkcode setSentryCapabilities}
102+
* @see {@linkcode setSentryInterface}
103+
* @category Sentry
104+
*/
105+
export function tryInitializeSentry({ errorReporting, capabilities }: Config): void {
28106
init({
107+
...DEFAULT_SENTRY_BROWSER_OPTIONS,
29108
dsn: errorReporting.sentryDsn,
30-
ignoreErrors: [
31-
/network/gi,
32-
/fetch/gi,
33-
/module/gi,
34-
// Error on edge on ios,
35-
// https://stackoverflow.com/questions/69261499/what-is-instantsearchsdkjsbridgeclearhighlight
36-
/instantSearchSDKJSBridgeClearHighlight/gi,
37-
// Seems to be an issue in Safari and Firefox
38-
/MutationObserver.observe/gi,
39-
/NS_ERROR_FAILURE/gi,
40-
],
41-
release: `authentik@${VERSION}`,
42-
integrations: [
43-
browserTracingIntegration({
44-
shouldCreateSpanForRequest: (url: string) => {
45-
return url.startsWith(window.location.host);
46-
},
47-
}),
48-
],
49109
tracesSampleRate: errorReporting.tracesSampleRate,
50110
environment: errorReporting.environment,
51-
beforeSend: (
52-
event: ErrorEvent,
53-
hint: EventHint,
54-
): ErrorEvent | PromiseLike<ErrorEvent | null> | null => {
55-
if (!hint) {
56-
return event;
57-
}
58-
if (hint.originalException instanceof SentryIgnoredError) {
59-
return null;
60-
}
61-
if (
62-
hint.originalException instanceof ResponseError ||
63-
hint.originalException instanceof DOMException
64-
) {
65-
return null;
66-
}
67-
return event;
68-
},
111+
enabled: process.env.NODE_ENV !== "development",
69112
});
70113

71-
setTag(TAG_SENTRY_CAPABILITIES, capabilities.join(","));
72-
73-
if (window.location.pathname.includes("if/")) {
74-
setTag(TAG_SENTRY_COMPONENT, `web/${currentInterface()}`);
75-
}
114+
setSentryCapabilities(capabilities);
115+
setSentryInterface(readInterfaceRouteParam());
76116

77117
if (capabilities.includes(CapabilitiesEnum.CanDebug)) {
78-
const Spotlight = await import("@spotlightjs/spotlight");
79-
80-
Spotlight.init({ injectImmediately: true });
81-
}
82-
83-
if (errorReporting.sendPii && canDoPpi) {
84-
me().then((user) => {
85-
setUser({ email: user.user.email });
86-
console.debug("authentik/config: Sentry with PII enabled.");
87-
});
88-
} else {
89-
console.debug("authentik/config: Sentry enabled.");
90-
}
91-
}
92-
93-
// Get the interface name from URL
94-
export function currentInterface(): string {
95-
const pathMatches = window.location.pathname.match(/.+if\/(\w+)\//);
96-
let currentInterface = "unknown";
97-
if (pathMatches && pathMatches.length >= 2) {
98-
currentInterface = pathMatches[1];
118+
tryInitializingSpotlight()
119+
.then(() => {
120+
console.debug("authentik/config: Sentry with spotlight enabled.");
121+
})
122+
.catch((err) => {
123+
console.warn("sentry: Failed to load spotlight", err);
124+
});
99125
}
100-
return currentInterface.toLowerCase();
101126
}

web/src/common/ui/config.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { currentInterface } from "@goauthentik/common/sentry";
21
import { me } from "@goauthentik/common/users";
2+
import { readInterfaceRouteParam } from "@goauthentik/elements/router/utils";
33

44
import { UiThemeEnum, UserSelf } from "@goauthentik/api";
55

@@ -77,7 +77,7 @@ export class DefaultUIConfig implements UIConfig {
7777
};
7878

7979
constructor() {
80-
if (currentInterface() === "user") {
80+
if (readInterfaceRouteParam() === "user") {
8181
this.enabledFeatures.apiDrawer = false;
8282
}
8383
}

web/src/elements/PageHeader.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@ import {
33
EVENT_WS_MESSAGE,
44
TITLE_DEFAULT,
55
} from "@goauthentik/common/constants";
6-
import { currentInterface } from "@goauthentik/common/sentry";
76
import { ServerContext } from "@goauthentik/common/server-context";
87
import { UIConfig, UserDisplay, uiConfig } from "@goauthentik/common/ui/config";
98
import { me } from "@goauthentik/common/users";
109
import "@goauthentik/components/ak-nav-buttons";
1110
import { AKElement } from "@goauthentik/elements/Base";
1211
import { WithBrandConfig } from "@goauthentik/elements/Interface/brandProvider";
12+
import { readInterfaceRouteParam } from "@goauthentik/elements/router/utils";
1313
import "@patternfly/elements/pf-tooltip/pf-tooltip.js";
1414

1515
import { msg } from "@lit/localize";
@@ -126,7 +126,7 @@ export class PageHeader extends WithBrandConfig(AKElement) {
126126
}
127127

128128
setTitle(header?: string) {
129-
const currentIf = currentInterface();
129+
const currentIf = readInterfaceRouteParam();
130130
let title = this.brand?.brandingTitle || TITLE_DEFAULT;
131131
if (currentIf === "admin") {
132132
title = `${msg("Admin")} - ${title}`;

web/src/elements/router/utils.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/**
2+
* The name identifier for the current interface.
3+
*/
4+
export type RouteInterfaceName = "user" | "admin" | "flow" | "unknown";
5+
6+
/**
7+
* Read the current interface route parameter from the URL.
8+
*
9+
* @param location - The location object to read the pathname from. Defaults to `window.location`.
10+
* * @returns The name of the current interface, or "unknown" if not found.
11+
*/
12+
export function readInterfaceRouteParam(
13+
location: Pick<URL, "pathname"> = window.location,
14+
): RouteInterfaceName {
15+
const [, currentInterface = "unknown"] = location.pathname.match(/.+if\/(\w+)\//) || [];
16+
17+
return currentInterface.toLowerCase() as RouteInterfaceName;
18+
}

web/src/flow/FlowExecutor.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import {
44
EVENT_FLOW_INSPECTOR_TOGGLE,
55
TITLE_DEFAULT,
66
} from "@goauthentik/common/constants";
7-
import { configureSentry } from "@goauthentik/common/sentry";
7+
import { tryInitializeSentry } from "@goauthentik/common/sentry";
88
import { ServerContext } from "@goauthentik/common/server-context";
99
import { WebsocketClient } from "@goauthentik/common/ws";
1010
import { Interface } from "@goauthentik/elements/Interface";
@@ -242,10 +242,12 @@ export class FlowExecutor extends Interface implements StageHost {
242242
}
243243

244244
async firstUpdated(): Promise<void> {
245-
configureSentry();
245+
tryInitializeSentry(ServerContext.config);
246+
246247
if (this.config?.capabilities.includes(CapabilitiesEnum.CanDebug)) {
247248
this.inspectorAvailable = true;
248249
}
250+
249251
this.loading = true;
250252
try {
251253
const challenge = await new FlowsApi(DEFAULT_CONFIG).flowsExecutorGet({

web/src/user/UserInterface.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import {
44
EVENT_NOTIFICATION_DRAWER_TOGGLE,
55
EVENT_WS_MESSAGE,
66
} from "@goauthentik/common/constants";
7-
import { configureSentry } from "@goauthentik/common/sentry";
7+
import { setSentryPII, tryInitializeSentry } from "@goauthentik/common/sentry";
88
import { ServerContext } from "@goauthentik/common/server-context";
99
import { UIConfig } from "@goauthentik/common/ui/config";
1010
import { me } from "@goauthentik/common/users";
@@ -280,7 +280,9 @@ export class UserInterface extends AuthenticatedInterface {
280280
super();
281281
this.ws = new WebsocketClient();
282282
this.fetchConfigurationDetails();
283-
configureSentry(true);
283+
284+
tryInitializeSentry(ServerContext.config);
285+
284286
this.toggleNotificationDrawer = this.toggleNotificationDrawer.bind(this);
285287
this.toggleApiDrawer = this.toggleApiDrawer.bind(this);
286288
this.fetchConfigurationDetails = this.fetchConfigurationDetails.bind(this);
@@ -321,8 +323,10 @@ export class UserInterface extends AuthenticatedInterface {
321323
}
322324

323325
fetchConfigurationDetails() {
324-
me().then((me: SessionUser) => {
325-
this.me = me;
326+
me().then((session: SessionUser) => {
327+
this.me = session;
328+
setSentryPII(session.user);
329+
326330
new EventsApi(DEFAULT_CONFIG)
327331
.eventsNotificationsList({
328332
seen: false,

0 commit comments

Comments
 (0)