web: use OAuth
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
parent
d3cbe26106
commit
c0bb1f7347
|
@ -15,6 +15,7 @@ from django.views.decorators.clickjacking import xframe_options_sameorigin
|
||||||
from django.views.generic import View
|
from django.views.generic import View
|
||||||
from drf_spectacular.types import OpenApiTypes
|
from drf_spectacular.types import OpenApiTypes
|
||||||
from drf_spectacular.utils import OpenApiParameter, PolymorphicProxySerializer, extend_schema
|
from drf_spectacular.utils import OpenApiParameter, PolymorphicProxySerializer, extend_schema
|
||||||
|
from rest_framework.authentication import SessionAuthentication
|
||||||
from rest_framework.permissions import AllowAny
|
from rest_framework.permissions import AllowAny
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
from sentry_sdk import capture_exception
|
from sentry_sdk import capture_exception
|
||||||
|
@ -22,6 +23,7 @@ from sentry_sdk.api import set_tag
|
||||||
from sentry_sdk.hub import Hub
|
from sentry_sdk.hub import Hub
|
||||||
from structlog.stdlib import BoundLogger, get_logger
|
from structlog.stdlib import BoundLogger, get_logger
|
||||||
|
|
||||||
|
from authentik.api.authentication import TokenAuthentication
|
||||||
from authentik.core.models import Application
|
from authentik.core.models import Application
|
||||||
from authentik.events.models import Event, EventAction, cleanse_dict
|
from authentik.events.models import Event, EventAction, cleanse_dict
|
||||||
from authentik.flows.apps import HIST_FLOW_EXECUTION_STAGE_TIME
|
from authentik.flows.apps import HIST_FLOW_EXECUTION_STAGE_TIME
|
||||||
|
@ -103,6 +105,10 @@ class FlowExecutorView(APIView):
|
||||||
"""Flow executor, passing requests to Stage Views"""
|
"""Flow executor, passing requests to Stage Views"""
|
||||||
|
|
||||||
permission_classes = [AllowAny]
|
permission_classes = [AllowAny]
|
||||||
|
authentication_classes = [
|
||||||
|
TokenAuthentication,
|
||||||
|
SessionAuthentication,
|
||||||
|
]
|
||||||
|
|
||||||
flow: Flow
|
flow: Flow
|
||||||
|
|
||||||
|
|
|
@ -165,10 +165,7 @@ REST_FRAMEWORK = {
|
||||||
"rest_framework.parsers.JSONParser",
|
"rest_framework.parsers.JSONParser",
|
||||||
],
|
],
|
||||||
"DEFAULT_PERMISSION_CLASSES": ("authentik.rbac.permissions.ObjectPermissions",),
|
"DEFAULT_PERMISSION_CLASSES": ("authentik.rbac.permissions.ObjectPermissions",),
|
||||||
"DEFAULT_AUTHENTICATION_CLASSES": (
|
"DEFAULT_AUTHENTICATION_CLASSES": ("authentik.api.authentication.TokenAuthentication",),
|
||||||
"authentik.api.authentication.TokenAuthentication",
|
|
||||||
"rest_framework.authentication.SessionAuthentication",
|
|
||||||
),
|
|
||||||
"DEFAULT_RENDERER_CLASSES": [
|
"DEFAULT_RENDERER_CLASSES": [
|
||||||
"rest_framework.renderers.JSONRenderer",
|
"rest_framework.renderers.JSONRenderer",
|
||||||
],
|
],
|
||||||
|
|
|
@ -62,6 +62,7 @@
|
||||||
"fuse.js": "^7.0.0",
|
"fuse.js": "^7.0.0",
|
||||||
"lit": "^2.8.0",
|
"lit": "^2.8.0",
|
||||||
"mermaid": "^10.6.1",
|
"mermaid": "^10.6.1",
|
||||||
|
"oidc-client-ts": "^2.4.0",
|
||||||
"rapidoc": "^9.3.4",
|
"rapidoc": "^9.3.4",
|
||||||
"style-mod": "^4.1.0",
|
"style-mod": "^4.1.0",
|
||||||
"webcomponent-qr-code": "^1.2.0",
|
"webcomponent-qr-code": "^1.2.0",
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { ROUTES } from "@goauthentik/admin/Routes";
|
import { ROUTES } from "@goauthentik/admin/Routes";
|
||||||
|
import { OAuthInterface } from "@goauthentik/app/common/oauth/interface";
|
||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
import {
|
import {
|
||||||
EVENT_API_DRAWER_TOGGLE,
|
EVENT_API_DRAWER_TOGGLE,
|
||||||
|
@ -7,7 +8,6 @@ import {
|
||||||
import { configureSentry } from "@goauthentik/common/sentry";
|
import { configureSentry } from "@goauthentik/common/sentry";
|
||||||
import { me } from "@goauthentik/common/users";
|
import { me } from "@goauthentik/common/users";
|
||||||
import { WebsocketClient } from "@goauthentik/common/ws";
|
import { WebsocketClient } from "@goauthentik/common/ws";
|
||||||
import { Interface } from "@goauthentik/elements/Base";
|
|
||||||
import "@goauthentik/elements/ak-locale-context";
|
import "@goauthentik/elements/ak-locale-context";
|
||||||
import "@goauthentik/elements/enterprise/EnterpriseStatusBanner";
|
import "@goauthentik/elements/enterprise/EnterpriseStatusBanner";
|
||||||
import "@goauthentik/elements/messages/MessageContainer";
|
import "@goauthentik/elements/messages/MessageContainer";
|
||||||
|
@ -33,7 +33,7 @@ import { AdminApi, SessionUser, UiThemeEnum, Version } from "@goauthentik/api";
|
||||||
import "./AdminSidebar";
|
import "./AdminSidebar";
|
||||||
|
|
||||||
@customElement("ak-interface-admin")
|
@customElement("ak-interface-admin")
|
||||||
export class AdminInterface extends Interface {
|
export class AdminInterface extends OAuthInterface {
|
||||||
@property({ type: Boolean })
|
@property({ type: Boolean })
|
||||||
notificationDrawerOpen = getURLParam("notificationDrawerOpen", false);
|
notificationDrawerOpen = getURLParam("notificationDrawerOpen", false);
|
||||||
|
|
||||||
|
@ -92,7 +92,8 @@ export class AdminInterface extends Interface {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async firstUpdated(): Promise<void> {
|
async firstUpdated(_changedProperties: Map<PropertyKey, unknown>): Promise<void> {
|
||||||
|
super.firstUpdated(_changedProperties);
|
||||||
configureSentry(true);
|
configureSentry(true);
|
||||||
this.version = await new AdminApi(DEFAULT_CONFIG).adminVersionRetrieve();
|
this.version = await new AdminApi(DEFAULT_CONFIG).adminVersionRetrieve();
|
||||||
this.user = await me();
|
this.user = await me();
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
import "@goauthentik/admin/admin-overview/AdminOverviewPage";
|
import "@goauthentik/admin/admin-overview/AdminOverviewPage";
|
||||||
|
import "@goauthentik/app/common/oauth/callback";
|
||||||
|
import "@goauthentik/app/common/oauth/signout";
|
||||||
import { ID_REGEX, Route, SLUG_REGEX, UUID_REGEX } from "@goauthentik/elements/router/Route";
|
import { ID_REGEX, Route, SLUG_REGEX, UUID_REGEX } from "@goauthentik/elements/router/Route";
|
||||||
|
|
||||||
import { html } from "lit";
|
import { html } from "lit";
|
||||||
|
@ -8,6 +10,12 @@ export const ROUTES: Route[] = [
|
||||||
new Route(new RegExp("^/$")).redirect("/administration/overview"),
|
new Route(new RegExp("^/$")).redirect("/administration/overview"),
|
||||||
new Route(new RegExp("^#.*")).redirect("/administration/overview"),
|
new Route(new RegExp("^#.*")).redirect("/administration/overview"),
|
||||||
new Route(new RegExp("^/library$")).redirect("/if/user/", true),
|
new Route(new RegExp("^/library$")).redirect("/if/user/", true),
|
||||||
|
new Route(new RegExp("^/oauth-callback/(?<rest>.*)$"), async (args) => {
|
||||||
|
return html`<ak-oauth-callback params=${args.rest}></ak-oauth-callback>`;
|
||||||
|
}),
|
||||||
|
new Route(new RegExp("^/oauth-signout$"), async () => {
|
||||||
|
return html`<ak-oauth-signout></ak-oauth-signout>`;
|
||||||
|
}),
|
||||||
// statically imported since this is the default route
|
// statically imported since this is the default route
|
||||||
new Route(new RegExp("^/administration/overview$"), async () => {
|
new Route(new RegExp("^/administration/overview$"), async () => {
|
||||||
return html`<ak-admin-overview></ak-admin-overview>`;
|
return html`<ak-admin-overview></ak-admin-overview>`;
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { TokenMiddleware } from "@goauthentik/app/common/oauth/middleware";
|
||||||
import {
|
import {
|
||||||
CSRFMiddleware,
|
CSRFMiddleware,
|
||||||
EventMiddleware,
|
EventMiddleware,
|
||||||
|
@ -73,6 +74,7 @@ export const DEFAULT_CONFIG = new Configuration({
|
||||||
"sentry-trace": getMetaContent("sentry-trace"),
|
"sentry-trace": getMetaContent("sentry-trace"),
|
||||||
},
|
},
|
||||||
middleware: [
|
middleware: [
|
||||||
|
new TokenMiddleware(),
|
||||||
new CSRFMiddleware(),
|
new CSRFMiddleware(),
|
||||||
new EventMiddleware(),
|
new EventMiddleware(),
|
||||||
new LoggingMiddleware(globalAK().tenant),
|
new LoggingMiddleware(globalAK().tenant),
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
import { state } from "@goauthentik/app/common/oauth/constants";
|
||||||
|
import { settings } from "@goauthentik/app/common/oauth/settings";
|
||||||
|
import { refreshMe } from "@goauthentik/app/common/users";
|
||||||
|
import { User, UserManager } from "oidc-client-ts";
|
||||||
|
|
||||||
|
import { LitElement } from "lit";
|
||||||
|
import { customElement, property } from "lit/decorators.js";
|
||||||
|
|
||||||
|
@customElement("ak-oauth-callback")
|
||||||
|
export class OAuthCallback extends LitElement {
|
||||||
|
@property()
|
||||||
|
params?: string;
|
||||||
|
async firstUpdated(): Promise<void> {
|
||||||
|
const client = new UserManager(settings);
|
||||||
|
const user = (await client.signinCallback(`#${this.params}`)) as User;
|
||||||
|
const st = user.state as state;
|
||||||
|
window.location.assign(st.url);
|
||||||
|
refreshMe();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
export class state {
|
||||||
|
url: string;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.url = window.location.href;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { state } from "@goauthentik/app/common/oauth/constants";
|
||||||
|
import { settings } from "@goauthentik/app/common/oauth/settings";
|
||||||
|
import { Interface } from "@goauthentik/app/elements/Base";
|
||||||
|
import { UserManager } from "oidc-client-ts";
|
||||||
|
|
||||||
|
export class OAuthInterface extends Interface {
|
||||||
|
private async ensureLoggedIn() {
|
||||||
|
const client = new UserManager(settings);
|
||||||
|
const user = await client.getUser();
|
||||||
|
if (user !== null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (window.location.href.startsWith(settings.redirect_uri)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const s = new state();
|
||||||
|
await client.signinRedirect({
|
||||||
|
state: s,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async firstUpdated(_changedProperties: Map<PropertyKey, unknown>): Promise<void> {
|
||||||
|
await this.ensureLoggedIn();
|
||||||
|
await super.firstUpdated(_changedProperties);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { settings } from "@goauthentik/app/common/oauth/settings";
|
||||||
|
import { UserManager } from "oidc-client-ts";
|
||||||
|
|
||||||
|
import { FetchParams, Middleware, RequestContext } from "@goauthentik/api";
|
||||||
|
|
||||||
|
export class TokenMiddleware implements Middleware {
|
||||||
|
async pre?(context: RequestContext): Promise<FetchParams | void> {
|
||||||
|
const user = await new UserManager(settings).getUser();
|
||||||
|
if (user !== null) {
|
||||||
|
// @ts-ignore
|
||||||
|
context.init.headers["Authorization"] = `Bearer ${user.access_token}`;
|
||||||
|
}
|
||||||
|
return Promise.resolve(context);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { Log, OidcClientSettings, UserManagerSettings } from "oidc-client-ts";
|
||||||
|
|
||||||
|
Log.setLogger(console);
|
||||||
|
Log.setLevel(Log.DEBUG);
|
||||||
|
|
||||||
|
export const settings: OidcClientSettings & UserManagerSettings = {
|
||||||
|
authority: `${window.location.origin}/application/o/authentik-admin-interface/`,
|
||||||
|
redirect_uri: `${window.location.origin}/if/admin/#/oauth-callback/`,
|
||||||
|
client_id: "authentik-admin-interface",
|
||||||
|
scope: "openid profile email goauthentik.io/api",
|
||||||
|
response_mode: "fragment",
|
||||||
|
automaticSilentRenew: true,
|
||||||
|
};
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { settings } from "@goauthentik/app/common/oauth/settings";
|
||||||
|
import { UserManager } from "oidc-client-ts";
|
||||||
|
|
||||||
|
import { LitElement } from "lit";
|
||||||
|
import { customElement } from "lit/decorators.js";
|
||||||
|
|
||||||
|
@customElement("ak-oauth-signout")
|
||||||
|
export class OAuthSignout extends LitElement {
|
||||||
|
async firstUpdated(): Promise<void> {
|
||||||
|
const client = new UserManager(settings);
|
||||||
|
await client.signoutRedirect();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
import { EVENT_LOCALE_REQUEST } from "@goauthentik/common/constants";
|
import { EVENT_LOCALE_REQUEST } from "@goauthentik/common/constants";
|
||||||
|
|
||||||
import { CoreApi, ResponseError, SessionUser } from "@goauthentik/api";
|
import { CoreApi, SessionUser } from "@goauthentik/api";
|
||||||
|
|
||||||
let globalMePromise: Promise<SessionUser> | undefined;
|
let globalMePromise: Promise<SessionUser> | undefined;
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ export function me(): Promise<SessionUser> {
|
||||||
}
|
}
|
||||||
return user;
|
return user;
|
||||||
})
|
})
|
||||||
.catch((ex: ResponseError) => {
|
.catch(() => {
|
||||||
const defaultUser: SessionUser = {
|
const defaultUser: SessionUser = {
|
||||||
user: {
|
user: {
|
||||||
pk: -1,
|
pk: -1,
|
||||||
|
@ -48,14 +48,6 @@ export function me(): Promise<SessionUser> {
|
||||||
systemPermissions: [],
|
systemPermissions: [],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
if (ex.response?.status === 401 || ex.response?.status === 403) {
|
|
||||||
const relativeUrl = window.location
|
|
||||||
.toString()
|
|
||||||
.substring(window.location.origin.length);
|
|
||||||
window.location.assign(
|
|
||||||
`/flows/-/default/authentication/?next=${encodeURIComponent(relativeUrl)}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return defaultUser;
|
return defaultUser;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,7 +47,7 @@ export class SidebarUser extends AKElement {
|
||||||
html``,
|
html``,
|
||||||
)}
|
)}
|
||||||
</a>
|
</a>
|
||||||
<a href="/flows/-/default/invalidation/" class="pf-c-nav__link user-logout" id="logout">
|
<a href="#/oauth-signout" class="pf-c-nav__link user-logout" id="logout">
|
||||||
<i class="fas fa-sign-out-alt" aria-hidden="true"></i>
|
<i class="fas fa-sign-out-alt" aria-hidden="true"></i>
|
||||||
</a>
|
</a>
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import "@goauthentik/app/common/oauth/callback";
|
||||||
|
import "@goauthentik/app/common/oauth/signout";
|
||||||
import { Route } from "@goauthentik/elements/router/Route";
|
import { Route } from "@goauthentik/elements/router/Route";
|
||||||
import "@goauthentik/user/LibraryPage/LibraryPage";
|
import "@goauthentik/user/LibraryPage/LibraryPage";
|
||||||
|
|
||||||
|
@ -7,6 +9,12 @@ export const ROUTES: Route[] = [
|
||||||
// Prevent infinite Shell loops
|
// Prevent infinite Shell loops
|
||||||
new Route(new RegExp("^/$")).redirect("/library"),
|
new Route(new RegExp("^/$")).redirect("/library"),
|
||||||
new Route(new RegExp("^#.*")).redirect("/library"),
|
new Route(new RegExp("^#.*")).redirect("/library"),
|
||||||
|
new Route(new RegExp("^/oauth-callback/(?<rest>.*)$"), async (args) => {
|
||||||
|
return html`<ak-oauth-callback params=${args.rest}></ak-oauth-callback>`;
|
||||||
|
}),
|
||||||
|
new Route(new RegExp("^/oauth-signout$"), async () => {
|
||||||
|
return html`<ak-oauth-signout></ak-oauth-signout>`;
|
||||||
|
}),
|
||||||
new Route(new RegExp("^/library$"), async () => html`<ak-library></ak-library>`),
|
new Route(new RegExp("^/library$"), async () => html`<ak-library></ak-library>`),
|
||||||
new Route(new RegExp("^/settings$"), async () => {
|
new Route(new RegExp("^/settings$"), async () => {
|
||||||
await import("@goauthentik/user/user-settings/UserSettingsPage");
|
await import("@goauthentik/user/user-settings/UserSettingsPage");
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { OAuthInterface } from "@goauthentik/app/common/oauth/interface";
|
||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
import {
|
import {
|
||||||
EVENT_API_DRAWER_TOGGLE,
|
EVENT_API_DRAWER_TOGGLE,
|
||||||
|
@ -9,7 +10,6 @@ import { UserDisplay } from "@goauthentik/common/ui/config";
|
||||||
import { me } from "@goauthentik/common/users";
|
import { me } from "@goauthentik/common/users";
|
||||||
import { first } from "@goauthentik/common/utils";
|
import { first } from "@goauthentik/common/utils";
|
||||||
import { WebsocketClient } from "@goauthentik/common/ws";
|
import { WebsocketClient } from "@goauthentik/common/ws";
|
||||||
import { Interface } from "@goauthentik/elements/Base";
|
|
||||||
import "@goauthentik/elements/ak-locale-context";
|
import "@goauthentik/elements/ak-locale-context";
|
||||||
import "@goauthentik/elements/buttons/ActionButton";
|
import "@goauthentik/elements/buttons/ActionButton";
|
||||||
import "@goauthentik/elements/enterprise/EnterpriseStatusBanner";
|
import "@goauthentik/elements/enterprise/EnterpriseStatusBanner";
|
||||||
|
@ -41,7 +41,7 @@ import PFDisplay from "@patternfly/patternfly/utilities/Display/display.css";
|
||||||
import { CoreApi, EventsApi, SessionUser } from "@goauthentik/api";
|
import { CoreApi, EventsApi, SessionUser } from "@goauthentik/api";
|
||||||
|
|
||||||
@customElement("ak-interface-user")
|
@customElement("ak-interface-user")
|
||||||
export class UserInterface extends Interface {
|
export class UserInterface extends OAuthInterface {
|
||||||
@property({ type: Boolean })
|
@property({ type: Boolean })
|
||||||
notificationDrawerOpen = getURLParam("notificationDrawerOpen", false);
|
notificationDrawerOpen = getURLParam("notificationDrawerOpen", false);
|
||||||
|
|
||||||
|
@ -141,12 +141,13 @@ export class UserInterface extends Interface {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
window.addEventListener(EVENT_WS_MESSAGE, () => {
|
window.addEventListener(EVENT_WS_MESSAGE, () => {
|
||||||
this.firstUpdated();
|
this.firstUpdated(new Map());
|
||||||
});
|
});
|
||||||
configureSentry(true);
|
configureSentry(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
async firstUpdated(): Promise<void> {
|
async firstUpdated(_changedProperties: Map<PropertyKey, unknown>): Promise<void> {
|
||||||
|
super.firstUpdated(_changedProperties);
|
||||||
this.me = await me();
|
this.me = await me();
|
||||||
const notifications = await new EventsApi(DEFAULT_CONFIG).eventsNotificationsList({
|
const notifications = await new EventsApi(DEFAULT_CONFIG).eventsNotificationsList({
|
||||||
seen: false,
|
seen: false,
|
||||||
|
@ -275,10 +276,7 @@ export class UserInterface extends Interface {
|
||||||
</div>`
|
</div>`
|
||||||
: html``}
|
: html``}
|
||||||
<div class="pf-c-page__header-tools-item">
|
<div class="pf-c-page__header-tools-item">
|
||||||
<a
|
<a href="#/oauth-signou" class="pf-c-button pf-m-plain">
|
||||||
href="/flows/-/default/invalidation/"
|
|
||||||
class="pf-c-button pf-m-plain"
|
|
||||||
>
|
|
||||||
<pf-tooltip position="top" content=${msg("Sign out")}>
|
<pf-tooltip position="top" content=${msg("Sign out")}>
|
||||||
<i class="fas fa-sign-out-alt" aria-hidden="true"></i>
|
<i class="fas fa-sign-out-alt" aria-hidden="true"></i>
|
||||||
</pf-tooltip>
|
</pf-tooltip>
|
||||||
|
|
Reference in New Issue