Static SPA (#648)
* core: initial migration to /if Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * core: move jsi18n to api Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * tests: fix static URLs in tests Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * web: add new html files to rollup Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * web: fix rollup config and nginx config Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * core: add Impersonation support to user API Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * web: add banner for impersonation Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * tests: fix test_user function for new User API Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * flows: add background to API Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * web: set background from flow API Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * core: make root view login_required for redirect Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * flows: redirect to root-redirect instead of if-admin direct Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * api: add header to prevent Authorization Basic prompt in browser Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * web: redirect to root when user/me request fails Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
@ -9,6 +9,7 @@ export const DEFAULT_CONFIG = new Configuration({
|
||||
basePath: "/api/v2beta",
|
||||
headers: {
|
||||
"X-CSRFToken": getCookie("authentik_csrf"),
|
||||
"X-Authentik-Prevent-Basic": "true"
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@ -1,10 +1,15 @@
|
||||
import { CoreApi, User } from "authentik-api";
|
||||
import { CoreApi, SessionUser } from "authentik-api";
|
||||
import { DEFAULT_CONFIG } from "./Config";
|
||||
|
||||
let _globalMePromise: Promise<User>;
|
||||
export function me(): Promise<User> {
|
||||
let _globalMePromise: Promise<SessionUser>;
|
||||
export function me(): Promise<SessionUser> {
|
||||
if (!_globalMePromise) {
|
||||
_globalMePromise = new CoreApi(DEFAULT_CONFIG).coreUsersMe({});
|
||||
_globalMePromise = new CoreApi(DEFAULT_CONFIG).coreUsersMe({}).catch((ex) => {
|
||||
if (ex.status === 401 || ex.status === 403) {
|
||||
window.location.assign("/");
|
||||
}
|
||||
return ex;
|
||||
});
|
||||
}
|
||||
return _globalMePromise;
|
||||
}
|
||||
|
||||
@ -56,7 +56,7 @@ html > form > input {
|
||||
|
||||
/* ensure background on non-flow pages match */
|
||||
.pf-c-background-image::before {
|
||||
background-image: url("/static/dist/assets/images/flow_background.jpg");
|
||||
background-image: var(--ak-flow-background, url("/static/dist/assets/images/flow_background.jpg"));
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
|
||||
27
web/src/elements/Banner.ts
Normal file
27
web/src/elements/Banner.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { customElement, CSSResult, html, LitElement, property, TemplateResult } from "lit-element";
|
||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||
import PFFlex from "@patternfly/patternfly/layouts/Flex/flex.css";
|
||||
import PFBanner from "@patternfly/patternfly/components/Banner/banner.css";
|
||||
import AKGlobal from "../authentik.css";
|
||||
|
||||
@customElement("ak-banner")
|
||||
export class Banner extends LitElement {
|
||||
|
||||
@property()
|
||||
level = "pf-m-warning";
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [PFBase, PFBanner, PFFlex, AKGlobal];
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
return html`<div class="pf-c-banner ${this.level} pf-m-sticky">
|
||||
<div class="pf-l-flex pf-m-justify-content-center pf-m-justify-content-space-between-on-lg pf-m-nowrap" style="height: 100%;">
|
||||
<div class="pf-u-display-none pf-u-display-block-on-lg">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
}
|
||||
@ -36,7 +36,7 @@ export class SidebarUser extends LitElement {
|
||||
return html`
|
||||
<a href="#/user" class="pf-c-nav__link user-avatar" id="user-settings">
|
||||
${until(me().then((u) => {
|
||||
return html`<img class="pf-c-avatar" src="${ifDefined(u.avatar)}" alt="" />`;
|
||||
return html`<img class="pf-c-avatar" src="${ifDefined(u.user.avatar)}" alt="" />`;
|
||||
}), html``)}
|
||||
</a>
|
||||
<ak-notification-trigger class="pf-c-nav__link user-notifications">
|
||||
|
||||
@ -83,7 +83,13 @@ export class FlowExecutor extends LitElement implements StageHost {
|
||||
this.addEventListener("ak-flow-submit", () => {
|
||||
this.submit();
|
||||
});
|
||||
this.flowSlug = window.location.pathname.split("/")[2];
|
||||
this.flowSlug = window.location.pathname.split("/")[3];
|
||||
}
|
||||
|
||||
setBackground(url: string): void {
|
||||
this.shadowRoot?.querySelectorAll<HTMLDivElement>(".pf-c-background-image").forEach((bg) => {
|
||||
bg.style.setProperty("--ak-flow-background", `url('${url}')`);
|
||||
});
|
||||
}
|
||||
|
||||
submit<T>(formData?: T): Promise<void> {
|
||||
@ -95,6 +101,9 @@ export class FlowExecutor extends LitElement implements StageHost {
|
||||
return challengeRaw.raw.json();
|
||||
}).then((data) => {
|
||||
this.challenge = data;
|
||||
if (this.challenge?.background) {
|
||||
this.setBackground(this.challenge.background);
|
||||
}
|
||||
}).catch((e) => {
|
||||
this.errorMessage(e);
|
||||
}).finally(() => {
|
||||
@ -113,6 +122,9 @@ export class FlowExecutor extends LitElement implements StageHost {
|
||||
return challengeRaw.raw.json();
|
||||
}).then((challenge) => {
|
||||
this.challenge = challenge as Challenge;
|
||||
if (this.challenge?.background) {
|
||||
this.setBackground(this.challenge.background);
|
||||
}
|
||||
}).catch((e) => {
|
||||
// Catch JSON or Update errors
|
||||
this.errorMessage(e);
|
||||
|
||||
@ -1,44 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
|
||||
<title>authentik</title>
|
||||
<link rel="icon" type="image/png" href="/static/dist/assets/icons/icon.png" />
|
||||
<link rel="shortcut icon" type="image/png" href="/static/dist/assets/icons/icon.png" />
|
||||
<link
|
||||
rel="stylesheet"
|
||||
type="text/css"
|
||||
href="/static/node_modules/%40patternfly/patternfly/patternfly.css"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
type="text/css"
|
||||
href="/static/node_modules/%40patternfly/patternfly/patternfly-addons.css"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
type="text/css"
|
||||
href="/static/node_modules/%40fortawesome/fontawesome-free/css/fontawesome.min.css"
|
||||
/>
|
||||
<link rel="stylesheet" type="text/css" href="/static/authentik/authentik.css" />
|
||||
<script src="/static/dist/main.js" type="module"></script>
|
||||
</head>
|
||||
<body>
|
||||
<ak-message-container></ak-message-container>
|
||||
<div class="pf-c-page">
|
||||
<a class="pf-c-skip-to-content pf-c-button pf-m-primary" href="#main-content"
|
||||
>Skip to content</a
|
||||
>
|
||||
<ak-sidebar class="pf-c-page__sidebar"> </ak-sidebar>
|
||||
<ak-router-outlet
|
||||
role="main"
|
||||
class="pf-c-page__main"
|
||||
tabindex="-1"
|
||||
id="main-content"
|
||||
defaultUrl="/library"
|
||||
>
|
||||
</ak-router-outlet>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@ -10,7 +10,7 @@ export const SIDEBAR_ITEMS: SidebarItem[] = [
|
||||
new SidebarItem("Overview", "/administration/overview"),
|
||||
new SidebarItem("System Tasks", "/administration/system-tasks"),
|
||||
).when((): Promise<boolean> => {
|
||||
return me().then(u => u.isSuperuser||false);
|
||||
return me().then(u => u.user.isSuperuser||false);
|
||||
}),
|
||||
new SidebarItem("Events").children(
|
||||
new SidebarItem("Log", "/events/log").activeWhen(
|
||||
@ -19,7 +19,7 @@ export const SIDEBAR_ITEMS: SidebarItem[] = [
|
||||
new SidebarItem("Notification Rules", "/events/rules"),
|
||||
new SidebarItem("Notification Transports", "/events/transports"),
|
||||
).when((): Promise<boolean> => {
|
||||
return me().then(u => u.isSuperuser||false);
|
||||
return me().then(u => u.user.isSuperuser||false);
|
||||
}),
|
||||
new SidebarItem("Resources").children(
|
||||
new SidebarItem("Applications", "/core/applications").activeWhen(
|
||||
@ -34,13 +34,13 @@ export const SIDEBAR_ITEMS: SidebarItem[] = [
|
||||
new SidebarItem("Outposts", "/outpost/outposts"),
|
||||
new SidebarItem("Outpost Service Connections", "/outpost/service-connections"),
|
||||
).when((): Promise<boolean> => {
|
||||
return me().then(u => u.isSuperuser||false);
|
||||
return me().then(u => u.user.isSuperuser||false);
|
||||
}),
|
||||
new SidebarItem("Customisation").children(
|
||||
new SidebarItem("Policies", "/policy/policies"),
|
||||
new SidebarItem("Property Mappings", "/core/property-mappings"),
|
||||
).when((): Promise<boolean> => {
|
||||
return me().then(u => u.isSuperuser||false);
|
||||
return me().then(u => u.user.isSuperuser||false);
|
||||
}),
|
||||
new SidebarItem("Flows").children(
|
||||
new SidebarItem("Flows", "/flow/flows").activeWhen(`^/flow/flows/(?<slug>${SLUG_REGEX})$`),
|
||||
@ -48,7 +48,7 @@ export const SIDEBAR_ITEMS: SidebarItem[] = [
|
||||
new SidebarItem("Prompts", "/flow/stages/prompts"),
|
||||
new SidebarItem("Invitations", "/flow/stages/invitations"),
|
||||
).when((): Promise<boolean> => {
|
||||
return me().then(u => u.isSuperuser||false);
|
||||
return me().then(u => u.user.isSuperuser||false);
|
||||
}),
|
||||
new SidebarItem("Identity & Cryptography").children(
|
||||
new SidebarItem("User", "/identity/users").activeWhen(`^/identity/users/(?<id>${ID_REGEX})$`),
|
||||
@ -56,7 +56,7 @@ export const SIDEBAR_ITEMS: SidebarItem[] = [
|
||||
new SidebarItem("Certificates", "/crypto/certificates"),
|
||||
new SidebarItem("Tokens", "/core/tokens"),
|
||||
).when((): Promise<boolean> => {
|
||||
return me().then(u => u.isSuperuser||false);
|
||||
return me().then(u => u.user.isSuperuser||false);
|
||||
}),
|
||||
];
|
||||
|
||||
|
||||
@ -10,6 +10,10 @@ import "../elements/router/RouterOutlet";
|
||||
import "../elements/messages/MessageContainer";
|
||||
import "../elements/sidebar/SidebarHamburger";
|
||||
import "../elements/notifications/NotificationDrawer";
|
||||
import "../elements/Banner";
|
||||
import { until } from "lit-html/directives/until";
|
||||
import { me } from "../api/Users";
|
||||
import { gettext } from "django";
|
||||
|
||||
export abstract class Interface extends LitElement {
|
||||
@property({type: Boolean})
|
||||
@ -44,6 +48,17 @@ export abstract class Interface extends LitElement {
|
||||
|
||||
render(): TemplateResult {
|
||||
return html`
|
||||
${until(me().then((u) => {
|
||||
if (u.original) {
|
||||
return html`<ak-banner>
|
||||
${gettext(`You're currently impersonating ${u.user.username}.`)}
|
||||
<a href=${`/-/impersonation/end/?back=${window.location.pathname}%23${window.location.hash}`}>
|
||||
${gettext("Stop impersonation")}
|
||||
</a>
|
||||
</ak-banner>`;
|
||||
}
|
||||
return html``;
|
||||
}))}
|
||||
<div class="pf-c-page">
|
||||
<ak-sidebar-hamburger>
|
||||
</ak-sidebar-hamburger>
|
||||
|
||||
17
web/src/interfaces/admin/index.html
Normal file
17
web/src/interfaces/admin/index.html
Normal file
@ -0,0 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
<link rel="icon" type="image/png" href="/static/dist/assets/icons/icon.png?v=2021.3.4">
|
||||
<link rel="shortcut icon" type="image/png" href="/static/dist/assets/icons/icon.png?v=2021.3.4">
|
||||
<link rel="stylesheet" type="text/css" href="/static/dist/patternfly-base.css?v=2021.3.4">
|
||||
<link rel="stylesheet" type="text/css" href="/static/dist/authentik.css?v=2021.3.4">
|
||||
<script src="/api/jsi18n/?v=2021.3.4"></script>
|
||||
<script src="/static/dist/main.js?v=2021.3.4" type="module"></script>
|
||||
</head>
|
||||
<body>
|
||||
<ak-message-container></ak-message-container>
|
||||
<ak-interface-admin></ak-interface-admin>
|
||||
</body>
|
||||
</html>
|
||||
16
web/src/interfaces/flow/index.html
Normal file
16
web/src/interfaces/flow/index.html
Normal file
@ -0,0 +1,16 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
<link rel="icon" type="image/png" href="/static/dist/assets/icons/icon.png?v=2021.3.4">
|
||||
<link rel="shortcut icon" type="image/png" href="/static/dist/assets/icons/icon.png?v=2021.3.4">
|
||||
<link rel="stylesheet" type="text/css" href="/static/dist/patternfly-base.css?v=2021.3.4">
|
||||
<link rel="stylesheet" type="text/css" href="/static/dist/authentik.css?v=2021.3.4">
|
||||
<script src="/api/jsi18n/?v=2021.3.4"></script>
|
||||
<script src="/static/dist/flow.js?v=2021.3.4" type="module"></script>
|
||||
</head>
|
||||
<body>
|
||||
<ak-flow-executor></ak-flow-executor>
|
||||
</body>
|
||||
</html>
|
||||
@ -61,7 +61,7 @@ export class LibraryApplication extends LitElement {
|
||||
? html`<img class="app-icon pf-c-avatar" src="${ifDefined(this.application.metaIcon)}" alt="Application Icon"/>`
|
||||
: html`<i class="fas fas fa-share-square"></i>`}
|
||||
${until(me().then((u) => {
|
||||
if (!u.isSuperuser) return html``;
|
||||
if (!u.user.isSuperuser) return html``;
|
||||
return html`
|
||||
<a href="#/core/applications/${this.application?.slug}">
|
||||
<i class="fas fa-pencil-alt"></i>
|
||||
|
||||
@ -110,7 +110,7 @@ export class UserListPage extends TablePage<User> {
|
||||
<ak-action-button method="GET" url="${AdminURLManager.users(`${item.pk}/reset/`)}">
|
||||
${gettext("Reset Password")}
|
||||
</ak-action-button>
|
||||
<a class="pf-c-button pf-m-tertiary" href="${`-/impersonation/${item.pk}/`}">
|
||||
<a class="pf-c-button pf-m-tertiary" href="${`/-/impersonation/${item.pk}/`}">
|
||||
${gettext("Impersonate")}
|
||||
</a>`,
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user