mirror of
https://github.com/gethomepage/homepage.git
synced 2026-08-31 01:15:53 -07:00
Enhancement: add authorizationsLast24H field to Authentik widget, opt… (#6989)
Docker CI / Docker Build & Push (push) Has been cancelled
Lint / Linting Checks (push) Has been cancelled
Release Drafter / Update Release Draft (push) Has been cancelled
Release Drafter / Auto Label PR (push) Has been cancelled
Tests / vitest (1) (push) Has been cancelled
Tests / vitest (2) (push) Has been cancelled
Tests / vitest (3) (push) Has been cancelled
Tests / vitest (4) (push) Has been cancelled
Docker CI / Docker Build & Push (push) Has been cancelled
Lint / Linting Checks (push) Has been cancelled
Release Drafter / Update Release Draft (push) Has been cancelled
Release Drafter / Auto Label PR (push) Has been cancelled
Tests / vitest (1) (push) Has been cancelled
Tests / vitest (2) (push) Has been cancelled
Tests / vitest (3) (push) Has been cancelled
Tests / vitest (4) (push) Has been cancelled
Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
This commit is contained in:
@@ -5,7 +5,7 @@ description: Authentik Widget Configuration
|
|||||||
|
|
||||||
Learn more about [Authentik](https://github.com/goauthentik/authentik).
|
Learn more about [Authentik](https://github.com/goauthentik/authentik).
|
||||||
|
|
||||||
This widget reads the number of active users in the system, as well as logins for the last 24 hours.
|
This widget reads the number of active users in the system, as well as logins for the last 24 hours, and authorizations when using widget version 2.
|
||||||
|
|
||||||
You will need to generate an API token for an existing user under `Admin Portal` > `Directory` > `Tokens & App passwords`.
|
You will need to generate an API token for an existing user under `Admin Portal` > `Directory` > `Tokens & App passwords`.
|
||||||
Make sure to set Intent to "API Token".
|
Make sure to set Intent to "API Token".
|
||||||
@@ -15,7 +15,7 @@ The account you made the API token for also needs the following **Assigned globa
|
|||||||
- authentik Core -> Can view User (Model: User)
|
- authentik Core -> Can view User (Model: User)
|
||||||
- authentik Events -> Can view Event (Model: Event)
|
- authentik Events -> Can view Event (Model: Event)
|
||||||
|
|
||||||
Allowed fields: `["users", "loginsLast24H", "failedLoginsLast24H"]`.
|
Allowed fields: `["users", "loginsLast24H", "failedLoginsLast24H", "authorizationsLast24H"]` (`authorizationsLast24H` works with v2 only).
|
||||||
|
|
||||||
| Authentik Version | Homepage Widget Version |
|
| Authentik Version | Homepage Widget Version |
|
||||||
| ----------------- | ----------------------- |
|
| ----------------- | ----------------------- |
|
||||||
|
|||||||
@@ -471,7 +471,8 @@
|
|||||||
"authentik": {
|
"authentik": {
|
||||||
"users": "Users",
|
"users": "Users",
|
||||||
"loginsLast24H": "Logins (24h)",
|
"loginsLast24H": "Logins (24h)",
|
||||||
"failedLoginsLast24H": "Failed Logins (24h)"
|
"failedLoginsLast24H": "Failed Logins (24h)",
|
||||||
|
"authorizationsLast24H": "Auths (24h)"
|
||||||
},
|
},
|
||||||
"proxmox": {
|
"proxmox": {
|
||||||
"mem": "MEM",
|
"mem": "MEM",
|
||||||
|
|||||||
@@ -8,32 +8,40 @@ export default function Component({ service }) {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const { widget } = service;
|
const { widget } = service;
|
||||||
|
const isV2 = widget.version === 2;
|
||||||
|
|
||||||
const { data: usersData, error: usersError } = useWidgetAPI(widget, "users");
|
const { data: usersData, error: usersError } = useWidgetAPI(widget, "users");
|
||||||
|
|
||||||
const loginsEndpoint = widget.version === 2 ? "loginv2" : "login";
|
const loginsEndpoint = isV2 ? "" : "login";
|
||||||
const { data: loginsData, error: loginsError } = useWidgetAPI(widget, loginsEndpoint);
|
const { data: loginsData, error: loginsError } = useWidgetAPI(widget, loginsEndpoint);
|
||||||
|
|
||||||
const failedLoginsEndpoint = widget.version === 2 ? "login_failedv2" : "login_failed";
|
const failedLoginsEndpoint = isV2 ? "" : "login_failed";
|
||||||
const { data: failedLoginsData, error: failedLoginsError } = useWidgetAPI(widget, failedLoginsEndpoint);
|
const { data: failedLoginsData, error: failedLoginsError } = useWidgetAPI(widget, failedLoginsEndpoint);
|
||||||
|
|
||||||
if (usersError || loginsError || failedLoginsError) {
|
const eventsDataEndpoint = isV2 ? "datav2" : "";
|
||||||
const finalError = usersError ?? loginsError ?? failedLoginsError;
|
const { data: eventsData, error: eventsDataError } = useWidgetAPI(widget, eventsDataEndpoint);
|
||||||
|
|
||||||
|
if (usersError || loginsError || failedLoginsError || eventsDataError) {
|
||||||
|
const finalError = usersError ?? loginsError ?? failedLoginsError ?? eventsDataError;
|
||||||
return <Container service={service} error={finalError} />;
|
return <Container service={service} error={finalError} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!usersData || !loginsData || !failedLoginsData) {
|
const hasNoData = isV2 ? !usersData || !eventsData : !usersData || !loginsData || !failedLoginsData;
|
||||||
|
|
||||||
|
if (hasNoData) {
|
||||||
return (
|
return (
|
||||||
<Container service={service}>
|
<Container service={service}>
|
||||||
<Block label="authentik.users" />
|
<Block label="authentik.users" />
|
||||||
<Block label="authentik.loginsLast24H" />
|
<Block label="authentik.loginsLast24H" />
|
||||||
<Block label="authentik.failedLoginsLast24H" />
|
<Block label="authentik.failedLoginsLast24H" />
|
||||||
|
{isV2 && <Block label="authentik.authorizationsLast24H" />}
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let loginsLast24H;
|
let loginsLast24H;
|
||||||
let failedLoginsLast24H;
|
let failedLoginsLast24H;
|
||||||
|
let authorizationsLast24H;
|
||||||
switch (widget.version) {
|
switch (widget.version) {
|
||||||
// v1 is default
|
// v1 is default
|
||||||
default:
|
default:
|
||||||
@@ -48,13 +56,28 @@ export default function Component({ service }) {
|
|||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
loginsLast24H =
|
const events = Array.isArray(eventsData) ? eventsData : [];
|
||||||
loginsData.reduce?.(
|
const result = events.reduce(
|
||||||
(total, current) => (current?.count && current?.action === "login" ? total + current.count : total),
|
(acc, current) => {
|
||||||
0,
|
if (!current?.count || !current?.action) {
|
||||||
) || 0;
|
return acc;
|
||||||
failedLoginsLast24H =
|
}
|
||||||
failedLoginsData.reduce?.((total, current) => (current?.count ? total + current.count : total), 0) || 0;
|
|
||||||
|
if (current.action === "login") {
|
||||||
|
acc.logins += current.count;
|
||||||
|
} else if (current.action === "login_failed") {
|
||||||
|
acc.failed += current.count;
|
||||||
|
} else if (current.action === "authorize_application") {
|
||||||
|
acc.authorizations += current.count;
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{ logins: 0, failed: 0, authorizations: 0 },
|
||||||
|
);
|
||||||
|
loginsLast24H = result.logins;
|
||||||
|
failedLoginsLast24H = result.failed;
|
||||||
|
authorizationsLast24H = result.authorizations;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,6 +86,9 @@ export default function Component({ service }) {
|
|||||||
<Block label="authentik.users" value={t("common.number", { value: usersData.pagination.count })} />
|
<Block label="authentik.users" value={t("common.number", { value: usersData.pagination.count })} />
|
||||||
<Block label="authentik.loginsLast24H" value={t("common.number", { value: loginsLast24H })} />
|
<Block label="authentik.loginsLast24H" value={t("common.number", { value: loginsLast24H })} />
|
||||||
<Block label="authentik.failedLoginsLast24H" value={t("common.number", { value: failedLoginsLast24H })} />
|
<Block label="authentik.failedLoginsLast24H" value={t("common.number", { value: failedLoginsLast24H })} />
|
||||||
|
{isV2 && (
|
||||||
|
<Block label="authentik.authorizationsLast24H" value={t("common.number", { value: authorizationsLast24H })} />
|
||||||
|
)}
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,24 +30,31 @@ describe("widgets/authentik/component", () => {
|
|||||||
settings: { hideErrors: false },
|
settings: { hideErrors: false },
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(container.querySelectorAll(".service-block")).toHaveLength(3);
|
expect(container.querySelectorAll(".service-block")).toHaveLength(4);
|
||||||
expect(screen.getByText("authentik.users")).toBeInTheDocument();
|
expect(screen.getByText("authentik.users")).toBeInTheDocument();
|
||||||
expect(screen.getByText("authentik.loginsLast24H")).toBeInTheDocument();
|
expect(screen.getByText("authentik.loginsLast24H")).toBeInTheDocument();
|
||||||
expect(screen.getByText("authentik.failedLoginsLast24H")).toBeInTheDocument();
|
expect(screen.getByText("authentik.failedLoginsLast24H")).toBeInTheDocument();
|
||||||
|
expect(screen.getByText("authentik.authorizationsLast24H")).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("computes v2 login/failed counts from action data", () => {
|
it("computes v2 login/failed counts from action data", () => {
|
||||||
useWidgetAPI.mockImplementation((widget, endpoint) => {
|
useWidgetAPI.mockImplementation((widget, endpoint) => {
|
||||||
if (endpoint === "users") return { data: { pagination: { count: 10 } }, error: undefined };
|
if (endpoint === "users") return { data: { pagination: { count: 10 } }, error: undefined };
|
||||||
if (endpoint === "loginv2")
|
if (endpoint === "datav2")
|
||||||
return {
|
return {
|
||||||
data: [
|
data: [
|
||||||
{ action: "login", count: 2 },
|
{ action: "login", count: 2 },
|
||||||
|
{ action: "login", count: 4 },
|
||||||
{ action: "logout", count: 9 },
|
{ action: "logout", count: 9 },
|
||||||
|
{ action: "login_failed", count: 1 },
|
||||||
|
{ action: "login_failed", count: 2 },
|
||||||
|
{ action: "authorize_application", count: 10 },
|
||||||
|
{ action: "authorize_application", count: 8 },
|
||||||
|
{ action: null, count: null },
|
||||||
|
{ action: "login", count: null },
|
||||||
],
|
],
|
||||||
error: undefined,
|
error: undefined,
|
||||||
};
|
};
|
||||||
if (endpoint === "login_failedv2") return { data: [{ count: 3 }, { count: null }], error: undefined };
|
|
||||||
return { data: undefined, error: undefined };
|
return { data: undefined, error: undefined };
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -55,10 +62,11 @@ describe("widgets/authentik/component", () => {
|
|||||||
settings: { hideErrors: false },
|
settings: { hideErrors: false },
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(container.querySelectorAll(".service-block")).toHaveLength(3);
|
expect(container.querySelectorAll(".service-block")).toHaveLength(4);
|
||||||
expectBlockValue(container, "authentik.users", 10);
|
expectBlockValue(container, "authentik.users", 10);
|
||||||
expectBlockValue(container, "authentik.loginsLast24H", 2);
|
expectBlockValue(container, "authentik.loginsLast24H", 6);
|
||||||
expectBlockValue(container, "authentik.failedLoginsLast24H", 3);
|
expectBlockValue(container, "authentik.failedLoginsLast24H", 3);
|
||||||
|
expectBlockValue(container, "authentik.authorizationsLast24H", 18);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("computes v1 login/failed counts for entries within the last 24h window", () => {
|
it("computes v1 login/failed counts for entries within the last 24h window", () => {
|
||||||
|
|||||||
@@ -11,14 +11,11 @@ const widget = {
|
|||||||
login: {
|
login: {
|
||||||
endpoint: "events/events/per_month/?action=login",
|
endpoint: "events/events/per_month/?action=login",
|
||||||
},
|
},
|
||||||
loginv2: {
|
|
||||||
endpoint: "events/events/volume/?action=login&&history_days=1",
|
|
||||||
},
|
|
||||||
login_failed: {
|
login_failed: {
|
||||||
endpoint: "events/events/per_month/?action=login_failed",
|
endpoint: "events/events/per_month/?action=login_failed",
|
||||||
},
|
},
|
||||||
login_failedv2: {
|
datav2: {
|
||||||
endpoint: "events/events/volume/?action=login_failed&&history_days=1",
|
endpoint: "events/events/volume/?actions=login&actions=login_failed&actions=authorize_application&history_days=1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user