web: re-format with prettier

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
Jens Langhammer
2021-08-03 17:52:21 +02:00
parent 77ed25ae34
commit 2c60ec50be
218 changed files with 11696 additions and 8225 deletions

View File

@ -1,5 +1,13 @@
import { t } from "@lingui/macro";
import { css, CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element";
import {
css,
CSSResult,
customElement,
html,
LitElement,
property,
TemplateResult,
} from "lit-element";
import { ifDefined } from "lit-html/directives/if-defined";
import { until } from "lit-html/directives/until";
import { Application, CoreApi } from "authentik-api";
@ -20,11 +28,15 @@ import PFGallery from "@patternfly/patternfly/layouts/Gallery/gallery.css";
@customElement("ak-library-app")
export class LibraryApplication extends LitElement {
@property({attribute: false})
@property({ attribute: false })
application?: Application;
static get styles(): CSSResult[] {
return [PFBase, PFCard, PFAvatar, AKGlobal,
return [
PFBase,
PFCard,
PFAvatar,
AKGlobal,
css`
a {
height: 100%;
@ -48,7 +60,7 @@ export class LibraryApplication extends LitElement {
justify-content: center;
margin-right: 0.25em;
}
`
`,
];
}
@ -56,19 +68,28 @@ export class LibraryApplication extends LitElement {
if (!this.application) {
return html`<ak-spinner></ak-spinner>`;
}
return html` <a href="${ifDefined(this.application.launchUrl ?? "")}" class="pf-c-card pf-m-hoverable pf-m-compact">
return html` <a
href="${ifDefined(this.application.launchUrl ?? "")}"
class="pf-c-card pf-m-hoverable pf-m-compact"
>
<div class="pf-c-card__header">
${this.application.metaIcon
? html`<img class="app-icon pf-c-avatar" src="${ifDefined(this.application.metaIcon)}" alt="Application Icon"/>`
? 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.user.isSuperuser) return html``;
return html`
<a href="#/core/applications/${this.application?.slug}">
<i class="fas fa-pencil-alt"></i>
</a>
`;
}))}
${until(
me().then((u) => {
if (!u.user.isSuperuser) return html``;
return html`
<a href="#/core/applications/${this.application?.slug}">
<i class="fas fa-pencil-alt"></i>
</a>
`;
}),
)}
</div>
<div class="pf-c-card__title">
<p id="card-1-check-label">${this.application.name}</p>
@ -76,15 +97,13 @@ export class LibraryApplication extends LitElement {
<small>${this.application.metaPublisher}</small>
</div>
</div>
<div class="pf-c-card__body">${truncate(this.application.metaDescription, 35)}</div>
</a>`;
}
}
@customElement("ak-library")
export class LibraryPage extends LitElement {
@property({attribute: false})
@property({ attribute: false })
apps?: AKResponse<Application>;
pageTitle(): string {
@ -120,20 +139,23 @@ export class LibraryPage extends LitElement {
renderApps(): TemplateResult {
return html`<div class="pf-l-gallery pf-m-gutter">
${this.apps?.results.map((app) => html`<ak-library-app .application=${app}></ak-library-app>`)}
${this.apps?.results.map(
(app) => html`<ak-library-app .application=${app}></ak-library-app>`,
)}
</div>`;
}
render(): TemplateResult {
return html`<main role="main" class="pf-c-page__main" tabindex="-1" id="main-content">
<ak-page-header
icon="pf-icon pf-icon-applications"
header=${t`Applications`}>
<ak-page-header icon="pf-icon pf-icon-applications" header=${t`Applications`}>
</ak-page-header>
<section class="pf-c-page__main-section">
${loading(this.apps, html`${(this.apps?.results.length || 0) > 0 ?
this.renderApps() :
this.renderEmptyState()}`)}
${loading(
this.apps,
html`${(this.apps?.results.length || 0) > 0
? this.renderApps()
: this.renderEmptyState()}`,
)}
</section>
</main>`;
}

View File

@ -26,101 +26,173 @@ import "../../elements/PageHeader";
@customElement("ak-admin-overview")
export class AdminOverviewPage extends LitElement {
static get styles(): CSSResult[] {
return [PFGrid, PFPage, PFContent, AKGlobal, css`
.row-divider {
margin-top: -4px;
margin-bottom: -4px;
}
.graph-container {
height: 20em;
}
.big-graph-container {
height: 35em;
}
.card-container {
max-height: 10em;
}
`];
return [
PFGrid,
PFPage,
PFContent,
AKGlobal,
css`
.row-divider {
margin-top: -4px;
margin-bottom: -4px;
}
.graph-container {
height: 20em;
}
.big-graph-container {
height: 35em;
}
.card-container {
max-height: 10em;
}
`,
];
}
render(): TemplateResult {
return html`
<ak-page-header
icon=""
header=${t`System Overview`}
description=${t`General system status`}>
</ak-page-header>
<section class="pf-c-page__main-section">
<div class="pf-l-grid pf-m-gutter">
<!-- row 1 -->
<div class="pf-l-grid__item pf-m-6-col pf-m-4-col-on-xl pf-m-2-col-on-2xl graph-container">
<ak-aggregate-card icon="pf-icon pf-icon-infrastructure" header=${t`Policies`} headerLink="#/policy/policies">
<ak-admin-status-chart-policy></ak-admin-status-chart-policy>
</ak-aggregate-card>
return html` <ak-page-header
icon=""
header=${t`System Overview`}
description=${t`General system status`}
>
</ak-page-header>
<section class="pf-c-page__main-section">
<div class="pf-l-grid pf-m-gutter">
<!-- row 1 -->
<div
class="pf-l-grid__item pf-m-6-col pf-m-4-col-on-xl pf-m-2-col-on-2xl graph-container"
>
<ak-aggregate-card
icon="pf-icon pf-icon-infrastructure"
header=${t`Policies`}
headerLink="#/policy/policies"
>
<ak-admin-status-chart-policy></ak-admin-status-chart-policy>
</ak-aggregate-card>
</div>
<div
class="pf-l-grid__item pf-m-6-col pf-m-4-col-on-xl pf-m-2-col-on-2xl graph-container"
>
<ak-aggregate-card
icon="pf-icon pf-icon-server"
header=${t`Flows`}
headerLink="#/flow/flows"
>
<ak-admin-status-chart-flow></ak-admin-status-chart-flow>
</ak-aggregate-card>
</div>
<div
class="pf-l-grid__item pf-m-6-col pf-m-4-col-on-xl pf-m-2-col-on-2xl graph-container"
>
<ak-aggregate-card
icon="fa fa-sync-alt"
header=${t`Outpost status`}
headerLink="#/outpost/outposts"
>
<ak-admin-status-chart-outpost></ak-admin-status-chart-outpost>
</ak-aggregate-card>
</div>
<div
class="pf-l-grid__item pf-m-6-col pf-m-4-col-on-xl pf-m-2-col-on-2xl graph-container"
>
<ak-aggregate-card
icon="fa fa-sync-alt"
header=${t`Users`}
headerLink="#/identity/users"
>
<ak-admin-status-chart-user-count></ak-admin-status-chart-user-count>
</ak-aggregate-card>
</div>
<div
class="pf-l-grid__item pf-m-6-col pf-m-4-col-on-xl pf-m-2-col-on-2xl graph-container"
>
<ak-aggregate-card
icon="fa fa-sync-alt"
header=${t`Groups`}
headerLink="#/identity/groups"
>
<ak-admin-status-chart-group-count></ak-admin-status-chart-group-count>
</ak-aggregate-card>
</div>
<div
class="pf-l-grid__item pf-m-6-col pf-m-4-col-on-xl pf-m-2-col-on-2xl graph-container"
>
<ak-aggregate-card
icon="fa fa-sync-alt"
header=${t`LDAP Sync status`}
headerLink="#/core/sources"
>
<ak-admin-status-chart-ldap-sync></ak-admin-status-chart-ldap-sync>
</ak-aggregate-card>
</div>
<div class="pf-l-grid__item pf-m-12-col row-divider">
<hr />
</div>
<!-- row 2 -->
<div
class="pf-l-grid__item pf-m-6-col pf-m-4-col-on-md pf-m-4-col-on-xl card-container"
>
<ak-admin-status-version
icon="pf-icon pf-icon-bundle"
header=${t`Version`}
headerLink="https://github.com/goauthentik/authentik/releases"
>
</ak-admin-status-version>
</div>
<div
class="pf-l-grid__item pf-m-6-col pf-m-2-col-on-md pf-m-2-col-on-xl card-container"
>
<ak-admin-status-card-backup
icon="fa fa-database"
header=${t`Backup status`}
headerLink="#/administration/system-tasks"
>
</ak-admin-status-card-backup>
</div>
<div
class="pf-l-grid__item pf-m-6-col pf-m-3-col-on-md pf-m-3-col-on-xl card-container"
>
<ak-admin-status-card-workers
icon="pf-icon pf-icon-server"
header=${t`Workers`}
>
</ak-admin-status-card-workers>
</div>
<div
class="pf-l-grid__item pf-m-6-col pf-m-3-col-on-md pf-m-3-col-on-xl card-container"
>
<ak-admin-status-system
icon="pf-icon pf-icon-server"
header=${t`System status`}
>
</ak-admin-status-system>
</div>
<div class="pf-l-grid__item pf-m-12-col row-divider">
<hr />
</div>
<!-- row 3 -->
<div
class="pf-l-grid__item pf-m-12-col pf-m-6-col-on-xl pf-m-8-col-on-2xl big-graph-container"
>
<ak-aggregate-card
icon="pf-icon pf-icon-server"
header=${t`Logins over the last 24 hours`}
>
<ak-charts-admin-login></ak-charts-admin-login>
</ak-aggregate-card>
</div>
<div
class="pf-l-grid__item pf-m-12-col pf-m-6-col-on-xl pf-m-4-col-on-2xl big-graph-container"
>
<ak-aggregate-card
icon="pf-icon pf-icon-server"
header=${t`Apps with most usage`}
>
<ak-top-applications-table></ak-top-applications-table>
</ak-aggregate-card>
</div>
</div>
<div class="pf-l-grid__item pf-m-6-col pf-m-4-col-on-xl pf-m-2-col-on-2xl graph-container">
<ak-aggregate-card icon="pf-icon pf-icon-server" header=${t`Flows`} headerLink="#/flow/flows">
<ak-admin-status-chart-flow></ak-admin-status-chart-flow>
</ak-aggregate-card>
</div>
<div class="pf-l-grid__item pf-m-6-col pf-m-4-col-on-xl pf-m-2-col-on-2xl graph-container">
<ak-aggregate-card icon="fa fa-sync-alt" header=${t`Outpost status`} headerLink="#/outpost/outposts">
<ak-admin-status-chart-outpost></ak-admin-status-chart-outpost>
</ak-aggregate-card>
</div>
<div class="pf-l-grid__item pf-m-6-col pf-m-4-col-on-xl pf-m-2-col-on-2xl graph-container">
<ak-aggregate-card icon="fa fa-sync-alt" header=${t`Users`} headerLink="#/identity/users">
<ak-admin-status-chart-user-count></ak-admin-status-chart-user-count>
</ak-aggregate-card>
</div>
<div class="pf-l-grid__item pf-m-6-col pf-m-4-col-on-xl pf-m-2-col-on-2xl graph-container">
<ak-aggregate-card icon="fa fa-sync-alt" header=${t`Groups`} headerLink="#/identity/groups">
<ak-admin-status-chart-group-count></ak-admin-status-chart-group-count>
</ak-aggregate-card>
</div>
<div class="pf-l-grid__item pf-m-6-col pf-m-4-col-on-xl pf-m-2-col-on-2xl graph-container">
<ak-aggregate-card icon="fa fa-sync-alt" header=${t`LDAP Sync status`} headerLink="#/core/sources">
<ak-admin-status-chart-ldap-sync></ak-admin-status-chart-ldap-sync>
</ak-aggregate-card>
</div>
<div class="pf-l-grid__item pf-m-12-col row-divider">
<hr>
</div>
<!-- row 2 -->
<div class="pf-l-grid__item pf-m-6-col pf-m-4-col-on-md pf-m-4-col-on-xl card-container">
<ak-admin-status-version icon="pf-icon pf-icon-bundle" header=${t`Version`} headerLink="https://github.com/goauthentik/authentik/releases">
</ak-admin-status-version>
</div>
<div class="pf-l-grid__item pf-m-6-col pf-m-2-col-on-md pf-m-2-col-on-xl card-container">
<ak-admin-status-card-backup icon="fa fa-database" header=${t`Backup status`} headerLink="#/administration/system-tasks">
</ak-admin-status-card-backup>
</div>
<div class="pf-l-grid__item pf-m-6-col pf-m-3-col-on-md pf-m-3-col-on-xl card-container">
<ak-admin-status-card-workers icon="pf-icon pf-icon-server" header=${t`Workers`}>
</ak-admin-status-card-workers>
</div>
<div class="pf-l-grid__item pf-m-6-col pf-m-3-col-on-md pf-m-3-col-on-xl card-container">
<ak-admin-status-system icon="pf-icon pf-icon-server" header=${t`System status`}>
</ak-admin-status-system>
</div>
<div class="pf-l-grid__item pf-m-12-col row-divider">
<hr>
</div>
<!-- row 3 -->
<div class="pf-l-grid__item pf-m-12-col pf-m-6-col-on-xl pf-m-8-col-on-2xl big-graph-container">
<ak-aggregate-card icon="pf-icon pf-icon-server" header=${t`Logins over the last 24 hours`}>
<ak-charts-admin-login></ak-charts-admin-login>
</ak-aggregate-card>
</div>
<div class="pf-l-grid__item pf-m-12-col pf-m-6-col-on-xl pf-m-4-col-on-2xl big-graph-container">
<ak-aggregate-card icon="pf-icon pf-icon-server" header=${t`Apps with most usage`}>
<ak-top-applications-table></ak-top-applications-table>
</ak-aggregate-card>
</div>
</div>
</section>`;
</section>`;
}
}

View File

@ -9,8 +9,7 @@ import { DEFAULT_CONFIG } from "../../api/Config";
@customElement("ak-top-applications-table")
export class TopApplicationsTable extends LitElement {
@property({attribute: false})
@property({ attribute: false })
topN?: EventTopPerUser[];
static get styles(): CSSResult[] {
@ -18,41 +17,43 @@ export class TopApplicationsTable extends LitElement {
}
firstUpdated(): void {
new EventsApi(DEFAULT_CONFIG).eventsEventsTopPerUserList({
action: "authorize_application",
topN: 11,
}).then((events) => {
this.topN = events;
});
new EventsApi(DEFAULT_CONFIG)
.eventsEventsTopPerUserList({
action: "authorize_application",
topN: 11,
})
.then((events) => {
this.topN = events;
});
}
renderRow(event: EventTopPerUser): TemplateResult {
return html`<tr role="row">
<td role="cell">${event.application.name}</td>
<td role="cell">${event.countedEvents}</td>
<td role="cell">
${event.application.name}
</td>
<td role="cell">
${event.countedEvents}
</td>
<td role="cell">
<progress value="${event.countedEvents}" max="${this.topN ? this.topN[0].countedEvents : 0}"></progress>
<progress
value="${event.countedEvents}"
max="${this.topN ? this.topN[0].countedEvents : 0}"
></progress>
</td>
</tr>`;
}
render(): TemplateResult {
return html`<table class="pf-c-table pf-m-compact" role="grid">
<thead>
<tr role="row">
<th role="columnheader" scope="col">${t`Application`}</th>
<th role="columnheader" scope="col">${t`Logins`}</th>
<th role="columnheader" scope="col"></th>
</tr>
</thead>
<tbody role="rowgroup">
${this.topN ? this.topN.map((e) => this.renderRow(e)) : html`<ak-spinner></ak-spinner>`}
</tbody>
</table>`;
<thead>
<tr role="row">
<th role="columnheader" scope="col">${t`Application`}</th>
<th role="columnheader" scope="col">${t`Logins`}</th>
<th role="columnheader" scope="col"></th>
</tr>
</thead>
<tbody role="rowgroup">
${this.topN
? this.topN.map((e) => this.renderRow(e))
: html`<ak-spinner></ak-spinner>`}
</tbody>
</table>`;
}
}

View File

@ -10,7 +10,6 @@ export interface AdminStatus {
}
export abstract class AdminStatusCard<T> extends AggregateCard {
abstract getPrimaryValue(): Promise<T>;
abstract getStatus(value: T): Promise<AdminStatus>;
@ -30,16 +29,20 @@ export abstract class AdminStatusCard<T> extends AggregateCard {
renderInner(): TemplateResult {
return html`<p class="center-value">
${until(this.getPrimaryValue().then((v) => {
this.value = v;
return this.getStatus(v);
}).then((status) => {
return html`<p>
<i class="${status.icon}"></i>&nbsp;${this.renderValue()}
</p>
${status.message ? html`<p class="subtext">${status.message}</p>` : html``}`;
}), html`<ak-spinner size="${PFSize.Large}"></ak-spinner>`)}
${until(
this.getPrimaryValue()
.then((v) => {
this.value = v;
return this.getStatus(v);
})
.then((status) => {
return html`<p><i class="${status.icon}"></i>&nbsp;${this.renderValue()}</p>
${status.message
? html`<p class="subtext">${status.message}</p>`
: html``}`;
}),
html`<ak-spinner size="${PFSize.Large}"></ak-spinner>`,
)}
</p>`;
}
}

View File

@ -7,22 +7,24 @@ import { convertToTitle } from "../../../utils";
@customElement("ak-admin-status-card-backup")
export class BackupStatusCard extends AdminStatusCard<StatusEnum> {
getPrimaryValue(): Promise<StatusEnum> {
return new AdminApi(DEFAULT_CONFIG).adminSystemTasksRetrieve({
id: "backup_database"
}).then((value) => {
return value.status;
}).catch(() => {
// On error (probably 404), check the config and see if the server
// can even backup
return config().then(c => {
if (c.capabilities.includes(CapabilitiesEnum.Backup)) {
return StatusEnum.Error;
}
return StatusEnum.Warning;
return new AdminApi(DEFAULT_CONFIG)
.adminSystemTasksRetrieve({
id: "backup_database",
})
.then((value) => {
return value.status;
})
.catch(() => {
// On error (probably 404), check the config and see if the server
// can even backup
return config().then((c) => {
if (c.capabilities.includes(CapabilitiesEnum.Backup)) {
return StatusEnum.Error;
}
return StatusEnum.Warning;
});
});
});
}
renderValue(): TemplateResult {
@ -43,9 +45,8 @@ export class BackupStatusCard extends AdminStatusCard<StatusEnum> {
});
default:
return Promise.resolve<AdminStatus>({
icon: "fa fa-check-circle pf-m-success"
icon: "fa fa-check-circle pf-m-success",
});
}
}
}

View File

@ -6,7 +6,6 @@ import { AdminStatusCard, AdminStatus } from "./AdminStatusCard";
@customElement("ak-admin-status-system")
export class SystemStatusCard extends AdminStatusCard<System> {
now?: Date;
header = "OK";
@ -35,12 +34,11 @@ export class SystemStatusCard extends AdminStatusCard<System> {
}
return Promise.resolve<AdminStatus>({
icon: "fa fa-check-circle pf-m-success",
message: t`Everything is ok.`
message: t`Everything is ok.`,
});
}
renderValue(): TemplateResult {
return html`${this.header}`;
}
}

View File

@ -6,7 +6,6 @@ import { AdminStatusCard, AdminStatus } from "./AdminStatusCard";
@customElement("ak-admin-status-version")
export class VersionStatusCard extends AdminStatusCard<Version> {
getPrimaryValue(): Promise<Version> {
return new AdminApi(DEFAULT_CONFIG).adminVersionRetrieve();
}
@ -26,12 +25,11 @@ export class VersionStatusCard extends AdminStatusCard<Version> {
}
return Promise.resolve<AdminStatus>({
icon: "fa fa-check-circle pf-m-success",
message: t`Up-to-date!`
message: t`Up-to-date!`,
});
}
renderValue(): TemplateResult {
return html`${this.value?.versionCurrent}`;
}
}

View File

@ -6,7 +6,6 @@ import { AdminStatus, AdminStatusCard } from "./AdminStatusCard";
@customElement("ak-admin-status-card-workers")
export class WorkersStatusCard extends AdminStatusCard<number> {
getPrimaryValue(): Promise<number> {
return new AdminApi(DEFAULT_CONFIG).adminWorkersRetrieve().then((workers) => {
return workers.count;
@ -21,9 +20,8 @@ export class WorkersStatusCard extends AdminStatusCard<number> {
});
} else {
return Promise.resolve<AdminStatus>({
icon: "fa fa-check-circle pf-m-success"
icon: "fa fa-check-circle pf-m-success",
});
}
}
}

View File

@ -13,7 +13,6 @@ interface FlowMetrics {
@customElement("ak-admin-status-chart-flow")
export class PolicyStatusChart extends AKChart<FlowMetrics> {
getChartType(): string {
return "doughnut";
}
@ -32,9 +31,11 @@ export class PolicyStatusChart extends AKChart<FlowMetrics> {
async apiRequest(): Promise<FlowMetrics> {
const api = new FlowsApi(DEFAULT_CONFIG);
const cached = (await api.flowsInstancesCacheInfoRetrieve()).count || 0;
const count = (await api.flowsInstancesList({
pageSize: 1
})).pagination.count;
const count = (
await api.flowsInstancesList({
pageSize: 1,
})
).pagination.count;
this.centerText = count.toString();
return {
count: count - cached,
@ -44,24 +45,14 @@ export class PolicyStatusChart extends AKChart<FlowMetrics> {
getChartData(data: FlowMetrics): ChartData {
return {
labels: [
t`Total flows`,
t`Cached flows`,
],
labels: [t`Total flows`, t`Cached flows`],
datasets: [
{
backgroundColor: [
"#2b9af3",
"#3e8635",
],
backgroundColor: ["#2b9af3", "#3e8635"],
spanGaps: true,
data: [
data.count,
data.cached,
],
data: [data.count, data.cached],
},
]
],
};
}
}

View File

@ -12,7 +12,6 @@ interface GroupMetrics {
@customElement("ak-admin-status-chart-group-count")
export class GroupCountStatusChart extends AKChart<GroupMetrics> {
getChartType(): string {
return "doughnut";
}
@ -30,12 +29,16 @@ export class GroupCountStatusChart extends AKChart<GroupMetrics> {
async apiRequest(): Promise<GroupMetrics> {
const api = new CoreApi(DEFAULT_CONFIG);
const count = (await api.coreGroupsList({
pageSize: 1
})).pagination.count;
const superusers = (await api.coreGroupsList({
isSuperuser: true
})).pagination.count;
const count = (
await api.coreGroupsList({
pageSize: 1,
})
).pagination.count;
const superusers = (
await api.coreGroupsList({
isSuperuser: true,
})
).pagination.count;
this.centerText = count.toString();
return {
count: count - superusers,
@ -45,24 +48,14 @@ export class GroupCountStatusChart extends AKChart<GroupMetrics> {
getChartData(data: GroupMetrics): ChartData {
return {
labels: [
t`Total groups`,
t`Superuser-groups`,
],
labels: [t`Total groups`, t`Superuser-groups`],
datasets: [
{
backgroundColor: [
"#2b9af3",
"#3e8635",
],
backgroundColor: ["#2b9af3", "#3e8635"],
spanGaps: true,
data: [
data.count,
data.superusers,
],
data: [data.count, data.superusers],
},
]
],
};
}
}

View File

@ -14,7 +14,6 @@ interface LDAPSyncStats {
@customElement("ak-admin-status-chart-ldap-sync")
export class LDAPSyncStatusChart extends AKChart<LDAPSyncStats> {
getChartType(): string {
return "doughnut";
}
@ -36,56 +35,45 @@ export class LDAPSyncStatusChart extends AKChart<LDAPSyncStats> {
let healthy = 0;
let failed = 0;
let unsynced = 0;
await Promise.all(sources.results.map(async (element) => {
try {
const health = await api.sourcesLdapSyncStatusRetrieve({
slug: element.slug,
});
if (health.status !== StatusEnum.Successful) {
failed += 1;
}
const now = new Date().getTime();
const maxDelta = 3600000; // 1 hour
if (!health || (now - health.taskFinishTimestamp.getTime()) > maxDelta) {
await Promise.all(
sources.results.map(async (element) => {
try {
const health = await api.sourcesLdapSyncStatusRetrieve({
slug: element.slug,
});
if (health.status !== StatusEnum.Successful) {
failed += 1;
}
const now = new Date().getTime();
const maxDelta = 3600000; // 1 hour
if (!health || now - health.taskFinishTimestamp.getTime() > maxDelta) {
unsynced += 1;
} else {
healthy += 1;
}
} catch {
unsynced += 1;
} else {
healthy += 1;
}
} catch {
unsynced += 1;
}
}));
}),
);
this.centerText = sources.pagination.count.toString();
return {
healthy: sources.pagination.count === 0 ? -1 : healthy,
failed,
unsynced
unsynced,
};
}
getChartData(data: LDAPSyncStats): ChartData {
return {
labels: [
t`Healthy sources`,
t`Failed sources`,
t`Unsynced sources`,
],
labels: [t`Healthy sources`, t`Failed sources`, t`Unsynced sources`],
datasets: [
{
backgroundColor: [
"#3e8635",
"#C9190B",
"#2b9af3",
],
backgroundColor: ["#3e8635", "#C9190B", "#2b9af3"],
spanGaps: true,
data: [
data.healthy,
data.failed,
data.unsynced
],
data: [data.healthy, data.failed, data.unsynced],
},
]
],
};
}
}

View File

@ -14,7 +14,6 @@ interface OutpostStats {
@customElement("ak-admin-status-chart-outpost")
export class OutpostStatusChart extends AKChart<OutpostStats> {
getChartType(): string {
return "doughnut";
}
@ -36,52 +35,41 @@ export class OutpostStatusChart extends AKChart<OutpostStats> {
let healthy = 0;
let outdated = 0;
let unhealthy = 0;
await Promise.all(outposts.results.map(async (element) => {
const health = await api.outpostsInstancesHealthList({
uuid: element.pk || "",
});
if (health.length === 0) {
unhealthy += 1;
}
health.forEach(h => {
if (h.versionOutdated) {
outdated += 1;
} else {
healthy += 1;
await Promise.all(
outposts.results.map(async (element) => {
const health = await api.outpostsInstancesHealthList({
uuid: element.pk || "",
});
if (health.length === 0) {
unhealthy += 1;
}
});
}));
health.forEach((h) => {
if (h.versionOutdated) {
outdated += 1;
} else {
healthy += 1;
}
});
}),
);
this.centerText = outposts.pagination.count.toString();
return {
healthy: outposts.pagination.count === 0 ? -1 : healthy,
outdated,
unhealthy
unhealthy,
};
}
getChartData(data: OutpostStats): ChartData {
return {
labels: [
t`Healthy outposts`,
t`Outdated outposts`,
t`Unhealthy outposts`,
],
labels: [t`Healthy outposts`, t`Outdated outposts`, t`Unhealthy outposts`],
datasets: [
{
backgroundColor: [
"#3e8635",
"#f0ab00",
"#C9190B",
],
backgroundColor: ["#3e8635", "#f0ab00", "#C9190B"],
spanGaps: true,
data: [
data.healthy,
data.outdated,
data.unhealthy
],
data: [data.healthy, data.outdated, data.unhealthy],
},
]
],
};
}
}

View File

@ -14,7 +14,6 @@ interface PolicyMetrics {
@customElement("ak-admin-status-chart-policy")
export class PolicyStatusChart extends AKChart<PolicyMetrics> {
getChartType(): string {
return "doughnut";
}
@ -33,13 +32,17 @@ export class PolicyStatusChart extends AKChart<PolicyMetrics> {
async apiRequest(): Promise<PolicyMetrics> {
const api = new PoliciesApi(DEFAULT_CONFIG);
const cached = (await api.policiesAllCacheInfoRetrieve()).count || 0;
const count = (await api.policiesAllList({
pageSize: 1
})).pagination.count;
const unbound = (await api.policiesAllList({
bindingsIsnull: true,
promptstageIsnull: true,
})).pagination.count;
const count = (
await api.policiesAllList({
pageSize: 1,
})
).pagination.count;
const unbound = (
await api.policiesAllList({
bindingsIsnull: true,
promptstageIsnull: true,
})
).pagination.count;
this.centerText = count.toString();
return {
// If we have more cache than total policies, only show that
@ -52,27 +55,14 @@ export class PolicyStatusChart extends AKChart<PolicyMetrics> {
getChartData(data: PolicyMetrics): ChartData {
return {
labels: [
t`Total policies`,
t`Cached policies`,
t`Unbound policies`,
],
labels: [t`Total policies`, t`Cached policies`, t`Unbound policies`],
datasets: [
{
backgroundColor: [
"#2b9af3",
"#3e8635",
"#f0ab00",
],
backgroundColor: ["#2b9af3", "#3e8635", "#f0ab00"],
spanGaps: true,
data: [
data.count,
data.cached,
data.unbound
],
data: [data.count, data.cached, data.unbound],
},
]
],
};
}
}

View File

@ -12,7 +12,6 @@ interface UserMetrics {
@customElement("ak-admin-status-chart-user-count")
export class UserCountStatusChart extends AKChart<UserMetrics> {
getChartType(): string {
return "doughnut";
}
@ -30,12 +29,16 @@ export class UserCountStatusChart extends AKChart<UserMetrics> {
async apiRequest(): Promise<UserMetrics> {
const api = new CoreApi(DEFAULT_CONFIG);
const count = (await api.coreUsersList({
pageSize: 1
})).pagination.count;
const superusers = (await api.coreUsersList({
isSuperuser: true
})).pagination.count;
const count = (
await api.coreUsersList({
pageSize: 1,
})
).pagination.count;
const superusers = (
await api.coreUsersList({
isSuperuser: true,
})
).pagination.count;
this.centerText = count.toString();
return {
count: count - superusers,
@ -45,24 +48,14 @@ export class UserCountStatusChart extends AKChart<UserMetrics> {
getChartData(data: UserMetrics): ChartData {
return {
labels: [
t`Total users`,
t`Superusers`,
],
labels: [t`Total users`, t`Superusers`],
datasets: [
{
backgroundColor: [
"#2b9af3",
"#3e8635",
],
backgroundColor: ["#2b9af3", "#3e8635"],
spanGaps: true,
data: [
data.count,
data.superusers,
],
data: [data.count, data.superusers],
},
]
],
};
}
}

View File

@ -9,14 +9,13 @@ import "../../elements/forms/HorizontalFormElement";
@customElement("ak-application-check-access-form")
export class ApplicationCheckAccessForm extends Form<{ forUser: number }> {
@property({attribute: false})
@property({ attribute: false })
application!: Application;
@property({ attribute: false})
@property({ attribute: false })
result?: PolicyTestResult;
@property({ attribute: false})
@property({ attribute: false })
request?: number;
getSuccessMessage(): string {
@ -25,10 +24,12 @@ export class ApplicationCheckAccessForm extends Form<{ forUser: number }> {
send = (data: { forUser: number }): Promise<PolicyTestResult> => {
this.request = data.forUser;
return new CoreApi(DEFAULT_CONFIG).coreApplicationsCheckAccessRetrieve({
slug: this.application?.slug,
forUser: data.forUser,
}).then(result => this.result = result);
return new CoreApi(DEFAULT_CONFIG)
.coreApplicationsCheckAccessRetrieve({
slug: this.application?.slug,
forUser: data.forUser,
})
.then((result) => (this.result = result));
};
resetForm(): void {
@ -37,25 +38,28 @@ export class ApplicationCheckAccessForm extends Form<{ forUser: number }> {
}
renderResult(): TemplateResult {
return html`
<ak-form-element-horizontal
label=${t`Passing`}>
return html` <ak-form-element-horizontal label=${t`Passing`}>
<div class="pf-c-form__group-label">
<div class="c-form__horizontal-group">
<span class="pf-c-form__label-text">${this.result?.passing ? t`Yes` : t`No`}</span>
<span class="pf-c-form__label-text"
>${this.result?.passing ? t`Yes` : t`No`}</span
>
</div>
</div>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Messages`}>
<ak-form-element-horizontal label=${t`Messages`}>
<div class="pf-c-form__group-label">
<div class="c-form__horizontal-group">
<ul>
${(this.result?.messages || []).length > 0 ?
this.result?.messages?.map(m => {
return html`<li><span class="pf-c-form__label-text">${m}</span></li>`;
}) :
html`<li><span class="pf-c-form__label-text">-</span></li>`}
${(this.result?.messages || []).length > 0
? this.result?.messages?.map((m) => {
return html`<li>
<span class="pf-c-form__label-text">${m}</span>
</li>`;
})
: html`<li>
<span class="pf-c-form__label-text">-</span>
</li>`}
</ul>
</div>
</div>
@ -64,22 +68,28 @@ export class ApplicationCheckAccessForm extends Form<{ forUser: number }> {
renderForm(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
<ak-form-element-horizontal
label=${t`User`}
?required=${true}
name="forUser">
<ak-form-element-horizontal label=${t`User`} ?required=${true} name="forUser">
<select class="pf-c-form-control">
${until(new CoreApi(DEFAULT_CONFIG).coreUsersList({
ordering: "username",
}).then(users => {
return users.results.map(user => {
return html`<option ?selected=${user.pk.toString() === this.request?.toString()} value=${user.pk}>${user.username}</option>`;
});
}), html`<option>${t`Loading...`}</option>`)}
${until(
new CoreApi(DEFAULT_CONFIG)
.coreUsersList({
ordering: "username",
})
.then((users) => {
return users.results.map((user) => {
return html`<option
?selected=${user.pk.toString() === this.request?.toString()}
value=${user.pk}
>
${user.username}
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
</ak-form-element-horizontal>
${this.result ? this.renderResult(): html``}
${this.result ? this.renderResult() : html``}
</form>`;
}
}

View File

@ -1,4 +1,11 @@
import { CoreApi, Application, ProvidersApi, Provider, PolicyEngineMode, CapabilitiesEnum } from "authentik-api";
import {
CoreApi,
Application,
ProvidersApi,
Provider,
PolicyEngineMode,
CapabilitiesEnum,
} from "authentik-api";
import { t } from "@lingui/macro";
import { CSSResult, customElement, property } from "lit-element";
import { html, TemplateResult } from "lit-html";
@ -17,10 +24,9 @@ import { first } from "../../utils";
@customElement("ak-application-form")
export class ApplicationForm extends ModelForm<Application, string> {
loadInstance(pk: string): Promise<Application> {
return new CoreApi(DEFAULT_CONFIG).coreApplicationsRetrieve({
slug: pk
slug: pk,
});
}
@ -47,18 +53,18 @@ export class ApplicationForm extends ModelForm<Application, string> {
if (this.instance) {
writeOp = new CoreApi(DEFAULT_CONFIG).coreApplicationsUpdate({
slug: this.instance.slug,
applicationRequest: data
applicationRequest: data,
});
} else {
writeOp = new CoreApi(DEFAULT_CONFIG).coreApplicationsCreate({
applicationRequest: data
applicationRequest: data,
});
}
return config().then((c) => {
if (c.capabilities.includes(CapabilitiesEnum.SaveMedia)) {
const icon = this.getFormFile();
if (icon || this.clearIcon) {
return writeOp.then(app => {
return writeOp.then((app) => {
return new CoreApi(DEFAULT_CONFIG).coreApplicationsSetIconCreate({
slug: app.slug,
file: icon,
@ -67,12 +73,12 @@ export class ApplicationForm extends ModelForm<Application, string> {
});
}
} else {
return writeOp.then(app => {
return writeOp.then((app) => {
return new CoreApi(DEFAULT_CONFIG).coreApplicationsSetIconUrlCreate({
slug: app.slug,
setIconURLRequest: {
url: data.metaIcon || "",
}
},
});
});
}
@ -81,7 +87,7 @@ export class ApplicationForm extends ModelForm<Application, string> {
groupProviders(providers: Provider[]): TemplateResult {
const m = new Map<string, Provider[]>();
providers.forEach(p => {
providers.forEach((p) => {
if (!m.has(p.verboseName || "")) {
m.set(p.verboseName || "", []);
}
@ -91,9 +97,11 @@ export class ApplicationForm extends ModelForm<Application, string> {
return html`
${Array.from(m).map(([group, providers]) => {
return html`<optgroup label=${group}>
${providers.map(p => {
const selected = (this.instance?.provider === p.pk) || (this.provider === p.pk);
return html`<option ?selected=${selected} value=${ifDefined(p.pk)}>${p.name}</option>`;
${providers.map((p) => {
const selected = this.instance?.provider === p.pk || this.provider === p.pk;
return html`<option ?selected=${selected} value=${ifDefined(p.pk)}>
${p.name}
</option>`;
})}
</optgroup>`;
})}
@ -102,129 +110,178 @@ export class ApplicationForm extends ModelForm<Application, string> {
renderForm(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
<ak-form-element-horizontal
label=${t`Name`}
?required=${true}
name="name">
<input type="text" value="${ifDefined(this.instance?.name)}" class="pf-c-form-control" required>
<ak-form-element-horizontal label=${t`Name`} ?required=${true} name="name">
<input
type="text"
value="${ifDefined(this.instance?.name)}"
class="pf-c-form-control"
required
/>
<p class="pf-c-form__helper-text">${t`Application's display Name.`}</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Slug`}
?required=${true}
name="slug">
<input type="text" value="${ifDefined(this.instance?.slug)}" class="pf-c-form-control" required>
<ak-form-element-horizontal label=${t`Slug`} ?required=${true} name="slug">
<input
type="text"
value="${ifDefined(this.instance?.slug)}"
class="pf-c-form-control"
required
/>
<p class="pf-c-form__helper-text">${t`Internal application name, used in URLs.`}</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Provider`}
name="provider">
<ak-form-element-horizontal label=${t`Provider`} name="provider">
<select class="pf-c-form-control">
<option value="" ?selected=${this.instance?.provider === undefined}>---------</option>
${until(new ProvidersApi(DEFAULT_CONFIG).providersAllList({}).then(providers => {
return this.groupProviders(providers.results);
}), html`<option>${t`Loading...`}</option>`)}
<option value="" ?selected=${this.instance?.provider === undefined}>
---------
</option>
${until(
new ProvidersApi(DEFAULT_CONFIG).providersAllList({}).then((providers) => {
return this.groupProviders(providers.results);
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
<p class="pf-c-form__helper-text">${t`Select a provider that this application should use. Alternatively, create a new provider.`}</p>
<p class="pf-c-form__helper-text">
${t`Select a provider that this application should use. Alternatively, create a new provider.`}
</p>
<ak-dropdown class="pf-c-dropdown">
<button class="pf-m-primary pf-c-dropdown__toggle" type="button">
<span class="pf-c-dropdown__toggle-text">${t`Create provider`}</span>
<i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i>
<i
class="fas fa-caret-down pf-c-dropdown__toggle-icon"
aria-hidden="true"
></i>
</button>
<ul class="pf-c-dropdown__menu" hidden>
${until(new ProvidersApi(DEFAULT_CONFIG).providersAllTypesList().then((types) => {
return types.map((type) => {
return html`<li>
<ak-forms-modal>
<span slot="submit">
${t`Create`}
</span>
<span slot="header">
${t`Create ${type.name}`}
</span>
<ak-proxy-form
slot="form"
type=${type.component}>
</ak-proxy-form>
<button type="button" slot="trigger" class="pf-c-dropdown__menu-item">
${type.name}<br>
<small>${type.description}</small>
</button>
</ak-forms-modal>
</li>`;
});
}), html`<ak-spinner></ak-spinner>`)}
${until(
new ProvidersApi(DEFAULT_CONFIG)
.providersAllTypesList()
.then((types) => {
return types.map((type) => {
return html`<li>
<ak-forms-modal>
<span slot="submit"> ${t`Create`} </span>
<span slot="header">
${t`Create ${type.name}`}
</span>
<ak-proxy-form slot="form" type=${type.component}>
</ak-proxy-form>
<button
type="button"
slot="trigger"
class="pf-c-dropdown__menu-item"
>
${type.name}<br />
<small>${type.description}</small>
</button>
</ak-forms-modal>
</li>`;
});
}),
html`<ak-spinner></ak-spinner>`,
)}
</ul>
</ak-dropdown>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Policy engine mode`}
?required=${true}
name="policyEngineMode">
name="policyEngineMode"
>
<select class="pf-c-form-control">
<option value=${PolicyEngineMode.Any} ?selected=${this.instance?.policyEngineMode === PolicyEngineMode.Any}>
<option
value=${PolicyEngineMode.Any}
?selected=${this.instance?.policyEngineMode === PolicyEngineMode.Any}
>
${t`ANY, any policy must match to grant access.`}
</option>
<option value=${PolicyEngineMode.All} ?selected=${this.instance?.policyEngineMode === PolicyEngineMode.All}>
<option
value=${PolicyEngineMode.All}
?selected=${this.instance?.policyEngineMode === PolicyEngineMode.All}
>
${t`ALL, all policies must match to grant access.`}
</option>
</select>
</ak-form-element-horizontal>
<ak-form-group .expanded=${true}>
<span slot="header">
${t`UI settings`}
</span>
<span slot="header"> ${t`UI settings`} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${t`Launch URL`}
name="metaLaunchUrl">
<input type="text" value="${ifDefined(this.instance?.metaLaunchUrl)}" class="pf-c-form-control">
<p class="pf-c-form__helper-text">${t`If left empty, authentik will try to extract the launch URL based on the selected provider.`}</p>
<ak-form-element-horizontal label=${t`Launch URL`} name="metaLaunchUrl">
<input
type="text"
value="${ifDefined(this.instance?.metaLaunchUrl)}"
class="pf-c-form-control"
/>
<p class="pf-c-form__helper-text">
${t`If left empty, authentik will try to extract the launch URL based on the selected provider.`}
</p>
</ak-form-element-horizontal>
${until(config().then((c) => {
if (c.capabilities.includes(CapabilitiesEnum.SaveMedia)) {
${until(
config().then((c) => {
if (c.capabilities.includes(CapabilitiesEnum.SaveMedia)) {
return html`<ak-form-element-horizontal
label=${t`Icon`}
name="metaIcon"
>
<input type="file" value="" class="pf-c-form-control" />
${this.instance?.metaIcon
? html`
<p class="pf-c-form__helper-text">
${t`Currently set to:`}
${this.instance?.metaIcon}
</p>
`
: html``}
</ak-form-element-horizontal>
${this.instance?.metaIcon
? html`
<ak-form-element-horizontal>
<div class="pf-c-check">
<input
type="checkbox"
class="pf-c-check__input"
@change=${(ev: Event) => {
const target =
ev.target as HTMLInputElement;
this.clearIcon = target.checked;
}}
/>
<label class="pf-c-check__label">
${t`Clear icon`}
</label>
</div>
<p class="pf-c-form__helper-text">
${t`Delete currently set icon.`}
</p>
</ak-form-element-horizontal>
`
: html``}`;
}
return html`<ak-form-element-horizontal
label=${t`Icon`}
name="metaIcon">
<input type="file" value="" class="pf-c-form-control">
${this.instance?.metaIcon ? html`
<p class="pf-c-form__helper-text">${t`Currently set to:`} ${this.instance?.metaIcon}</p>
`: html``}
</ak-form-element-horizontal>
${this.instance?.metaIcon ? html`
<ak-form-element-horizontal>
<div class="pf-c-check">
<input type="checkbox" class="pf-c-check__input" @change=${(ev: Event) => {
const target = ev.target as HTMLInputElement;
this.clearIcon = target.checked;
}}>
<label class="pf-c-check__label">
${t`Clear icon`}
</label>
</div>
<p class="pf-c-form__helper-text">${t`Delete currently set icon.`}</p>
</ak-form-element-horizontal>
`: html``}`;
}
return html`<ak-form-element-horizontal
label=${t`Icon`}
name="metaIcon">
<input type="text" value="${first(this.instance?.metaIcon, "")}" class="pf-c-form-control">
</ak-form-element-horizontal>`;
}))}
<ak-form-element-horizontal
label=${t`Description`}
name="metaDescription">
<textarea class="pf-c-form-control">${ifDefined(this.instance?.metaDescription)}</textarea>
name="metaIcon"
>
<input
type="text"
value="${first(this.instance?.metaIcon, "")}"
class="pf-c-form-control"
/>
</ak-form-element-horizontal>`;
}),
)}
<ak-form-element-horizontal label=${t`Description`} name="metaDescription">
<textarea class="pf-c-form-control">
${ifDefined(this.instance?.metaDescription)}</textarea
>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Publisher`}
name="metaPublisher">
<input type="text" value="${ifDefined(this.instance?.metaPublisher)}" class="pf-c-form-control">
<ak-form-element-horizontal label=${t`Publisher`} name="metaPublisher">
<input
type="text"
value="${ifDefined(this.instance?.metaPublisher)}"
class="pf-c-form-control"
/>
</ak-form-element-horizontal>
</div>
</ak-form-group>
</form>`;
}
}

View File

@ -42,14 +42,17 @@ export class ApplicationListPage extends TablePage<Application> {
}
static get styles(): CSSResult[] {
return super.styles.concat(PFAvatar, css`
tr td:first-child {
width: auto;
min-width: 0px;
text-align: center;
vertical-align: middle;
}
`);
return super.styles.concat(
PFAvatar,
css`
tr td:first-child {
width: auto;
min-width: 0px;
text-align: center;
vertical-align: middle;
}
`,
);
}
columns(): TableColumn[] {
@ -65,77 +68,68 @@ export class ApplicationListPage extends TablePage<Application> {
row(item: Application): TemplateResult[] {
return [
item.metaIcon ?
html`<img class="app-icon pf-c-avatar" src="${item.metaIcon}" alt="${t`Application Icon`}">` :
html`<i class="fas fa-question-circle"></i>`,
item.metaIcon
? html`<img
class="app-icon pf-c-avatar"
src="${item.metaIcon}"
alt="${t`Application Icon`}"
/>`
: html`<i class="fas fa-question-circle"></i>`,
html`<a href="#/core/applications/${item.slug}">
<div>
${item.name}
</div>
<div>${item.name}</div>
${item.metaPublisher ? html`<small>${item.metaPublisher}</small>` : html``}
</a>`,
html`<code>${item.slug}</code>`,
item.provider ?
html`<a href="#/core/providers/${item.providerObj?.pk}">
${item.providerObj?.name}
</a>` :
html`-`,
item.provider
? html`<a href="#/core/providers/${item.providerObj?.pk}">
${item.providerObj?.name}
</a>`
: html`-`,
html`${item.providerObj?.verboseName || "-"}`,
html`
<ak-forms-modal>
<span slot="submit">
${t`Update`}
</span>
<span slot="header">
${t`Update Application`}
</span>
<ak-application-form slot="form" .instancePk=${item.slug}>
</ak-application-form>
<button slot="trigger" class="pf-c-button pf-m-secondary">
${t`Edit`}
</button>
</ak-forms-modal>
${item.launchUrl ?
html`<a href=${item.launchUrl} target="_blank" class="pf-c-button pf-m-secondary">
${t`Open application`}
</a>`:
html``}
<ak-forms-delete
.obj=${item}
objectLabel=${t`Application`}
.usedBy=${() => {
return new CoreApi(DEFAULT_CONFIG).coreApplicationsUsedByList({
slug: item.slug
});
}}
.delete=${() => {
return new CoreApi(DEFAULT_CONFIG).coreApplicationsDestroy({
slug: item.slug
});
}}>
<button slot="trigger" class="pf-c-button pf-m-danger">
${t`Delete`}
</button>
</ak-forms-delete>`,
html` <ak-forms-modal>
<span slot="submit"> ${t`Update`} </span>
<span slot="header"> ${t`Update Application`} </span>
<ak-application-form slot="form" .instancePk=${item.slug}>
</ak-application-form>
<button slot="trigger" class="pf-c-button pf-m-secondary">${t`Edit`}</button>
</ak-forms-modal>
${item.launchUrl
? html`<a
href=${item.launchUrl}
target="_blank"
class="pf-c-button pf-m-secondary"
>
${t`Open application`}
</a>`
: html``}
<ak-forms-delete
.obj=${item}
objectLabel=${t`Application`}
.usedBy=${() => {
return new CoreApi(DEFAULT_CONFIG).coreApplicationsUsedByList({
slug: item.slug,
});
}}
.delete=${() => {
return new CoreApi(DEFAULT_CONFIG).coreApplicationsDestroy({
slug: item.slug,
});
}}
>
<button slot="trigger" class="pf-c-button pf-m-danger">${t`Delete`}</button>
</ak-forms-delete>`,
];
}
renderToolbar(): TemplateResult {
return html`
<ak-forms-modal>
<span slot="submit">
${t`Create`}
</span>
<span slot="header">
${t`Create Application`}
</span>
<ak-application-form slot="form">
</ak-application-form>
<button slot="trigger" class="pf-c-button pf-m-primary">
${t`Create`}
</button>
</ak-forms-modal>
${super.renderToolbar()}
<ak-forms-modal>
<span slot="submit"> ${t`Create`} </span>
<span slot="header"> ${t`Create Application`} </span>
<ak-application-form slot="form"> </ak-application-form>
<button slot="trigger" class="pf-c-button pf-m-primary">${t`Create`}</button>
</ak-forms-modal>
${super.renderToolbar()}
`;
}
}

View File

@ -24,21 +24,31 @@ import { ifDefined } from "lit-html/directives/if-defined";
@customElement("ak-application-view")
export class ApplicationViewPage extends LitElement {
@property()
set applicationSlug(value: string) {
new CoreApi(DEFAULT_CONFIG).coreApplicationsRetrieve({
slug: value
}).then((app) => {
this.application = app;
});
new CoreApi(DEFAULT_CONFIG)
.coreApplicationsRetrieve({
slug: value,
})
.then((app) => {
this.application = app;
});
}
@property({attribute: false})
@property({ attribute: false })
application!: Application;
static get styles(): CSSResult[] {
return [PFBase, PFPage, PFContent, PFButton, PFDescriptionList, PFGallery, PFCard, AKGlobal];
return [
PFBase,
PFPage,
PFContent,
PFButton,
PFDescriptionList,
PFGallery,
PFCard,
AKGlobal,
];
}
render(): TemplateResult {
@ -46,137 +56,178 @@ export class ApplicationViewPage extends LitElement {
icon=${this.application?.metaIcon || ""}
header=${this.application?.name || t`Loading`}
description=${ifDefined(this.application?.metaPublisher)}
.iconImage=${true}>
.iconImage=${true}
>
</ak-page-header>
${this.renderApp()}`;
}
renderApp(): TemplateResult {
if (!this.application) {
return html`<ak-empty-state
?loading="${true}"
header=${t`Loading`}>
</ak-empty-state>`;
return html`<ak-empty-state ?loading="${true}" header=${t`Loading`}> </ak-empty-state>`;
}
return html`
<ak-tabs>
<section slot="page-overview" data-tab-title="${t`Overview`}" class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-l-gallery pf-m-gutter">
<div class="pf-c-card pf-l-gallery__item">
<div class="pf-c-card__title">${t`Related`}</div>
<div class="pf-c-card__body">
<dl class="pf-c-description-list">
${this.application.providerObj ?
html`<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${t`Provider`}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
<a href="#/core/providers/${this.application.providerObj?.pk}">
${this.application.providerObj?.name}
</a>
</div>
</dd>
</div>`:
html``}
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${t`Policy engine mode`}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
${this.application.policyEngineMode?.toUpperCase()}
</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${t`Edit`}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
<ak-forms-modal>
<span slot="submit">
${t`Update`}
</span>
<span slot="header">
${t`Update Application`}
</span>
<ak-application-form slot="form" .instancePk=${this.application.slug}>
</ak-application-form>
<button slot="trigger" class="pf-c-button pf-m-secondary">
${t`Edit`}
</button>
</ak-forms-modal>
</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${t`Check access`}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
<ak-forms-modal .closeAfterSuccessfulSubmit=${false}>
<span slot="submit">
${t`Check`}
</span>
<span slot="header">
${t`Check Application access`}
</span>
<ak-application-check-access-form slot="form" .application=${this.application}>
</ak-application-check-access-form>
<button slot="trigger" class="pf-c-button pf-m-secondary">
${t`Test`}
</button>
</ak-forms-modal>
</div>
</dd>
</div>
${this.application.launchUrl ?
html`<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${t`Launch`}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
<a target="_blank" href=${this.application.launchUrl} slot="trigger" class="pf-c-button pf-m-secondary">
${t`Launch`}
</a>
</div>
</dd>
</div>`: html``}
</dl>
</div>
</div>
<div class="pf-c-card pf-l-gallery__item" style="grid-column-end: span 4;grid-row-end: span 2;">
<div class="pf-c-card__title">${t`Logins over the last 24 hours`}</div>
<div class="pf-c-card__body">
${this.application && html`
<ak-charts-application-authorize applicationSlug=${this.application.slug}>
</ak-charts-application-authorize>`}
</div>
</div>
<div class="pf-c-card pf-l-gallery__item" style="grid-column-end: span 5;grid-row-end: span 3;">
<div class="pf-c-card__title">${t`Changelog`}</div>
<div class="pf-c-card__body">
<ak-object-changelog
targetModelPk=${this.application.pk || ""}
targetModelApp="authentik_core"
targetModelName="application">
</ak-object-changelog>
</div>
return html` <ak-tabs>
<section
slot="page-overview"
data-tab-title="${t`Overview`}"
class="pf-c-page__main-section pf-m-no-padding-mobile"
>
<div class="pf-l-gallery pf-m-gutter">
<div class="pf-c-card pf-l-gallery__item">
<div class="pf-c-card__title">${t`Related`}</div>
<div class="pf-c-card__body">
<dl class="pf-c-description-list">
${this.application.providerObj
? html`<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text"
>${t`Provider`}</span
>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
<a
href="#/core/providers/${this.application
.providerObj?.pk}"
>
${this.application.providerObj?.name}
</a>
</div>
</dd>
</div>`
: html``}
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text"
>${t`Policy engine mode`}</span
>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
${this.application.policyEngineMode?.toUpperCase()}
</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${t`Edit`}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
<ak-forms-modal>
<span slot="submit"> ${t`Update`} </span>
<span slot="header">
${t`Update Application`}
</span>
<ak-application-form
slot="form"
.instancePk=${this.application.slug}
>
</ak-application-form>
<button
slot="trigger"
class="pf-c-button pf-m-secondary"
>
${t`Edit`}
</button>
</ak-forms-modal>
</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text"
>${t`Check access`}</span
>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
<ak-forms-modal .closeAfterSuccessfulSubmit=${false}>
<span slot="submit"> ${t`Check`} </span>
<span slot="header">
${t`Check Application access`}
</span>
<ak-application-check-access-form
slot="form"
.application=${this.application}
>
</ak-application-check-access-form>
<button
slot="trigger"
class="pf-c-button pf-m-secondary"
>
${t`Test`}
</button>
</ak-forms-modal>
</div>
</dd>
</div>
${this.application.launchUrl
? html`<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text"
>${t`Launch`}</span
>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
<a
target="_blank"
href=${this.application.launchUrl}
slot="trigger"
class="pf-c-button pf-m-secondary"
>
${t`Launch`}
</a>
</div>
</dd>
</div>`
: html``}
</dl>
</div>
</div>
</section>
<div slot="page-policy-bindings" data-tab-title="${t`Policy / Group / User Bindings`}" class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-c-card">
<div class="pf-c-card__title">${t`These policies control which users can access this application.`}</div>
<ak-bound-policies-list .target=${this.application.pk}>
</ak-bound-policies-list>
<div
class="pf-c-card pf-l-gallery__item"
style="grid-column-end: span 4;grid-row-end: span 2;"
>
<div class="pf-c-card__title">${t`Logins over the last 24 hours`}</div>
<div class="pf-c-card__body">
${this.application &&
html` <ak-charts-application-authorize
applicationSlug=${this.application.slug}
>
</ak-charts-application-authorize>`}
</div>
</div>
<div
class="pf-c-card pf-l-gallery__item"
style="grid-column-end: span 5;grid-row-end: span 3;"
>
<div class="pf-c-card__title">${t`Changelog`}</div>
<div class="pf-c-card__body">
<ak-object-changelog
targetModelPk=${this.application.pk || ""}
targetModelApp="authentik_core"
targetModelName="application"
>
</ak-object-changelog>
</div>
</div>
</div>
</ak-tabs>`;
</section>
<div
slot="page-policy-bindings"
data-tab-title="${t`Policy / Group / User Bindings`}"
class="pf-c-page__main-section pf-m-no-padding-mobile"
>
<div class="pf-c-card">
<div class="pf-c-card__title">
${t`These policies control which users can access this application.`}
</div>
<ak-bound-policies-list .target=${this.application.pk}>
</ak-bound-policies-list>
</div>
</div>
</ak-tabs>`;
}
}

View File

@ -8,38 +8,34 @@ import "../../elements/forms/HorizontalFormElement";
@customElement("ak-crypto-certificate-generate-form")
export class CertificateKeyPairForm extends Form<CertificateGenerationRequest> {
getSuccessMessage(): string {
return t`Successfully generated certificate-key pair.`;
}
send = (data: CertificateGenerationRequest): Promise<CertificateKeyPair> => {
return new CryptoApi(DEFAULT_CONFIG).cryptoCertificatekeypairsGenerateCreate({
certificateGenerationRequest: data
certificateGenerationRequest: data,
});
};
renderForm(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
<ak-form-element-horizontal
label=${t`Common Name`}
name="commonName"
?required=${true}>
<input type="text" class="pf-c-form-control" required>
<ak-form-element-horizontal label=${t`Common Name`} name="commonName" ?required=${true}>
<input type="text" class="pf-c-form-control" required />
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Subject-alt name`}
name="subjectAltName">
<input class="pf-c-form-control" type="text">
<p class="pf-c-form__helper-text">${t`Optional, comma-separated SubjectAlt Names.`}</p>
<ak-form-element-horizontal label=${t`Subject-alt name`} name="subjectAltName">
<input class="pf-c-form-control" type="text" />
<p class="pf-c-form__helper-text">
${t`Optional, comma-separated SubjectAlt Names.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Validity days`}
name="validityDays"
?required=${true}>
<input class="pf-c-form-control" type="number" value="365">
?required=${true}
>
<input class="pf-c-form-control" type="number" value="365" />
</ak-form-element-horizontal>
</form>`;
}
}

View File

@ -10,7 +10,6 @@ import { ModelForm } from "../../elements/forms/ModelForm";
@customElement("ak-crypto-certificate-form")
export class CertificateKeyPairForm extends ModelForm<CertificateKeyPair, string> {
loadInstance(pk: string): Promise<CertificateKeyPair> {
return new CryptoApi(DEFAULT_CONFIG).cryptoCertificatekeypairsRetrieve({
kpUuid: pk,
@ -29,39 +28,44 @@ export class CertificateKeyPairForm extends ModelForm<CertificateKeyPair, string
if (this.instance) {
return new CryptoApi(DEFAULT_CONFIG).cryptoCertificatekeypairsPartialUpdate({
kpUuid: this.instance.pk || "",
patchedCertificateKeyPairRequest: data
patchedCertificateKeyPairRequest: data,
});
} else {
return new CryptoApi(DEFAULT_CONFIG).cryptoCertificatekeypairsCreate({
certificateKeyPairRequest: data as unknown as CertificateKeyPairRequest
certificateKeyPairRequest: data as unknown as CertificateKeyPairRequest,
});
}
};
renderForm(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
<ak-form-element-horizontal
label=${t`Name`}
name="name"
?required=${true}>
<input type="text" value="${ifDefined(this.instance?.name)}" class="pf-c-form-control" required>
<ak-form-element-horizontal label=${t`Name`} name="name" ?required=${true}>
<input
type="text"
value="${ifDefined(this.instance?.name)}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Certificate`}
name="certificateData"
?writeOnly=${this.instance !== undefined}
?required=${true}>
?required=${true}
>
<textarea class="pf-c-form-control" required></textarea>
<p class="pf-c-form__helper-text">${t`PEM-encoded Certificate data.`}</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
name="keyData"
?writeOnly=${this.instance !== undefined}
label=${t`Private Key`}>
<textarea class="pf-c-form-control" ></textarea>
<p class="pf-c-form__helper-text">${t`Optional Private Key. If this is set, you can use this keypair for encryption.`}</p>
label=${t`Private Key`}
>
<textarea class="pf-c-form-control"></textarea>
<p class="pf-c-form__helper-text">
${t`Optional Private Key. If this is set, you can use this keypair for encryption.`}
</p>
</ak-form-element-horizontal>
</form>`;
}
}

View File

@ -62,122 +62,118 @@ export class CertificateKeyPairListPage extends TablePage<CertificateKeyPair> {
html`${item.name}`,
html`${item.privateKeyAvailable ? t`Yes` : t`No`}`,
html`${item.certExpiry?.toLocaleString()}`,
html`
<ak-forms-modal>
<span slot="submit">
${t`Update`}
</span>
<span slot="header">
${t`Update Certificate-Key Pair`}
</span>
<ak-crypto-certificate-form slot="form" .instancePk=${item.pk}>
</ak-crypto-certificate-form>
<button slot="trigger" class="pf-c-button pf-m-secondary">
${t`Edit`}
</button>
</ak-forms-modal>
<ak-forms-delete
.obj=${item}
objectLabel=${t`Certificate-Key Pair`}
.usedBy=${() => {
return new CryptoApi(DEFAULT_CONFIG).cryptoCertificatekeypairsUsedByList({
kpUuid: item.pk
});
}}
.delete=${() => {
return new CryptoApi(DEFAULT_CONFIG).cryptoCertificatekeypairsDestroy({
kpUuid: item.pk
});
}}>
<button slot="trigger" class="pf-c-button pf-m-danger">
${t`Delete`}
</button>
</ak-forms-delete>`,
html` <ak-forms-modal>
<span slot="submit"> ${t`Update`} </span>
<span slot="header"> ${t`Update Certificate-Key Pair`} </span>
<ak-crypto-certificate-form slot="form" .instancePk=${item.pk}>
</ak-crypto-certificate-form>
<button slot="trigger" class="pf-c-button pf-m-secondary">${t`Edit`}</button>
</ak-forms-modal>
<ak-forms-delete
.obj=${item}
objectLabel=${t`Certificate-Key Pair`}
.usedBy=${() => {
return new CryptoApi(DEFAULT_CONFIG).cryptoCertificatekeypairsUsedByList({
kpUuid: item.pk,
});
}}
.delete=${() => {
return new CryptoApi(DEFAULT_CONFIG).cryptoCertificatekeypairsDestroy({
kpUuid: item.pk,
});
}}
>
<button slot="trigger" class="pf-c-button pf-m-danger">${t`Delete`}</button>
</ak-forms-delete>`,
];
}
renderExpanded(item: CertificateKeyPair): TemplateResult {
return html`
<td role="cell" colspan="3">
<div class="pf-c-table__expandable-row-content">
<dl class="pf-c-description-list pf-m-horizontal">
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${t`Certificate Fingerprint (SHA1)`}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">${item.fingerprintSha1}</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${t`Certificate Fingerprint (SHA256)`}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">${item.fingerprintSha256}</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${t`Certificate Subjet`}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">${item.certSubject}</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${t`Download`}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
<a class="pf-c-button pf-m-secondary" target="_blank"
href=${item.certificateDownloadUrl}>
${t`Download Certificate`}
</a>
${item.privateKeyAvailable ? html`<a class="pf-c-button pf-m-secondary" target="_blank"
href=${item.privateKeyDownloadUrl}>
${t`Download Private key`}
</a>` : html``}
</div>
</dd>
</div>
</dl>
</div>
</td>
<td></td>
<td></td>`;
return html` <td role="cell" colspan="3">
<div class="pf-c-table__expandable-row-content">
<dl class="pf-c-description-list pf-m-horizontal">
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text"
>${t`Certificate Fingerprint (SHA1)`}</span
>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
${item.fingerprintSha1}
</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text"
>${t`Certificate Fingerprint (SHA256)`}</span
>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
${item.fingerprintSha256}
</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text"
>${t`Certificate Subjet`}</span
>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">${item.certSubject}</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${t`Download`}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
<a
class="pf-c-button pf-m-secondary"
target="_blank"
href=${item.certificateDownloadUrl}
>
${t`Download Certificate`}
</a>
${item.privateKeyAvailable
? html`<a
class="pf-c-button pf-m-secondary"
target="_blank"
href=${item.privateKeyDownloadUrl}
>
${t`Download Private key`}
</a>`
: html``}
</div>
</dd>
</div>
</dl>
</div>
</td>
<td></td>
<td></td>`;
}
renderToolbar(): TemplateResult {
return html`
<ak-forms-modal>
<span slot="submit">
${t`Create`}
</span>
<span slot="header">
${t`Create Certificate-Key Pair`}
</span>
<ak-crypto-certificate-form slot="form">
</ak-crypto-certificate-form>
<button slot="trigger" class="pf-c-button pf-m-primary">
${t`Create`}
</button>
</ak-forms-modal>
<ak-forms-modal>
<span slot="submit">
${t`Generate`}
</span>
<span slot="header">
${t`Generate Certificate-Key Pair`}
</span>
<ak-crypto-certificate-generate-form slot="form">
</ak-crypto-certificate-generate-form>
<button slot="trigger" class="pf-c-button pf-m-secondary">
${t`Generate`}
</button>
</ak-forms-modal>
${super.renderToolbar()}
<ak-forms-modal>
<span slot="submit"> ${t`Create`} </span>
<span slot="header"> ${t`Create Certificate-Key Pair`} </span>
<ak-crypto-certificate-form slot="form"> </ak-crypto-certificate-form>
<button slot="trigger" class="pf-c-button pf-m-primary">${t`Create`}</button>
</ak-forms-modal>
<ak-forms-modal>
<span slot="submit"> ${t`Generate`} </span>
<span slot="header"> ${t`Generate Certificate-Key Pair`} </span>
<ak-crypto-certificate-generate-form slot="form">
</ak-crypto-certificate-generate-form>
<button slot="trigger" class="pf-c-button pf-m-secondary">${t`Generate`}</button>
</ak-forms-modal>
${super.renderToolbar()}
`;
}
}

View File

@ -1,5 +1,13 @@
import { t } from "@lingui/macro";
import { css, CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element";
import {
css,
CSSResult,
customElement,
html,
LitElement,
property,
TemplateResult,
} from "lit-element";
import { until } from "lit-html/directives/until";
import { EventActions, FlowsApi } from "authentik-api";
import "../../elements/Spinner";
@ -16,12 +24,16 @@ import { VERSION } from "../../constants";
@customElement("ak-event-info")
export class EventInfo extends LitElement {
@property({attribute: false})
@property({ attribute: false })
event!: EventWithContext;
static get styles(): CSSResult[] {
return [PFBase, PFButton, PFFlex, PFList, PFDescriptionList,
return [
PFBase,
PFButton,
PFFlex,
PFList,
PFDescriptionList,
css`
code {
display: block;
@ -37,7 +49,7 @@ export class EventInfo extends LitElement {
width: 100%;
height: 50rem;
}
`
`,
];
}
@ -116,7 +128,7 @@ export class EventInfo extends LitElement {
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
${(context.to_email as string[]).map(to => {
${(context.to_email as string[]).map((to) => {
return html`<li>${to}</li>`;
})}
</div>
@ -127,15 +139,15 @@ export class EventInfo extends LitElement {
defaultResponse(): TemplateResult {
return html`<div class="pf-l-flex">
<div class="pf-l-flex__item">
<h3>${t`Context`}</h3>
<code>${JSON.stringify(this.event?.context, null, 4)}</code>
</div>
<div class="pf-l-flex__item">
<h3>${t`User`}</h3>
<code>${JSON.stringify(this.event?.user, null, 4)}</code>
</div>
</div>`;
<div class="pf-l-flex__item">
<h3>${t`Context`}</h3>
<code>${JSON.stringify(this.event?.context, null, 4)}</code>
</div>
<div class="pf-l-flex__item">
<h3>${t`User`}</h3>
<code>${JSON.stringify(this.event?.user, null, 4)}</code>
</div>
</div>`;
}
buildGitHubIssueUrl(context: EventContext): string {
@ -189,150 +201,201 @@ new?labels=bug,from_authentik&title=${encodeURIComponent(title)}
return html`<ak-spinner size=${PFSize.Medium}></ak-spinner>`;
}
switch (this.event?.action) {
case EventActions.ModelCreated:
case EventActions.ModelUpdated:
case EventActions.ModelDeleted:
return html`
<h3>${t`Affected model:`}</h3>
${this.getModelInfo(this.event.context?.model as EventModel)}
case EventActions.ModelCreated:
case EventActions.ModelUpdated:
case EventActions.ModelDeleted:
return html`
<h3>${t`Affected model:`}</h3>
${this.getModelInfo(this.event.context?.model as EventModel)}
`;
case EventActions.AuthorizeApplication:
return html`<div class="pf-l-flex">
<div class="pf-l-flex__item">
<h3>${t`Authorized application:`}</h3>
${this.getModelInfo(this.event.context.authorized_application as EventModel)}
</div>
<div class="pf-l-flex__item">
<h3>${t`Using flow`}</h3>
<span>${until(new FlowsApi(DEFAULT_CONFIG).flowsInstancesList({
flowUuid: this.event.context.flow as string,
}).then(resp => {
return html`<a href="#/flow/flows/${resp.results[0].slug}">${resp.results[0].name}</a>`;
}), html`<ak-spinner size=${PFSize.Medium}></ak-spinner>`)}
</span>
</div>
</div>
<ak-expand>${this.defaultResponse()}</ak-expand>`;
case EventActions.EmailSent:
return html`<h3>${t`Email info:`}</h3>
${this.getEmailInfo(this.event.context)}
<ak-expand>
<iframe srcdoc=${this.event.context.body}></iframe>
</ak-expand>`;
case EventActions.SecretView:
return html`
<h3>${t`Secret:`}</h3>
${this.getModelInfo(this.event.context.secret as EventModel)}`;
case EventActions.SystemException:
return html`
<a
class="pf-c-button pf-m-primary"
target="_blank"
href=${this.buildGitHubIssueUrl(
this.event.context
)}>
${t`Open issue on GitHub...`}
</a>
<div class="pf-l-flex">
<div class="pf-l-flex__item">
<h3>${t`Exception`}</h3>
<code>${this.event.context.message}</code>
</div>
</div>
<ak-expand>${this.defaultResponse()}</ak-expand>`;
case EventActions.PropertyMappingException:
return html`<div class="pf-l-flex">
<div class="pf-l-flex__item">
<h3>${t`Exception`}</h3>
<code>${this.event.context.message || this.event.context.error}</code>
</div>
<div class="pf-l-flex__item">
<h3>${t`Expression`}</h3>
<code>${this.event.context.expression}</code>
</div>
</div>
<ak-expand>${this.defaultResponse()}</ak-expand>`;
case EventActions.PolicyException:
return html`<div class="pf-l-flex">
<div class="pf-l-flex__item">
<h3>${t`Binding`}</h3>
${this.getModelInfo(this.event.context.binding as EventModel)}
</div>
<div class="pf-l-flex__item">
<h3>${t`Request`}</h3>
<ul class="pf-c-list">
<li>${t`Object`}: ${this.getModelInfo((this.event.context.request as EventContext).obj as EventModel)}</li>
<li><span>${t`Context`}: <code>${JSON.stringify((this.event.context.request as EventContext).context, null, 4)}</code></span></li>
</ul>
</div>
<div class="pf-l-flex__item">
<h3>${t`Exception`}</h3>
<code>${this.event.context.message || this.event.context.error}</code>
</div>
</div>
<ak-expand>${this.defaultResponse()}</ak-expand>`;
case EventActions.PolicyExecution:
return html`<div class="pf-l-flex">
<div class="pf-l-flex__item">
<h3>${t`Binding`}</h3>
${this.getModelInfo(this.event.context.binding as EventModel)}
</div>
<div class="pf-l-flex__item">
<h3>${t`Request`}</h3>
<ul class="pf-c-list">
<li>${t`Object`}: ${this.getModelInfo((this.event.context.request as EventContext).obj as EventModel)}</li>
<li><span>${t`Context`}: <code>${JSON.stringify((this.event.context.request as EventContext).context, null, 4)}</code></span></li>
</ul>
</div>
<div class="pf-l-flex__item">
<h3>${t`Result`}</h3>
<ul class="pf-c-list">
<li>${t`Passing`}: ${(this.event.context.result as EventContext).passing}</li>
<li>${t`Messages`}:
<ul class="pf-c-list">
${((this.event.context.result as EventContext).messages as string[]).map(msg => {
return html`<li>${msg}</li>`;
})}
</ul>
</li>
</ul>
</div>
</div>
<ak-expand>${this.defaultResponse()}</ak-expand>`;
case EventActions.ConfigurationError:
return html`<h3>${this.event.context.message}</h3>
<ak-expand>${this.defaultResponse()}</ak-expand>`;
case EventActions.UpdateAvailable:
return html`<h3>${t`New version available!`}</h3>
<a
target="_blank"
href="https://github.com/goauthentik/authentik/releases/tag/version%2F${this.event.context.new_version}">
${this.event.context.new_version}
</a>`;
// Action types which typically don't record any extra context.
// If context is not empty, we fall to the default response.
case EventActions.Login:
if ("using_source" in this.event.context) {
case EventActions.AuthorizeApplication:
return html`<div class="pf-l-flex">
<div class="pf-l-flex__item">
<h3>${t`Using source`}</h3>
${this.getModelInfo(this.event.context.using_source as EventModel)}
<div class="pf-l-flex__item">
<h3>${t`Authorized application:`}</h3>
${this.getModelInfo(
this.event.context.authorized_application as EventModel,
)}
</div>
<div class="pf-l-flex__item">
<h3>${t`Using flow`}</h3>
<span
>${until(
new FlowsApi(DEFAULT_CONFIG)
.flowsInstancesList({
flowUuid: this.event.context.flow as string,
})
.then((resp) => {
return html`<a
href="#/flow/flows/${resp.results[0].slug}"
>${resp.results[0].name}</a
>`;
}),
html`<ak-spinner size=${PFSize.Medium}></ak-spinner>`,
)}
</span>
</div>
</div>
</div>`;
}
return this.defaultResponse();
case EventActions.LoginFailed:
return html`
<h3>${t`Attempted to log in as ${this.event.context.username}`}</h3>
<ak-expand>${this.defaultResponse()}</ak-expand>`;
case EventActions.Logout:
if (this.event.context === {}) {
return html`<span>${t`No additional data available.`}</span>`;
}
return this.defaultResponse();
default:
return this.defaultResponse();
<ak-expand>${this.defaultResponse()}</ak-expand>`;
case EventActions.EmailSent:
return html`<h3>${t`Email info:`}</h3>
${this.getEmailInfo(this.event.context)}
<ak-expand>
<iframe srcdoc=${this.event.context.body}></iframe>
</ak-expand>`;
case EventActions.SecretView:
return html` <h3>${t`Secret:`}</h3>
${this.getModelInfo(this.event.context.secret as EventModel)}`;
case EventActions.SystemException:
return html` <a
class="pf-c-button pf-m-primary"
target="_blank"
href=${this.buildGitHubIssueUrl(this.event.context)}
>
${t`Open issue on GitHub...`}
</a>
<div class="pf-l-flex">
<div class="pf-l-flex__item">
<h3>${t`Exception`}</h3>
<code>${this.event.context.message}</code>
</div>
</div>
<ak-expand>${this.defaultResponse()}</ak-expand>`;
case EventActions.PropertyMappingException:
return html`<div class="pf-l-flex">
<div class="pf-l-flex__item">
<h3>${t`Exception`}</h3>
<code>${this.event.context.message || this.event.context.error}</code>
</div>
<div class="pf-l-flex__item">
<h3>${t`Expression`}</h3>
<code>${this.event.context.expression}</code>
</div>
</div>
<ak-expand>${this.defaultResponse()}</ak-expand>`;
case EventActions.PolicyException:
return html`<div class="pf-l-flex">
<div class="pf-l-flex__item">
<h3>${t`Binding`}</h3>
${this.getModelInfo(this.event.context.binding as EventModel)}
</div>
<div class="pf-l-flex__item">
<h3>${t`Request`}</h3>
<ul class="pf-c-list">
<li>
${t`Object`}:
${this.getModelInfo(
(this.event.context.request as EventContext)
.obj as EventModel,
)}
</li>
<li>
<span
>${t`Context`}:
<code
>${JSON.stringify(
(this.event.context.request as EventContext)
.context,
null,
4,
)}</code
></span
>
</li>
</ul>
</div>
<div class="pf-l-flex__item">
<h3>${t`Exception`}</h3>
<code>${this.event.context.message || this.event.context.error}</code>
</div>
</div>
<ak-expand>${this.defaultResponse()}</ak-expand>`;
case EventActions.PolicyExecution:
return html`<div class="pf-l-flex">
<div class="pf-l-flex__item">
<h3>${t`Binding`}</h3>
${this.getModelInfo(this.event.context.binding as EventModel)}
</div>
<div class="pf-l-flex__item">
<h3>${t`Request`}</h3>
<ul class="pf-c-list">
<li>
${t`Object`}:
${this.getModelInfo(
(this.event.context.request as EventContext)
.obj as EventModel,
)}
</li>
<li>
<span
>${t`Context`}:
<code
>${JSON.stringify(
(this.event.context.request as EventContext)
.context,
null,
4,
)}</code
></span
>
</li>
</ul>
</div>
<div class="pf-l-flex__item">
<h3>${t`Result`}</h3>
<ul class="pf-c-list">
<li>
${t`Passing`}:
${(this.event.context.result as EventContext).passing}
</li>
<li>
${t`Messages`}:
<ul class="pf-c-list">
${(
(this.event.context.result as EventContext)
.messages as string[]
).map((msg) => {
return html`<li>${msg}</li>`;
})}
</ul>
</li>
</ul>
</div>
</div>
<ak-expand>${this.defaultResponse()}</ak-expand>`;
case EventActions.ConfigurationError:
return html`<h3>${this.event.context.message}</h3>
<ak-expand>${this.defaultResponse()}</ak-expand>`;
case EventActions.UpdateAvailable:
return html`<h3>${t`New version available!`}</h3>
<a
target="_blank"
href="https://github.com/goauthentik/authentik/releases/tag/version%2F${this
.event.context.new_version}"
>
${this.event.context.new_version}
</a>`;
// Action types which typically don't record any extra context.
// If context is not empty, we fall to the default response.
case EventActions.Login:
if ("using_source" in this.event.context) {
return html`<div class="pf-l-flex">
<div class="pf-l-flex__item">
<h3>${t`Using source`}</h3>
${this.getModelInfo(this.event.context.using_source as EventModel)}
</div>
</div>`;
}
return this.defaultResponse();
case EventActions.LoginFailed:
return html` <h3>${t`Attempted to log in as ${this.event.context.username}`}</h3>
<ak-expand>${this.defaultResponse()}</ak-expand>`;
case EventActions.Logout:
if (this.event.context === {}) {
return html`<span>${t`No additional data available.`}</span>`;
}
return this.defaultResponse();
default:
return this.defaultResponse();
}
}
}

View File

@ -13,14 +13,15 @@ import "../../elements/PageHeader";
@customElement("ak-event-info-page")
export class EventInfoPage extends LitElement {
@property()
set eventID(value: string) {
new EventsApi(DEFAULT_CONFIG).eventsEventsRetrieve({
eventUuid: value
}).then((ev) => {
this.event = ev as EventWithContext;
});
new EventsApi(DEFAULT_CONFIG)
.eventsEventsRetrieve({
eventUuid: value,
})
.then((ev) => {
this.event = ev as EventWithContext;
});
}
@property({ attribute: false })
@ -32,19 +33,17 @@ export class EventInfoPage extends LitElement {
render(): TemplateResult {
return html`<ak-page-header
icon="pf-icon pf-icon-catalog"
header=${t`Event ${this.event?.pk || ""}`}>
</ak-page-header>
<section class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-c-card">
<div class="pf-c-card__title">
${t`Event info`}
icon="pf-icon pf-icon-catalog"
header=${t`Event ${this.event?.pk || ""}`}
>
</ak-page-header>
<section class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-c-card">
<div class="pf-c-card__title">${t`Event info`}</div>
<div class="pf-c-card__body">
<ak-event-info .event=${this.event}></ak-event-info>
</div>
</div>
<div class="pf-c-card__body">
<ak-event-info .event=${this.event}></ak-event-info>
</div>
</div>
</section>`;
</section>`;
}
}

View File

@ -53,15 +53,15 @@ export class EventListPage extends TablePage<Event> {
row(item: EventWithContext): TemplateResult[] {
return [
html`<div>${ActionToLabel(item.action)}</div>
<small>${item.app}</small>`,
item.user?.username ?
html`<a href="#/identity/users/${item.user.pk}">
${item.user?.username}
</a>
${item.user.on_behalf_of ? html`<small>
${t`On behalf of ${item.user.on_behalf_of.username}`}
</small>` : html``}` :
html`-`,
<small>${item.app}</small>`,
item.user?.username
? html`<a href="#/identity/users/${item.user.pk}"> ${item.user?.username} </a>
${item.user.on_behalf_of
? html`<small>
${t`On behalf of ${item.user.on_behalf_of.username}`}
</small>`
: html``}`
: html`-`,
html`<span>${item.created?.toLocaleString()}</span>`,
html`<span>${item.clientIp || "-"}</span>`,
html`<span>${item.tenant?.name || "-"}</span>`,
@ -72,15 +72,13 @@ export class EventListPage extends TablePage<Event> {
}
renderExpanded(item: Event): TemplateResult {
return html`
<td role="cell" colspan="3">
<div class="pf-c-table__expandable-row-content">
<ak-event-info .event=${item as EventWithContext}></ak-event-info>
</div>
</td>
<td></td>
<td></td>
<td></td>`;
return html` <td role="cell" colspan="3">
<div class="pf-c-table__expandable-row-content">
<ak-event-info .event=${item as EventWithContext}></ak-event-info>
</div>
</td>
<td></td>
<td></td>
<td></td>`;
}
}

View File

@ -10,7 +10,6 @@ import { ModelForm } from "../../elements/forms/ModelForm";
@customElement("ak-event-rule-form")
export class RuleForm extends ModelForm<NotificationRule, string> {
loadInstance(pk: string): Promise<NotificationRule> {
return new EventsApi(DEFAULT_CONFIG).eventsRulesRetrieve({
pbmUuid: pk,
@ -29,24 +28,33 @@ export class RuleForm extends ModelForm<NotificationRule, string> {
if (this.instance) {
return new EventsApi(DEFAULT_CONFIG).eventsRulesUpdate({
pbmUuid: this.instance.pk || "",
notificationRuleRequest: data
notificationRuleRequest: data,
});
} else {
return new EventsApi(DEFAULT_CONFIG).eventsRulesCreate({
notificationRuleRequest: data
notificationRuleRequest: data,
});
}
};
renderSeverity(): TemplateResult {
return html`
<option value=${SeverityEnum.Alert} ?selected=${this.instance?.severity === SeverityEnum.Alert}>
<option
value=${SeverityEnum.Alert}
?selected=${this.instance?.severity === SeverityEnum.Alert}
>
${t`Alert`}
</option>
<option value=${SeverityEnum.Warning} ?selected=${this.instance?.severity === SeverityEnum.Warning}>
<option
value=${SeverityEnum.Warning}
?selected=${this.instance?.severity === SeverityEnum.Warning}
>
${t`Warning`}
</option>
<option value=${SeverityEnum.Notice} ?selected=${this.instance?.severity === SeverityEnum.Notice}>
<option
value=${SeverityEnum.Notice}
?selected=${this.instance?.severity === SeverityEnum.Notice}
>
${t`Notice`}
</option>
`;
@ -54,50 +62,69 @@ export class RuleForm extends ModelForm<NotificationRule, string> {
renderForm(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
<ak-form-element-horizontal
label=${t`Name`}
?required=${true}
name="name">
<input type="text" value="${ifDefined(this.instance?.name)}" class="pf-c-form-control" required>
<ak-form-element-horizontal label=${t`Name`} ?required=${true} name="name">
<input
type="text"
value="${ifDefined(this.instance?.name)}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Group`}
name="group">
<ak-form-element-horizontal label=${t`Group`} name="group">
<select class="pf-c-form-control">
<option value="" ?selected=${this.instance?.group === undefined}>---------</option>
${until(new CoreApi(DEFAULT_CONFIG).coreGroupsList({}).then(groups => {
return groups.results.map(group => {
return html`<option value=${ifDefined(group.pk)} ?selected=${this.instance?.group === group.pk}>${group.name}</option>`;
});
}), html`<option>${t`Loading...`}</option>`)}
</select>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Transports`}
?required=${true}
name="transports">
<select name="users" class="pf-c-form-control" multiple>
${until(new EventsApi(DEFAULT_CONFIG).eventsTransportsList({}).then(transports => {
return transports.results.map(transport => {
const selected = Array.from(this.instance?.transports || []).some(su => {
return su == transport.pk;
<option value="" ?selected=${this.instance?.group === undefined}>
---------
</option>
${until(
new CoreApi(DEFAULT_CONFIG).coreGroupsList({}).then((groups) => {
return groups.results.map((group) => {
return html`<option
value=${ifDefined(group.pk)}
?selected=${this.instance?.group === group.pk}
>
${group.name}
</option>`;
});
return html`<option value=${ifDefined(transport.pk)} ?selected=${selected}>${transport.name}</option>`;
});
}), html`<option>${t`Loading...`}</option>`)}
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
<p class="pf-c-form__helper-text">${t`Select which transports should be used to notify the user. If none are selected, the notification will only be shown in the authentik UI.`}</p>
<p class="pf-c-form__helper-text">${t`Hold control/command to select multiple items.`}</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Severity`}
?required=${true}
name="severity">
<ak-form-element-horizontal label=${t`Transports`} ?required=${true} name="transports">
<select name="users" class="pf-c-form-control" multiple>
${until(
new EventsApi(DEFAULT_CONFIG)
.eventsTransportsList({})
.then((transports) => {
return transports.results.map((transport) => {
const selected = Array.from(
this.instance?.transports || [],
).some((su) => {
return su == transport.pk;
});
return html`<option
value=${ifDefined(transport.pk)}
?selected=${selected}
>
${transport.name}
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
<p class="pf-c-form__helper-text">
${t`Select which transports should be used to notify the user. If none are selected, the notification will only be shown in the authentik UI.`}
</p>
<p class="pf-c-form__helper-text">
${t`Hold control/command to select multiple items.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${t`Severity`} ?required=${true} name="severity">
<select class="pf-c-form-control">
${this.renderSeverity()}
</select>
</ak-form-element-horizontal>
</form>`;
}
}

View File

@ -56,70 +56,54 @@ export class RuleListPage extends TablePage<NotificationRule> {
html`${item.name}`,
html`${item.severity}`,
html`${item.groupObj?.name || t`None (rule disabled)`}`,
html`
<ak-forms-modal>
<span slot="submit">
${t`Update`}
</span>
<span slot="header">
${t`Update Notification Rule`}
</span>
<ak-event-rule-form slot="form" .instancePk=${item.pk}>
</ak-event-rule-form>
<button slot="trigger" class="pf-c-button pf-m-secondary">
${t`Edit`}
</button>
</ak-forms-modal>
<ak-forms-delete
.obj=${item}
objectLabel=${t`Notification rule`}
.usedBy=${() => {
return new EventsApi(DEFAULT_CONFIG).eventsRulesUsedByList({
pbmUuid: item.pk
});
}}
.delete=${() => {
return new EventsApi(DEFAULT_CONFIG).eventsRulesDestroy({
pbmUuid: item.pk
});
}}>
<button slot="trigger" class="pf-c-button pf-m-danger">
${t`Delete`}
</button>
</ak-forms-delete>`,
html` <ak-forms-modal>
<span slot="submit"> ${t`Update`} </span>
<span slot="header"> ${t`Update Notification Rule`} </span>
<ak-event-rule-form slot="form" .instancePk=${item.pk}> </ak-event-rule-form>
<button slot="trigger" class="pf-c-button pf-m-secondary">${t`Edit`}</button>
</ak-forms-modal>
<ak-forms-delete
.obj=${item}
objectLabel=${t`Notification rule`}
.usedBy=${() => {
return new EventsApi(DEFAULT_CONFIG).eventsRulesUsedByList({
pbmUuid: item.pk,
});
}}
.delete=${() => {
return new EventsApi(DEFAULT_CONFIG).eventsRulesDestroy({
pbmUuid: item.pk,
});
}}
>
<button slot="trigger" class="pf-c-button pf-m-danger">${t`Delete`}</button>
</ak-forms-delete>`,
];
}
renderToolbar(): TemplateResult {
return html`
<ak-forms-modal>
<span slot="submit">
${t`Create`}
</span>
<span slot="header">
${t`Create Notification Rule`}
</span>
<ak-event-rule-form slot="form">
</ak-event-rule-form>
<button slot="trigger" class="pf-c-button pf-m-primary">
${t`Create`}
</button>
</ak-forms-modal>
${super.renderToolbar()}
<ak-forms-modal>
<span slot="submit"> ${t`Create`} </span>
<span slot="header"> ${t`Create Notification Rule`} </span>
<ak-event-rule-form slot="form"> </ak-event-rule-form>
<button slot="trigger" class="pf-c-button pf-m-primary">${t`Create`}</button>
</ak-forms-modal>
${super.renderToolbar()}
`;
}
renderExpanded(item: NotificationRule): TemplateResult {
return html`
<td role="cell" colspan="4">
<div class="pf-c-table__expandable-row-content">
<p>${t`These bindings control upon which events this rule triggers. Bindings to
groups/users are checked against the user of the event.`}</p>
<ak-bound-policies-list .target=${item.pk}>
</ak-bound-policies-list>
</div>
</td>
<td></td>
<td></td>`;
return html` <td role="cell" colspan="4">
<div class="pf-c-table__expandable-row-content">
<p>
${t`These bindings control upon which events this rule triggers. Bindings to
groups/users are checked against the user of the event.`}
</p>
<ak-bound-policies-list .target=${item.pk}> </ak-bound-policies-list>
</div>
</td>
<td></td>
<td></td>`;
}
}

View File

@ -10,14 +10,13 @@ import { ModelForm } from "../../elements/forms/ModelForm";
@customElement("ak-event-transport-form")
export class TransportForm extends ModelForm<NotificationTransport, string> {
loadInstance(pk: string): Promise<NotificationTransport> {
return new EventsApi(DEFAULT_CONFIG).eventsTransportsRetrieve({
uuid: pk,
});
}
@property({type: Boolean})
@property({ type: Boolean })
showWebhook = false;
getSuccessMessage(): string {
@ -32,24 +31,33 @@ export class TransportForm extends ModelForm<NotificationTransport, string> {
if (this.instance) {
return new EventsApi(DEFAULT_CONFIG).eventsTransportsUpdate({
uuid: this.instance.pk || "",
notificationTransportRequest: data
notificationTransportRequest: data,
});
} else {
return new EventsApi(DEFAULT_CONFIG).eventsTransportsCreate({
notificationTransportRequest: data
notificationTransportRequest: data,
});
}
};
renderTransportModes(): TemplateResult {
return html`
<option value=${NotificationTransportModeEnum.Email} ?selected=${this.instance?.mode === NotificationTransportModeEnum.Email}>
<option
value=${NotificationTransportModeEnum.Email}
?selected=${this.instance?.mode === NotificationTransportModeEnum.Email}
>
${t`Email`}
</option>
<option value=${NotificationTransportModeEnum.Webhook} ?selected=${this.instance?.mode === NotificationTransportModeEnum.Webhook}>
<option
value=${NotificationTransportModeEnum.Webhook}
?selected=${this.instance?.mode === NotificationTransportModeEnum.Webhook}
>
${t`Webhook (generic)`}
</option>
<option value=${NotificationTransportModeEnum.WebhookSlack} ?selected=${this.instance?.mode === NotificationTransportModeEnum.WebhookSlack}>
<option
value=${NotificationTransportModeEnum.WebhookSlack}
?selected=${this.instance?.mode === NotificationTransportModeEnum.WebhookSlack}
>
${t`Webhook (Slack/Discord)`}
</option>
`;
@ -62,7 +70,10 @@ export class TransportForm extends ModelForm<NotificationTransport, string> {
}
onModeChange(mode: string): void {
if (mode === NotificationTransportModeEnum.Webhook || mode === NotificationTransportModeEnum.WebhookSlack) {
if (
mode === NotificationTransportModeEnum.Webhook ||
mode === NotificationTransportModeEnum.WebhookSlack
) {
this.showWebhook = true;
} else {
this.showWebhook = false;
@ -71,39 +82,49 @@ export class TransportForm extends ModelForm<NotificationTransport, string> {
renderForm(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
<ak-form-element-horizontal
label=${t`Name`}
?required=${true}
name="name">
<input type="text" value="${ifDefined(this.instance?.name)}" class="pf-c-form-control" required>
<ak-form-element-horizontal label=${t`Name`} ?required=${true} name="name">
<input
type="text"
value="${ifDefined(this.instance?.name)}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Mode`}
?required=${true}
name="mode">
<select class="pf-c-form-control" @change=${(ev: Event) => {
const current = (ev.target as HTMLInputElement).value;
this.onModeChange(current);
}}>
<ak-form-element-horizontal label=${t`Mode`} ?required=${true} name="mode">
<select
class="pf-c-form-control"
@change=${(ev: Event) => {
const current = (ev.target as HTMLInputElement).value;
this.onModeChange(current);
}}
>
${this.renderTransportModes()}
</select>
</ak-form-element-horizontal>
<ak-form-element-horizontal
?hidden=${!this.showWebhook}
label=${t`Webhook URL`}
name="webhookUrl">
<input type="text" value="${ifDefined(this.instance?.webhookUrl)}" class="pf-c-form-control">
name="webhookUrl"
>
<input
type="text"
value="${ifDefined(this.instance?.webhookUrl)}"
class="pf-c-form-control"
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal name="sendOnce">
<div class="pf-c-check">
<input type="checkbox" class="pf-c-check__input" ?checked=${first(this.instance?.sendOnce, false)}>
<label class="pf-c-check__label">
${t`Send once`}
</label>
<input
type="checkbox"
class="pf-c-check__input"
?checked=${first(this.instance?.sendOnce, false)}
/>
<label class="pf-c-check__label"> ${t`Send once`} </label>
</div>
<p class="pf-c-form__helper-text">${t`Only send notification once, for example when sending a webhook into a chat channel.`}</p>
<p class="pf-c-form__helper-text">
${t`Only send notification once, for example when sending a webhook into a chat channel.`}
</p>
</ak-form-element-horizontal>
</form>`;
}
}

View File

@ -52,64 +52,50 @@ export class TransportListPage extends TablePage<NotificationTransport> {
return [
html`${item.name}`,
html`${item.modeVerbose}`,
html`
<ak-action-button
.apiRequest=${() => {
return new EventsApi(DEFAULT_CONFIG).eventsTransportsTestCreate({
uuid: item.pk || "",
});
}}>
${t`Test`}
</ak-action-button>
<ak-forms-modal>
<span slot="submit">
${t`Update`}
</span>
<span slot="header">
${t`Update Notification Transport`}
</span>
<ak-event-transport-form slot="form" .instancePk=${item.pk}>
</ak-event-transport-form>
<button slot="trigger" class="pf-c-button pf-m-secondary">
${t`Edit`}
</button>
</ak-forms-modal>
<ak-forms-delete
.obj=${item}
objectLabel=${t`Notifications Transport`}
.usedBy=${() => {
return new EventsApi(DEFAULT_CONFIG).eventsTransportsUsedByList({
uuid: item.pk
});
}}
.delete=${() => {
return new EventsApi(DEFAULT_CONFIG).eventsTransportsDestroy({
uuid: item.pk
});
}}>
<button slot="trigger" class="pf-c-button pf-m-danger">
${t`Delete`}
</button>
</ak-forms-delete>`,
html` <ak-action-button
.apiRequest=${() => {
return new EventsApi(DEFAULT_CONFIG).eventsTransportsTestCreate({
uuid: item.pk || "",
});
}}
>
${t`Test`}
</ak-action-button>
<ak-forms-modal>
<span slot="submit"> ${t`Update`} </span>
<span slot="header"> ${t`Update Notification Transport`} </span>
<ak-event-transport-form slot="form" .instancePk=${item.pk}>
</ak-event-transport-form>
<button slot="trigger" class="pf-c-button pf-m-secondary">${t`Edit`}</button>
</ak-forms-modal>
<ak-forms-delete
.obj=${item}
objectLabel=${t`Notifications Transport`}
.usedBy=${() => {
return new EventsApi(DEFAULT_CONFIG).eventsTransportsUsedByList({
uuid: item.pk,
});
}}
.delete=${() => {
return new EventsApi(DEFAULT_CONFIG).eventsTransportsDestroy({
uuid: item.pk,
});
}}
>
<button slot="trigger" class="pf-c-button pf-m-danger">${t`Delete`}</button>
</ak-forms-delete>`,
];
}
renderToolbar(): TemplateResult {
return html`
<ak-forms-modal>
<span slot="submit">
${t`Create`}
</span>
<span slot="header">
${t`Create Notification Transport`}
</span>
<ak-event-transport-form slot="form">
</ak-event-transport-form>
<button slot="trigger" class="pf-c-button pf-m-primary">
${t`Create`}
</button>
</ak-forms-modal>
${super.renderToolbar()}
<ak-forms-modal>
<span slot="submit"> ${t`Create`} </span>
<span slot="header"> ${t`Create Notification Transport`} </span>
<ak-event-transport-form slot="form"> </ak-event-transport-form>
<button slot="trigger" class="pf-c-button pf-m-primary">${t`Create`}</button>
</ak-forms-modal>
${super.renderToolbar()}
`;
}
}

View File

@ -47,87 +47,78 @@ export class BoundStagesList extends Table<FlowStageBinding> {
html`${item.order}`,
html`${item.stageObj?.name}`,
html`${item.stageObj?.verboseName}`,
html`
<ak-forms-modal>
<span slot="submit">
${t`Update`}
</span>
<span slot="header">
${t`Update ${item.stageObj?.verboseName}`}
</span>
<ak-proxy-form
slot="form"
.args=${{
"instancePk": item.stage
html` <ak-forms-modal>
<span slot="submit"> ${t`Update`} </span>
<span slot="header"> ${t`Update ${item.stageObj?.verboseName}`} </span>
<ak-proxy-form
slot="form"
.args=${{
instancePk: item.stage,
}}
type=${ifDefined(item.stageObj?.component)}
>
</ak-proxy-form>
<button slot="trigger" class="pf-c-button pf-m-secondary">
${t`Edit Stage`}
</button>
</ak-forms-modal>
<ak-forms-modal>
<span slot="submit"> ${t`Update`} </span>
<span slot="header"> ${t`Update Stage binding`} </span>
<ak-stage-binding-form slot="form" .instancePk=${item.pk}>
</ak-stage-binding-form>
<button slot="trigger" class="pf-c-button pf-m-secondary">
${t`Edit Binding`}
</button>
</ak-forms-modal>
<ak-forms-delete
.obj=${item}
objectLabel=${t`Stage binding`}
.usedBy=${() => {
return new FlowsApi(DEFAULT_CONFIG).flowsBindingsUsedByList({
fsbUuid: item.pk,
});
}}
type=${ifDefined(item.stageObj?.component)}>
</ak-proxy-form>
<button slot="trigger" class="pf-c-button pf-m-secondary">
${t`Edit Stage`}
</button>
</ak-forms-modal>
<ak-forms-modal>
<span slot="submit">
${t`Update`}
</span>
<span slot="header">
${t`Update Stage binding`}
</span>
<ak-stage-binding-form slot="form" .instancePk=${item.pk}>
</ak-stage-binding-form>
<button slot="trigger" class="pf-c-button pf-m-secondary">
${t`Edit Binding`}
</button>
</ak-forms-modal>
<ak-forms-delete
.obj=${item}
objectLabel=${t`Stage binding`}
.usedBy=${() => {
return new FlowsApi(DEFAULT_CONFIG).flowsBindingsUsedByList({
fsbUuid: item.pk
});
}}
.delete=${() => {
return new FlowsApi(DEFAULT_CONFIG).flowsBindingsDestroy({
fsbUuid: item.pk,
});
}}>
<button slot="trigger" class="pf-c-button pf-m-danger">
${t`Delete Binding`}
</button>
</ak-forms-delete>`,
.delete=${() => {
return new FlowsApi(DEFAULT_CONFIG).flowsBindingsDestroy({
fsbUuid: item.pk,
});
}}
>
<button slot="trigger" class="pf-c-button pf-m-danger">
${t`Delete Binding`}
</button>
</ak-forms-delete>`,
];
}
renderExpanded(item: FlowStageBinding): TemplateResult {
return html`
<td></td>
<td role="cell" colspan="3">
<div class="pf-c-table__expandable-row-content">
<div class="pf-c-content">
<p>${t`These bindings control if this stage will be applied to the flow.`}</p>
<ak-bound-policies-list .target=${item.policybindingmodelPtrId}>
</ak-bound-policies-list>
return html` <td></td>
<td role="cell" colspan="3">
<div class="pf-c-table__expandable-row-content">
<div class="pf-c-content">
<p>
${t`These bindings control if this stage will be applied to the flow.`}
</p>
<ak-bound-policies-list .target=${item.policybindingmodelPtrId}>
</ak-bound-policies-list>
</div>
</div>
</div>
</td>
<td></td>
<td></td>`;
</td>
<td></td>
<td></td>`;
}
renderEmpty(): TemplateResult {
return super.renderEmpty(html`<ak-empty-state header=${t`No Stages bound`} icon="pf-icon-module">
<div slot="body">
${t`No stages are currently bound to this flow.`}
</div>
return super.renderEmpty(html`<ak-empty-state
header=${t`No Stages bound`}
icon="pf-icon-module"
>
<div slot="body">${t`No stages are currently bound to this flow.`}</div>
<div slot="primary">
<ak-forms-modal>
<span slot="submit">
${t`Create`}
</span>
<span slot="header">
${t`Create Stage binding`}
</span>
<span slot="submit"> ${t`Create`} </span>
<span slot="header"> ${t`Create Stage binding`} </span>
<ak-stage-binding-form slot="form" targetPk=${ifDefined(this.target)}>
</ak-stage-binding-form>
<button slot="trigger" class="pf-c-button pf-m-primary">
@ -140,50 +131,41 @@ export class BoundStagesList extends Table<FlowStageBinding> {
renderToolbar(): TemplateResult {
return html`
<ak-forms-modal>
<span slot="submit">
${t`Create`}
</span>
<span slot="header">
${t`Create Stage binding`}
</span>
<ak-stage-binding-form slot="form" targetPk=${ifDefined(this.target)}>
</ak-stage-binding-form>
<button slot="trigger" class="pf-c-button pf-m-primary">
${t`Bind stage`}
</button>
</ak-forms-modal>
<ak-dropdown class="pf-c-dropdown">
<button class="pf-m-secondary pf-c-button pf-c-dropdown__toggle" type="button">
<span class="pf-c-dropdown__toggle-text">${t`Create Stage`}</span>
<i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i>
</button>
<ul class="pf-c-dropdown__menu" hidden>
${until(new StagesApi(DEFAULT_CONFIG).stagesAllTypesList().then((types) => {
return types.map((type) => {
return html`<li>
<ak-forms-modal>
<span slot="submit">
${t`Create`}
</span>
<span slot="header">
${t`Create ${type.name}`}
</span>
<ak-proxy-form
slot="form"
type=${type.component}>
</ak-proxy-form>
<button slot="trigger" class="pf-c-dropdown__menu-item">
${type.name}<br>
<small>${type.description}</small>
</button>
</ak-forms-modal>
</li>`;
});
}), html`<ak-spinner></ak-spinner>`)}
</ul>
</ak-dropdown>
${super.renderToolbar()}
<ak-forms-modal>
<span slot="submit"> ${t`Create`} </span>
<span slot="header"> ${t`Create Stage binding`} </span>
<ak-stage-binding-form slot="form" targetPk=${ifDefined(this.target)}>
</ak-stage-binding-form>
<button slot="trigger" class="pf-c-button pf-m-primary">${t`Bind stage`}</button>
</ak-forms-modal>
<ak-dropdown class="pf-c-dropdown">
<button class="pf-m-secondary pf-c-button pf-c-dropdown__toggle" type="button">
<span class="pf-c-dropdown__toggle-text">${t`Create Stage`}</span>
<i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i>
</button>
<ul class="pf-c-dropdown__menu" hidden>
${until(
new StagesApi(DEFAULT_CONFIG).stagesAllTypesList().then((types) => {
return types.map((type) => {
return html`<li>
<ak-forms-modal>
<span slot="submit"> ${t`Create`} </span>
<span slot="header"> ${t`Create ${type.name}`} </span>
<ak-proxy-form slot="form" type=${type.component}>
</ak-proxy-form>
<button slot="trigger" class="pf-c-dropdown__menu-item">
${type.name}<br />
<small>${type.description}</small>
</button>
</ak-forms-modal>
</li>`;
});
}),
html`<ak-spinner></ak-spinner>`,
)}
</ul>
</ak-dropdown>
${super.renderToolbar()}
`;
}
}

View File

@ -12,20 +12,21 @@ export const FILL_LIGHT_MODE = "#f0f0f0";
@customElement("ak-flow-diagram")
export class FlowDiagram extends LitElement {
_flowSlug?: string;
@property()
set flowSlug(value: string) {
this._flowSlug = value;
new FlowsApi(DEFAULT_CONFIG).flowsInstancesDiagramRetrieve({
slug: value,
}).then((data) => {
this.diagram = FlowChart.parse(data.diagram || "");
});
new FlowsApi(DEFAULT_CONFIG)
.flowsInstancesDiagramRetrieve({
slug: value,
})
.then((data) => {
this.diagram = FlowChart.parse(data.diagram || "");
});
}
@property({attribute: false})
@property({ attribute: false })
diagram?: FlowChart.Instance;
@property()
@ -73,5 +74,4 @@ export class FlowDiagram extends LitElement {
}
return loading(this.diagram, html``);
}
}

View File

@ -1,4 +1,10 @@
import { Flow, FlowDesignationEnum, PolicyEngineMode, FlowsApi, CapabilitiesEnum } from "authentik-api";
import {
Flow,
FlowDesignationEnum,
PolicyEngineMode,
FlowsApi,
CapabilitiesEnum,
} from "authentik-api";
import { t } from "@lingui/macro";
import { customElement, property } from "lit-element";
import { html, TemplateResult } from "lit-html";
@ -11,7 +17,6 @@ import { first } from "../../utils";
@customElement("ak-flow-form")
export class FlowForm extends ModelForm<Flow, string> {
loadInstance(pk: string): Promise<Flow> {
return new FlowsApi(DEFAULT_CONFIG).flowsInstancesRetrieve({
slug: pk,
@ -34,18 +39,18 @@ export class FlowForm extends ModelForm<Flow, string> {
if (this.instance) {
writeOp = new FlowsApi(DEFAULT_CONFIG).flowsInstancesUpdate({
slug: this.instance.slug,
flowRequest: data
flowRequest: data,
});
} else {
writeOp = new FlowsApi(DEFAULT_CONFIG).flowsInstancesCreate({
flowRequest: data
flowRequest: data,
});
}
return config().then((c) => {
if (c.capabilities.includes(CapabilitiesEnum.SaveMedia)) {
const icon = this.getFormFile();
if (icon || this.clearBackground) {
return writeOp.then(app => {
return writeOp.then((app) => {
return new FlowsApi(DEFAULT_CONFIG).flowsInstancesSetBackgroundCreate({
slug: app.slug,
file: icon,
@ -54,12 +59,12 @@ export class FlowForm extends ModelForm<Flow, string> {
});
}
} else {
return writeOp.then(app => {
return writeOp.then((app) => {
return new FlowsApi(DEFAULT_CONFIG).flowsInstancesSetBackgroundUrlCreate({
slug: app.slug,
setIconURLRequest: {
url: data.background || "",
}
},
});
});
}
@ -68,25 +73,46 @@ export class FlowForm extends ModelForm<Flow, string> {
renderDesignations(): TemplateResult {
return html`
<option value=${FlowDesignationEnum.Authentication} ?selected=${this.instance?.designation === FlowDesignationEnum.Authentication}>
<option
value=${FlowDesignationEnum.Authentication}
?selected=${this.instance?.designation === FlowDesignationEnum.Authentication}
>
${t`Authentication`}
</option>
<option value=${FlowDesignationEnum.Authorization} ?selected=${this.instance?.designation === FlowDesignationEnum.Authorization}>
<option
value=${FlowDesignationEnum.Authorization}
?selected=${this.instance?.designation === FlowDesignationEnum.Authorization}
>
${t`Authorization`}
</option>
<option value=${FlowDesignationEnum.Enrollment} ?selected=${this.instance?.designation === FlowDesignationEnum.Enrollment}>
<option
value=${FlowDesignationEnum.Enrollment}
?selected=${this.instance?.designation === FlowDesignationEnum.Enrollment}
>
${t`Enrollment`}
</option>
<option value=${FlowDesignationEnum.Invalidation} ?selected=${this.instance?.designation === FlowDesignationEnum.Invalidation}>
<option
value=${FlowDesignationEnum.Invalidation}
?selected=${this.instance?.designation === FlowDesignationEnum.Invalidation}
>
${t`Invalidation`}
</option>
<option value=${FlowDesignationEnum.Recovery} ?selected=${this.instance?.designation === FlowDesignationEnum.Recovery}>
<option
value=${FlowDesignationEnum.Recovery}
?selected=${this.instance?.designation === FlowDesignationEnum.Recovery}
>
${t`Recovery`}
</option>
<option value=${FlowDesignationEnum.StageConfiguration} ?selected=${this.instance?.designation === FlowDesignationEnum.StageConfiguration}>
<option
value=${FlowDesignationEnum.StageConfiguration}
?selected=${this.instance?.designation === FlowDesignationEnum.StageConfiguration}
>
${t`Stage Configuration`}
</option>
<option value=${FlowDesignationEnum.Unenrollment} ?selected=${this.instance?.designation === FlowDesignationEnum.Unenrollment}>
<option
value=${FlowDesignationEnum.Unenrollment}
?selected=${this.instance?.designation === FlowDesignationEnum.Unenrollment}
>
${t`Unenrollment`}
</option>
`;
@ -94,35 +120,48 @@ export class FlowForm extends ModelForm<Flow, string> {
renderForm(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
<ak-form-element-horizontal
label=${t`Name`}
?required=${true}
name="name">
<input type="text" value="${ifDefined(this.instance?.name)}" class="pf-c-form-control" required>
<ak-form-element-horizontal label=${t`Name`} ?required=${true} name="name">
<input
type="text"
value="${ifDefined(this.instance?.name)}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Title`}
?required=${true}
name="title">
<input type="text" value="${ifDefined(this.instance?.title)}" class="pf-c-form-control" required>
<ak-form-element-horizontal label=${t`Title`} ?required=${true} name="title">
<input
type="text"
value="${ifDefined(this.instance?.title)}"
class="pf-c-form-control"
required
/>
<p class="pf-c-form__helper-text">${t`Shown as the Title in Flow pages.`}</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Slug`}
?required=${true}
name="slug">
<input type="text" value="${ifDefined(this.instance?.slug)}" class="pf-c-form-control" required>
<ak-form-element-horizontal label=${t`Slug`} ?required=${true} name="slug">
<input
type="text"
value="${ifDefined(this.instance?.slug)}"
class="pf-c-form-control"
required
/>
<p class="pf-c-form__helper-text">${t`Visible in the URL.`}</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Policy engine mode`}
?required=${true}
name="policyEngineMode">
name="policyEngineMode"
>
<select class="pf-c-form-control">
<option value=${PolicyEngineMode.Any} ?selected=${this.instance?.policyEngineMode === PolicyEngineMode.Any}>
<option
value=${PolicyEngineMode.Any}
?selected=${this.instance?.policyEngineMode === PolicyEngineMode.Any}
>
${t`ANY, any policy must match to grant access.`}
</option>
<option value=${PolicyEngineMode.All} ?selected=${this.instance?.policyEngineMode === PolicyEngineMode.All}>
<option
value=${PolicyEngineMode.All}
?selected=${this.instance?.policyEngineMode === PolicyEngineMode.All}
>
${t`ALL, all policies must match to grant access.`}
</option>
</select>
@ -130,56 +169,88 @@ export class FlowForm extends ModelForm<Flow, string> {
<ak-form-element-horizontal
label=${t`Designation`}
?required=${true}
name="designation">
name="designation"
>
<select class="pf-c-form-control">
<option value="" ?selected=${this.instance?.designation === undefined}>---------</option>
<option value="" ?selected=${this.instance?.designation === undefined}>
---------
</option>
${this.renderDesignations()}
</select>
<p class="pf-c-form__helper-text">${t`Decides what this Flow is used for. For example, the Authentication flow is redirect to when an un-authenticated user visits authentik.`}</p>
<p class="pf-c-form__helper-text">
${t`Decides what this Flow is used for. For example, the Authentication flow is redirect to when an un-authenticated user visits authentik.`}
</p>
</ak-form-element-horizontal>
${until(config().then((c) => {
if (c.capabilities.includes(CapabilitiesEnum.SaveMedia)) {
${until(
config().then((c) => {
if (c.capabilities.includes(CapabilitiesEnum.SaveMedia)) {
return html`<ak-form-element-horizontal
label=${t`Background`}
name="background"
>
<input type="file" value="" class="pf-c-form-control" />
${this.instance?.background
? html`
<p class="pf-c-form__helper-text">
${t`Currently set to:`} ${this.instance?.background}
</p>
`
: html``}
<p class="pf-c-form__helper-text">
${t`Background shown during execution.`}
</p>
</ak-form-element-horizontal>
${this.instance?.background
? html`
<ak-form-element-horizontal>
<div class="pf-c-check">
<input
type="checkbox"
class="pf-c-check__input"
@change=${(ev: Event) => {
const target = ev.target as HTMLInputElement;
this.clearBackground = target.checked;
}}
/>
<label class="pf-c-check__label">
${t`Clear background image`}
</label>
</div>
<p class="pf-c-form__helper-text">
${t`Delete currently set background image.`}
</p>
</ak-form-element-horizontal>
`
: html``}`;
}
return html`<ak-form-element-horizontal
label=${t`Background`}
name="background">
<input type="file" value="" class="pf-c-form-control">
${this.instance?.background ? html`
<p class="pf-c-form__helper-text">${t`Currently set to:`} ${this.instance?.background}</p>
`: html``}
<p class="pf-c-form__helper-text">${t`Background shown during execution.`}</p>
</ak-form-element-horizontal>
${this.instance?.background ? html`
<ak-form-element-horizontal>
<div class="pf-c-check">
<input type="checkbox" class="pf-c-check__input" @change=${(ev: Event) => {
const target = ev.target as HTMLInputElement;
this.clearBackground = target.checked;
}}>
<label class="pf-c-check__label">
${t`Clear background image`}
</label>
</div>
<p class="pf-c-form__helper-text">${t`Delete currently set background image.`}</p>
</ak-form-element-horizontal>
`: html``}`;
}
return html`<ak-form-element-horizontal
label=${t`Background`}
name="background">
<input type="text" value="${first(this.instance?.background, "")}" class="pf-c-form-control">
<p class="pf-c-form__helper-text">${t`Background shown during execution.`}</p>
</ak-form-element-horizontal>`;
}))}
name="background"
>
<input
type="text"
value="${first(this.instance?.background, "")}"
class="pf-c-form-control"
/>
<p class="pf-c-form__helper-text">
${t`Background shown during execution.`}
</p>
</ak-form-element-horizontal>`;
}),
)}
<ak-form-element-horizontal name="compatibilityMode">
<div class="pf-c-check">
<input type="checkbox" class="pf-c-check__input" ?checked=${first(this.instance?.compatibilityMode, true)}>
<label class="pf-c-check__label">
${t`Compatibility mode`}
</label>
<input
type="checkbox"
class="pf-c-check__input"
?checked=${first(this.instance?.compatibilityMode, true)}
/>
<label class="pf-c-check__label"> ${t`Compatibility mode`} </label>
</div>
<p class="pf-c-form__helper-text">${t`Enable compatibility mode, increases compatibility with password managers on mobile devices.`}</p>
<p class="pf-c-form__helper-text">
${t`Enable compatibility mode, increases compatibility with password managers on mobile devices.`}
</p>
</ak-form-element-horizontal>
</form>`;
}
}

View File

@ -8,7 +8,6 @@ import "../../elements/forms/HorizontalFormElement";
@customElement("ak-flow-import-form")
export class FlowImportForm extends Form<Flow> {
getSuccessMessage(): string {
return t`Successfully imported flow.`;
}
@ -20,19 +19,16 @@ export class FlowImportForm extends Form<Flow> {
throw new Error("No form data");
}
return new FlowsApi(DEFAULT_CONFIG).flowsInstancesImportFlowCreate({
file: file
file: file,
});
};
renderForm(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
<ak-form-element-horizontal
label=${t`Flow`}
name="flow">
<input type="file" value="" class="pf-c-form-control">
<ak-form-element-horizontal label=${t`Flow`} name="flow">
<input type="file" value="" class="pf-c-form-control" />
<p class="pf-c-form__helper-text">${t`Background shown during execution.`}</p>
</ak-form-element-horizontal>
</form>`;
}
}

View File

@ -61,101 +61,79 @@ export class FlowListPage extends TablePage<Flow> {
html`${item.designation}`,
html`${Array.from(item.stages || []).length}`,
html`${Array.from(item.policies || []).length}`,
html`
<ak-forms-modal>
<span slot="submit">
${t`Update`}
</span>
<span slot="header">
${t`Update Flow`}
</span>
<ak-flow-form slot="form" .instancePk=${item.slug}>
</ak-flow-form>
<button slot="trigger" class="pf-c-button pf-m-secondary">
${t`Edit`}
html` <ak-forms-modal>
<span slot="submit"> ${t`Update`} </span>
<span slot="header"> ${t`Update Flow`} </span>
<ak-flow-form slot="form" .instancePk=${item.slug}> </ak-flow-form>
<button slot="trigger" class="pf-c-button pf-m-secondary">${t`Edit`}</button>
</ak-forms-modal>
<ak-forms-delete
.obj=${item}
objectLabel=${t`Flow`}
.usedBy=${() => {
return new FlowsApi(DEFAULT_CONFIG).flowsInstancesUsedByList({
slug: item.slug,
});
}}
.delete=${() => {
return new FlowsApi(DEFAULT_CONFIG).flowsInstancesDestroy({
slug: item.slug,
});
}}
>
<button slot="trigger" class="pf-c-button pf-m-danger">${t`Delete`}</button>
</ak-forms-delete>
<button
class="pf-c-button pf-m-secondary"
@click=${() => {
new FlowsApi(DEFAULT_CONFIG)
.flowsInstancesExecuteRetrieve({
slug: item.slug,
})
.then((link) => {
window.location.assign(
`${link.link}?next=/%23${window.location.href}`,
);
});
}}
>
${t`Execute`}
</button>
</ak-forms-modal>
<ak-forms-delete
.obj=${item}
objectLabel=${t`Flow`}
.usedBy=${() => {
return new FlowsApi(DEFAULT_CONFIG).flowsInstancesUsedByList({
slug: item.slug
});
}}
.delete=${() => {
return new FlowsApi(DEFAULT_CONFIG).flowsInstancesDestroy({
slug: item.slug
});
}}>
<button slot="trigger" class="pf-c-button pf-m-danger">
${t`Delete`}
</button>
</ak-forms-delete>
<button
class="pf-c-button pf-m-secondary"
@click=${() => {
new FlowsApi(DEFAULT_CONFIG).flowsInstancesExecuteRetrieve({
slug: item.slug
}).then(link => {
window.location.assign(`${link.link}?next=/%23${window.location.href}`);
});
}}>
${t`Execute`}
</button>
<a class="pf-c-button pf-m-secondary" href=${item.exportUrl}>
${t`Export`}
</a>`,
<a class="pf-c-button pf-m-secondary" href=${item.exportUrl}> ${t`Export`} </a>`,
];
}
renderToolbar(): TemplateResult {
return html`
<ak-forms-modal>
<span slot="submit">
${t`Create`}
</span>
<span slot="header">
${t`Create Flow`}
</span>
<ak-flow-form slot="form">
</ak-flow-form>
<button slot="trigger" class="pf-c-button pf-m-primary">
${t`Create`}
</button>
</ak-forms-modal>
<ak-forms-modal>
<span slot="submit">
${t`Import`}
</span>
<span slot="header">
${t`Import Flow`}
</span>
<ak-flow-import-form slot="form">
</ak-flow-import-form>
<button slot="trigger" class="pf-c-button pf-m-primary">
${t`Import`}
</button>
</ak-forms-modal>
${super.renderToolbar()}
<ak-forms-confirm
successMessage=${t`Successfully cleared flow cache`}
errorMessage=${t`Failed to delete flow cache`}
action=${t`Clear cache`}
.onConfirm=${() => {
return new FlowsApi(DEFAULT_CONFIG).flowsInstancesCacheClearCreate();
}}>
<span slot="header">
${t`Clear Flow cache`}
</span>
<p slot="body">
${t`Are you sure you want to clear the flow cache?
return html` <ak-forms-modal>
<span slot="submit"> ${t`Create`} </span>
<span slot="header"> ${t`Create Flow`} </span>
<ak-flow-form slot="form"> </ak-flow-form>
<button slot="trigger" class="pf-c-button pf-m-primary">${t`Create`}</button>
</ak-forms-modal>
<ak-forms-modal>
<span slot="submit"> ${t`Import`} </span>
<span slot="header"> ${t`Import Flow`} </span>
<ak-flow-import-form slot="form"> </ak-flow-import-form>
<button slot="trigger" class="pf-c-button pf-m-primary">${t`Import`}</button>
</ak-forms-modal>
${super.renderToolbar()}
<ak-forms-confirm
successMessage=${t`Successfully cleared flow cache`}
errorMessage=${t`Failed to delete flow cache`}
action=${t`Clear cache`}
.onConfirm=${() => {
return new FlowsApi(DEFAULT_CONFIG).flowsInstancesCacheClearCreate();
}}
>
<span slot="header"> ${t`Clear Flow cache`} </span>
<p slot="body">
${t`Are you sure you want to clear the flow cache?
This will cause all flows to be re-evaluated on their next usage.`}
</p>
<button slot="trigger" class="pf-c-button pf-m-secondary" type="button">
${t`Clear cache`}
</button>
<div slot="modal"></div>
</ak-forms-confirm>`;
</p>
<button slot="trigger" class="pf-c-button pf-m-secondary" type="button">
${t`Clear cache`}
</button>
<div slot="modal"></div>
</ak-forms-confirm>`;
}
}

View File

@ -1,5 +1,13 @@
import { t } from "@lingui/macro";
import { css, CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element";
import {
css,
CSSResult,
customElement,
html,
LitElement,
property,
TemplateResult,
} from "lit-element";
import "../../elements/Tabs";
import "../../elements/PageHeader";
@ -23,14 +31,16 @@ import PFGallery from "@patternfly/patternfly/layouts/Gallery/gallery.css";
export class FlowViewPage extends LitElement {
@property()
set flowSlug(value: string) {
new FlowsApi(DEFAULT_CONFIG).flowsInstancesRetrieve({
slug: value
}).then((flow) => {
this.flow = flow;
});
new FlowsApi(DEFAULT_CONFIG)
.flowsInstancesRetrieve({
slug: value,
})
.then((flow) => {
this.flow = flow;
});
}
@property({attribute: false})
@property({ attribute: false })
flow!: Flow;
static get styles(): CSSResult[] {
@ -42,7 +52,7 @@ export class FlowViewPage extends LitElement {
ak-tabs {
height: 100%;
}
`
`,
);
}
@ -53,10 +63,15 @@ export class FlowViewPage extends LitElement {
return html`<ak-page-header
icon="pf-icon pf-icon-process-automation"
header=${this.flow.name}
description=${this.flow.title}>
description=${this.flow.title}
>
</ak-page-header>
<ak-tabs>
<div slot="page-overview" data-tab-title="${t`Flow Overview`}" class="pf-c-page__main-section pf-m-no-padding-mobile">
<div
slot="page-overview"
data-tab-title="${t`Flow Overview`}"
class="pf-c-page__main-section pf-m-no-padding-mobile"
>
<div class="pf-l-gallery pf-m-gutter">
<div class="pf-c-card pf-l-gallery__item">
<div class="pf-c-card__title">${t`Related`}</div>
@ -64,30 +79,40 @@ export class FlowViewPage extends LitElement {
<dl class="pf-c-description-list">
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${t`Execute flow`}</span>
<span class="pf-c-description-list__text"
>${t`Execute flow`}</span
>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
<button
class="pf-c-button pf-m-primary"
@click=${() => {
new FlowsApi(DEFAULT_CONFIG).flowsInstancesExecuteRetrieve({
slug: this.flow.slug
}).then(link => {
const finalURL = `${link.link}?next=/%23${window.location.hash}`;
window.open(finalURL, "_blank");
});
}}>
new FlowsApi(DEFAULT_CONFIG)
.flowsInstancesExecuteRetrieve({
slug: this.flow.slug,
})
.then((link) => {
const finalURL = `${link.link}?next=/%23${window.location.hash}`;
window.open(finalURL, "_blank");
});
}}
>
${t`Execute`}
</button>
</div>
</dd>
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${t`Export flow`}</span>
<span class="pf-c-description-list__text"
>${t`Export flow`}</span
>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
<a class="pf-c-button pf-m-secondary" href=${this.flow.exportUrl}>
<a
class="pf-c-button pf-m-secondary"
href=${this.flow.exportUrl}
>
${t`Export`}
</a>
</div>
@ -96,40 +121,56 @@ export class FlowViewPage extends LitElement {
</dl>
</div>
</div>
<div class="pf-c-card pf-l-gallery__item" style="grid-column-end: span 4;grid-row-end: span 2;">
<div
class="pf-c-card pf-l-gallery__item"
style="grid-column-end: span 4;grid-row-end: span 2;"
>
<div class="pf-c-card">
<div class="pf-c-card__body">
<ak-flow-diagram flowSlug=${this.flow.slug}>
</ak-flow-diagram>
<ak-flow-diagram flowSlug=${this.flow.slug}> </ak-flow-diagram>
</div>
</div>
</div>
</div>
</div>
<div slot="page-stage-bindings" data-tab-title="${t`Stage Bindings`}" class="pf-c-page__main-section pf-m-no-padding-mobile">
<div
slot="page-stage-bindings"
data-tab-title="${t`Stage Bindings`}"
class="pf-c-page__main-section pf-m-no-padding-mobile"
>
<div class="pf-c-card">
<div class="pf-c-card__body">
<ak-bound-stages-list .target=${this.flow.pk}>
</ak-bound-stages-list>
<ak-bound-stages-list .target=${this.flow.pk}> </ak-bound-stages-list>
</div>
</div>
</div>
<div slot="page-policy-bindings" data-tab-title="${t`Policy / Group / User Bindings`}" class="pf-c-page__main-section pf-m-no-padding-mobile">
<div
slot="page-policy-bindings"
data-tab-title="${t`Policy / Group / User Bindings`}"
class="pf-c-page__main-section pf-m-no-padding-mobile"
>
<div class="pf-c-card">
<div class="pf-c-card__title">${t`These bindings control which users can access this flow.`}</div>
<div class="pf-c-card__title">
${t`These bindings control which users can access this flow.`}
</div>
<div class="pf-c-card__body">
<ak-bound-policies-list .target=${this.flow.policybindingmodelPtrId}>
</ak-bound-policies-list>
</div>
</div>
</div>
<div slot="page-changelog" data-tab-title="${t`Changelog`}" class="pf-c-page__main-section pf-m-no-padding-mobile">
<div
slot="page-changelog"
data-tab-title="${t`Changelog`}"
class="pf-c-page__main-section pf-m-no-padding-mobile"
>
<div class="pf-c-card">
<div class="pf-c-card__body">
<ak-object-changelog
targetModelPk=${this.flow.pk || ""}
targetModelApp="authentik_flows"
targetModelName="flow">
targetModelName="flow"
>
</ak-object-changelog>
</div>
</div>

View File

@ -1,4 +1,11 @@
import { FlowsApi, FlowStageBinding, InvalidResponseActionEnum, PolicyEngineMode, Stage, StagesApi } from "authentik-api";
import {
FlowsApi,
FlowStageBinding,
InvalidResponseActionEnum,
PolicyEngineMode,
Stage,
StagesApi,
} from "authentik-api";
import { t } from "@lingui/macro";
import { customElement, property } from "lit-element";
import { html, TemplateResult } from "lit-html";
@ -11,7 +18,6 @@ import { ModelForm } from "../../elements/forms/ModelForm";
@customElement("ak-stage-binding-form")
export class StageBindingForm extends ModelForm<FlowStageBinding, string> {
loadInstance(pk: string): Promise<FlowStageBinding> {
return new FlowsApi(DEFAULT_CONFIG).flowsBindingsRetrieve({
fsbUuid: pk,
@ -33,11 +39,11 @@ export class StageBindingForm extends ModelForm<FlowStageBinding, string> {
if (this.instance) {
return new FlowsApi(DEFAULT_CONFIG).flowsBindingsUpdate({
fsbUuid: this.instance.pk || "",
flowStageBindingRequest: data
flowStageBindingRequest: data,
});
} else {
return new FlowsApi(DEFAULT_CONFIG).flowsBindingsCreate({
flowStageBindingRequest: data
flowStageBindingRequest: data,
});
}
};
@ -45,11 +51,13 @@ export class StageBindingForm extends ModelForm<FlowStageBinding, string> {
groupStages(stages: Stage[]): TemplateResult {
return html`
<option value="">---------</option>
${groupBy<Stage>(stages, (s => s.verboseName || "")).map(([group, stages]) => {
${groupBy<Stage>(stages, (s) => s.verboseName || "").map(([group, stages]) => {
return html`<optgroup label=${group}>
${stages.map(stage => {
const selected = (this.instance?.stage === stage.pk);
return html`<option ?selected=${selected} value=${ifDefined(stage.pk)}>${stage.name}</option>`;
${stages.map((stage) => {
const selected = this.instance?.stage === stage.pk;
return html`<option ?selected=${selected} value=${ifDefined(stage.pk)}>
${stage.name}
</option>`;
})}
</optgroup>`;
})}
@ -60,36 +68,47 @@ export class StageBindingForm extends ModelForm<FlowStageBinding, string> {
if (this.instance) {
return Promise.resolve(this.instance.order);
}
return new FlowsApi(DEFAULT_CONFIG).flowsBindingsList({
target: this.targetPk || "",
}).then(bindings => {
const orders = bindings.results.map(binding => binding.order);
if (orders.length < 1) {
return 0;
}
return Math.max(...orders) + 1;
});
return new FlowsApi(DEFAULT_CONFIG)
.flowsBindingsList({
target: this.targetPk || "",
})
.then((bindings) => {
const orders = bindings.results.map((binding) => binding.order);
if (orders.length < 1) {
return 0;
}
return Math.max(...orders) + 1;
});
}
renderTarget(): TemplateResult {
if (this.instance?.target || this.targetPk) {
return html`
<input required name="target" type="hidden" value=${ifDefined(this.instance?.target || this.targetPk)}>
<input
required
name="target"
type="hidden"
value=${ifDefined(this.instance?.target || this.targetPk)}
/>
`;
}
return html`<ak-form-element-horizontal
label=${t`Target`}
?required=${true}
name="target">
return html`<ak-form-element-horizontal label=${t`Target`} ?required=${true} name="target">
<select class="pf-c-form-control">
${until(new FlowsApi(DEFAULT_CONFIG).flowsInstancesList({
ordering: "pk"
}).then(flows => {
return flows.results.map(flow => {
// No ?selected check here, as this input isnt shown on update forms
return html`<option value=${ifDefined(flow.pk)}>${flow.name} (${flow.slug})</option>`;
});
}), html`<option>${t`Loading...`}</option>`)}
${until(
new FlowsApi(DEFAULT_CONFIG)
.flowsInstancesList({
ordering: "pk",
})
.then((flows) => {
return flows.results.map((flow) => {
// No ?selected check here, as this input isnt shown on update forms
return html`<option value=${ifDefined(flow.pk)}>
${flow.name} (${flow.slug})
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
</ak-form-element-horizontal>`;
}
@ -97,30 +116,36 @@ export class StageBindingForm extends ModelForm<FlowStageBinding, string> {
renderForm(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
${this.renderTarget()}
<ak-form-element-horizontal
label=${t`Stage`}
?required=${true}
name="stage">
<ak-form-element-horizontal label=${t`Stage`} ?required=${true} name="stage">
<select class="pf-c-form-control">
${until(new StagesApi(DEFAULT_CONFIG).stagesAllList({
ordering: "pk"
}).then(stages => {
return this.groupStages(stages.results);
}), html`<option>${t`Loading...`}</option>`)}
${until(
new StagesApi(DEFAULT_CONFIG)
.stagesAllList({
ordering: "pk",
})
.then((stages) => {
return this.groupStages(stages.results);
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Order`}
?required=${true}
name="order">
<input type="number" value="${until(this.getOrder())}" class="pf-c-form-control" required>
<ak-form-element-horizontal label=${t`Order`} ?required=${true} name="order">
<input
type="number"
value="${until(this.getOrder())}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal name="evaluateOnPlan">
<div class="pf-c-check">
<input type="checkbox" class="pf-c-check__input" ?checked=${first(this.instance?.evaluateOnPlan, true)}>
<label class="pf-c-check__label">
${t`Evaluate on plan`}
</label>
<input
type="checkbox"
class="pf-c-check__input"
?checked=${first(this.instance?.evaluateOnPlan, true)}
/>
<label class="pf-c-check__label"> ${t`Evaluate on plan`} </label>
</div>
<p class="pf-c-form__helper-text">
${t`Evaluate policies during the Flow planning process. Disable this for input-based policies. Should be used in conjunction with 'Re-evaluate policies', as with both options disabled, policies are **not** evaluated.`}
@ -128,44 +153,69 @@ export class StageBindingForm extends ModelForm<FlowStageBinding, string> {
</ak-form-element-horizontal>
<ak-form-element-horizontal name="reEvaluatePolicies">
<div class="pf-c-check">
<input type="checkbox" class="pf-c-check__input" ?checked=${first(this.instance?.reEvaluatePolicies, false)}>
<label class="pf-c-check__label">
${t`Re-evaluate policies`}
</label>
<input
type="checkbox"
class="pf-c-check__input"
?checked=${first(this.instance?.reEvaluatePolicies, false)}
/>
<label class="pf-c-check__label"> ${t`Re-evaluate policies`} </label>
</div>
<p class="pf-c-form__helper-text">${t`Evaluate policies before the Stage is present to the user.`}</p>
<p class="pf-c-form__helper-text">
${t`Evaluate policies before the Stage is present to the user.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Invalid response action`}
?required=${true}
name="invalidResponseAction">
name="invalidResponseAction"
>
<select class="pf-c-form-control">
<option value=${InvalidResponseActionEnum.Retry} ?selected=${this.instance?.invalidResponseAction === InvalidResponseActionEnum.Retry}>
<option
value=${InvalidResponseActionEnum.Retry}
?selected=${this.instance?.invalidResponseAction ===
InvalidResponseActionEnum.Retry}
>
${t`RETRY returns the error message and a similar challenge to the executor.`}
</option>
<option value=${InvalidResponseActionEnum.Restart} ?selected=${this.instance?.invalidResponseAction === InvalidResponseActionEnum.Restart}>
<option
value=${InvalidResponseActionEnum.Restart}
?selected=${this.instance?.invalidResponseAction ===
InvalidResponseActionEnum.Restart}
>
${t`RESTART restarts the flow from the beginning.`}
</option>
<option value=${InvalidResponseActionEnum.RestartWithContext} ?selected=${this.instance?.invalidResponseAction === InvalidResponseActionEnum.RestartWithContext}>
<option
value=${InvalidResponseActionEnum.RestartWithContext}
?selected=${this.instance?.invalidResponseAction ===
InvalidResponseActionEnum.RestartWithContext}
>
${t`RESTART restarts the flow from the beginning, while keeping the flow context.`}
</option>
</select>
<p class="pf-c-form__helper-text">${t`Configure how the flow executor should handle an invalid response to a challenge.`}</p>
<p class="pf-c-form__helper-text">
${t`Configure how the flow executor should handle an invalid response to a challenge.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Policy engine mode`}
?required=${true}
name="policyEngineMode">
name="policyEngineMode"
>
<select class="pf-c-form-control">
<option value=${PolicyEngineMode.Any} ?selected=${this.instance?.policyEngineMode === PolicyEngineMode.Any}>
<option
value=${PolicyEngineMode.Any}
?selected=${this.instance?.policyEngineMode === PolicyEngineMode.Any}
>
${t`ANY, any policy must match to include this stage access.`}
</option>
<option value=${PolicyEngineMode.All} ?selected=${this.instance?.policyEngineMode === PolicyEngineMode.All}>
<option
value=${PolicyEngineMode.All}
?selected=${this.instance?.policyEngineMode === PolicyEngineMode.All}
>
${t`ALL, all policies must match to include this stage access.`}
</option>
</select>
</ak-form-element-horizontal>
</form>`;
}
}

View File

@ -16,10 +16,9 @@ import { ModelForm } from "../../elements/forms/ModelForm";
@customElement("ak-group-form")
export class GroupForm extends ModelForm<Group, string> {
loadInstance(pk: string): Promise<Group> {
return new CoreApi(DEFAULT_CONFIG).coreGroupsRetrieve({
groupUuid: pk
groupUuid: pk,
});
}
@ -35,101 +34,129 @@ export class GroupForm extends ModelForm<Group, string> {
if (this.instance?.pk) {
return new CoreApi(DEFAULT_CONFIG).coreGroupsUpdate({
groupUuid: this.instance.pk || "",
groupRequest: data
groupRequest: data,
});
} else {
data.users = Array.from(this.instance?.users || []);
return new CoreApi(DEFAULT_CONFIG).coreGroupsCreate({
groupRequest: data
groupRequest: data,
});
}
};
renderForm(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
<ak-form-element-horizontal
label=${t`Name`}
?required=${true}
name="name">
<input type="text" value="${ifDefined(this.instance?.name)}" class="pf-c-form-control" required>
<ak-form-element-horizontal label=${t`Name`} ?required=${true} name="name">
<input
type="text"
value="${ifDefined(this.instance?.name)}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal name="isSuperuser">
<div class="pf-c-check">
<input type="checkbox" class="pf-c-check__input" ?checked=${first(this.instance?.isSuperuser, false)}>
<label class="pf-c-check__label">
${t`Is superuser`}
</label>
<input
type="checkbox"
class="pf-c-check__input"
?checked=${first(this.instance?.isSuperuser, false)}
/>
<label class="pf-c-check__label"> ${t`Is superuser`} </label>
</div>
<p class="pf-c-form__helper-text">${t`Users added to this group will be superusers.`}</p>
<p class="pf-c-form__helper-text">
${t`Users added to this group will be superusers.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Parent`}
name="parent">
<ak-form-element-horizontal label=${t`Parent`} name="parent">
<select class="pf-c-form-control">
<option value="" ?selected=${this.instance?.parent === undefined}>---------</option>
${until(new CoreApi(DEFAULT_CONFIG).coreGroupsList({}).then(groups => {
return groups.results.map(group => {
return html`<option value=${ifDefined(group.pk)} ?selected=${this.instance?.parent === group.pk}>${group.name}</option>`;
});
}), html`<option>${t`Loading...`}</option>`)}
<option value="" ?selected=${this.instance?.parent === undefined}>
---------
</option>
${until(
new CoreApi(DEFAULT_CONFIG).coreGroupsList({}).then((groups) => {
return groups.results.map((group) => {
return html`<option
value=${ifDefined(group.pk)}
?selected=${this.instance?.parent === group.pk}
>
${group.name}
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Members`}
name="users">
<ak-form-element-horizontal label=${t`Members`} name="users">
<div class="pf-c-input-group">
<ak-group-member-select-table
.confirm=${(items: User[]) => {
// Because the model only has the IDs, map the user list to IDs
const ids = items.map(u => u.pk || 0);
const ids = items.map((u) => u.pk || 0);
if (!this.instance) this.instance = {} as Group;
this.instance.users = Array.from(this.instance?.users || []).concat(ids);
this.instance.users = Array.from(this.instance?.users || []).concat(
ids,
);
this.requestUpdate();
return Promise.resolve();
}}>
}}
>
<button slot="trigger" class="pf-c-button pf-m-control" type="button">
<i class="fas fa-plus" aria-hidden="true"></i>
</button>
</ak-group-member-select-table>
<div class="pf-c-form-control">
<ak-chip-group>
${until(new CoreApi(DEFAULT_CONFIG).coreUsersList({
ordering: "username",
}).then(users => {
return users.results.map(user => {
const selected = Array.from(this.instance?.users || []).some(su => {
return su == user.pk;
});
if (!selected) return;
return html`<ak-chip
.removable=${true}
value=${ifDefined(user.pk)}
@remove=${() => {
if (!this.instance) return;
const users = Array.from(this.instance?.users || []);
const idx = users.indexOf(user.pk || 0);
users.splice(idx, 1);
this.instance.users = users;
this.requestUpdate();
}}>
${user.username}
</ak-chip>`;
});
}), html`<option>${t`Loading...`}</option>`)}
${until(
new CoreApi(DEFAULT_CONFIG)
.coreUsersList({
ordering: "username",
})
.then((users) => {
return users.results.map((user) => {
const selected = Array.from(
this.instance?.users || [],
).some((su) => {
return su == user.pk;
});
if (!selected) return;
return html`<ak-chip
.removable=${true}
value=${ifDefined(user.pk)}
@remove=${() => {
if (!this.instance) return;
const users = Array.from(
this.instance?.users || [],
);
const idx = users.indexOf(user.pk || 0);
users.splice(idx, 1);
this.instance.users = users;
this.requestUpdate();
}}
>
${user.username}
</ak-chip>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
</ak-chip-group>
</div>
</div>
<p class="pf-c-form__helper-text">${t`Hold control/command to select multiple items.`}</p>
<p class="pf-c-form__helper-text">
${t`Hold control/command to select multiple items.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Attributes`}
?required=${true}
name="attributes">
<ak-codemirror mode="yaml" value="${YAML.stringify(first(this.instance?.attributes, {}))}">
<ak-form-element-horizontal label=${t`Attributes`} ?required=${true} name="attributes">
<ak-codemirror
mode="yaml"
value="${YAML.stringify(first(this.instance?.attributes, {}))}"
>
</ak-codemirror>
<p class="pf-c-form__helper-text">${t`Set custom attributes using YAML or JSON.`}</p>
<p class="pf-c-form__helper-text">
${t`Set custom attributes using YAML or JSON.`}
</p>
</ak-form-element-horizontal>
</form>`;
}
}

View File

@ -55,56 +55,40 @@ export class GroupListPage extends TablePage<Group> {
html`${item.parent || "-"}`,
html`${Array.from(item.users || []).length}`,
html`${item.isSuperuser ? t`Yes` : t`No`}`,
html`
<ak-forms-modal>
<span slot="submit">
${t`Update`}
</span>
<span slot="header">
${t`Update Group`}
</span>
<ak-group-form slot="form" .instancePk=${item.pk}>
</ak-group-form>
<button slot="trigger" class="pf-c-button pf-m-secondary">
${t`Edit`}
</button>
</ak-forms-modal>
<ak-forms-delete
.obj=${item}
objectLabel=${t`Group`}
.usedBy=${() => {
return new CoreApi(DEFAULT_CONFIG).coreGroupsUsedByList({
groupUuid: item.pk
});
}}
.delete=${() => {
return new CoreApi(DEFAULT_CONFIG).coreGroupsDestroy({
groupUuid: item.pk
});
}}>
<button slot="trigger" class="pf-c-button pf-m-danger">
${t`Delete`}
</button>
</ak-forms-delete>`,
html` <ak-forms-modal>
<span slot="submit"> ${t`Update`} </span>
<span slot="header"> ${t`Update Group`} </span>
<ak-group-form slot="form" .instancePk=${item.pk}> </ak-group-form>
<button slot="trigger" class="pf-c-button pf-m-secondary">${t`Edit`}</button>
</ak-forms-modal>
<ak-forms-delete
.obj=${item}
objectLabel=${t`Group`}
.usedBy=${() => {
return new CoreApi(DEFAULT_CONFIG).coreGroupsUsedByList({
groupUuid: item.pk,
});
}}
.delete=${() => {
return new CoreApi(DEFAULT_CONFIG).coreGroupsDestroy({
groupUuid: item.pk,
});
}}
>
<button slot="trigger" class="pf-c-button pf-m-danger">${t`Delete`}</button>
</ak-forms-delete>`,
];
}
renderToolbar(): TemplateResult {
return html`
<ak-forms-modal>
<span slot="submit">
${t`Create`}
</span>
<span slot="header">
${t`Create Group`}
</span>
<ak-group-form slot="form">
</ak-group-form>
<button slot="trigger" class="pf-c-button pf-m-primary">
${t`Create`}
</button>
</ak-forms-modal>
${super.renderToolbar()}
<ak-forms-modal>
<span slot="submit"> ${t`Create`} </span>
<span slot="header"> ${t`Create Group`} </span>
<ak-group-form slot="form"> </ak-group-form>
<button slot="trigger" class="pf-c-button pf-m-primary">${t`Create`}</button>
</ak-forms-modal>
${super.renderToolbar()}
`;
}
}

View File

@ -27,7 +27,7 @@ export class MemberSelectTable extends TableModal<User> {
return new CoreApi(DEFAULT_CONFIG).coreUsersList({
ordering: this.order,
page: page,
pageSize: PAGE_SIZE /2,
pageSize: PAGE_SIZE / 2,
search: this.search || "",
});
}
@ -57,33 +57,30 @@ export class MemberSelectTable extends TableModal<User> {
renderModalInner(): TemplateResult {
return html`<section class="pf-c-page__main-section pf-m-light">
<div class="pf-c-content">
<h1 class="pf-c-title pf-m-2xl">
${t`Select users to add`}
</h1>
</div>
</section>
<section class="pf-c-page__main-section pf-m-light">
${this.renderTable()}
</section>
<footer class="pf-c-modal-box__footer">
<ak-spinner-button
.callAction=${() => {
return this.confirm(this.selectedElements).then(() => {
<div class="pf-c-content">
<h1 class="pf-c-title pf-m-2xl">${t`Select users to add`}</h1>
</div>
</section>
<section class="pf-c-page__main-section pf-m-light">${this.renderTable()}</section>
<footer class="pf-c-modal-box__footer">
<ak-spinner-button
.callAction=${() => {
return this.confirm(this.selectedElements).then(() => {
this.open = false;
});
}}
class="pf-m-primary"
>
${t`Add`} </ak-spinner-button
>&nbsp;
<ak-spinner-button
.callAction=${async () => {
this.open = false;
});
}}
class="pf-m-primary">
${t`Add`}
</ak-spinner-button>&nbsp;
<ak-spinner-button
.callAction=${async () => {
this.open = false;
}}
class="pf-m-secondary">
${t`Cancel`}
</ak-spinner-button>
</footer>`;
}}
class="pf-m-secondary"
>
${t`Cancel`}
</ak-spinner-button>
</footer>`;
}
}

View File

@ -7,8 +7,7 @@ import { ModalButton } from "../../elements/buttons/ModalButton";
@customElement("ak-outpost-deployment-modal")
export class OutpostDeploymentModal extends ModalButton {
@property({attribute: false})
@property({ attribute: false })
outpost?: Outpost;
renderModalInner(): TemplateResult {
@ -16,25 +15,38 @@ export class OutpostDeploymentModal extends ModalButton {
<h1 class="pf-c-title pf-m-2xl">${t`Outpost Deployment Info`}</h1>
</div>
<div class="pf-c-modal-box__body">
<p><a target="_blank" href="https://goauthentik.io/docs/outposts/outposts/#deploy">${t`View deployment documentation`}</a></p>
<p>
<a target="_blank" href="https://goauthentik.io/docs/outposts/outposts/#deploy"
>${t`View deployment documentation`}</a
>
</p>
<form class="pf-c-form">
<div class="pf-c-form__group">
<label class="pf-c-form__label" for="help-text-simple-form-name">
<span class="pf-c-form__label-text">AUTHENTIK_HOST</span>
</label>
<input class="pf-c-form-control" readonly type="text" value="${document.location.origin}" />
<input
class="pf-c-form-control"
readonly
type="text"
value="${document.location.origin}"
/>
</div>
<div class="pf-c-form__group">
<label class="pf-c-form__label" for="help-text-simple-form-name">
<span class="pf-c-form__label-text">AUTHENTIK_TOKEN</span>
</label>
<div>
<ak-token-copy-button identifier="${ifDefined(this.outpost?.tokenIdentifier)}">
<ak-token-copy-button
identifier="${ifDefined(this.outpost?.tokenIdentifier)}"
>
${t`Click to copy token`}
</ak-token-copy-button>
</div>
</div>
<h3>${t`If your authentik Instance is using a self-signed certificate, set this value.`}</h3>
<h3>
${t`If your authentik Instance is using a self-signed certificate, set this value.`}
</h3>
<div class="pf-c-form__group">
<label class="pf-c-form__label" for="help-text-simple-form-name">
<span class="pf-c-form__label-text">AUTHENTIK_INSECURE</span>
@ -44,10 +56,14 @@ export class OutpostDeploymentModal extends ModalButton {
</form>
</div>
<footer class="pf-c-modal-box__footer pf-m-align-left">
<button class="pf-c-button pf-m-primary" @click=${() => {this.open = false;}}>
<button
class="pf-c-button pf-m-primary"
@click=${() => {
this.open = false;
}}
>
${t`Close`}
</button>
</footer>`;
}
}

View File

@ -12,17 +12,18 @@ import { ModelForm } from "../../elements/forms/ModelForm";
@customElement("ak-outpost-form")
export class OutpostForm extends ModelForm<Outpost, string> {
@property()
type: OutpostTypeEnum = OutpostTypeEnum.Proxy;
loadInstance(pk: string): Promise<Outpost> {
return new OutpostsApi(DEFAULT_CONFIG).outpostsInstancesRetrieve({
uuid: pk
}).then(o => {
this.type = o.type || OutpostTypeEnum.Proxy;
return o;
});
return new OutpostsApi(DEFAULT_CONFIG)
.outpostsInstancesRetrieve({
uuid: pk,
})
.then((o) => {
this.type = o.type || OutpostTypeEnum.Proxy;
return o;
});
}
getSuccessMessage(): string {
@ -37,11 +38,11 @@ export class OutpostForm extends ModelForm<Outpost, string> {
if (this.instance) {
return new OutpostsApi(DEFAULT_CONFIG).outpostsInstancesUpdate({
uuid: this.instance.pk || "",
outpostRequest: data
outpostRequest: data,
});
} else {
return new OutpostsApi(DEFAULT_CONFIG).outpostsInstancesCreate({
outpostRequest: data
outpostRequest: data,
});
}
};
@ -49,102 +50,155 @@ export class OutpostForm extends ModelForm<Outpost, string> {
renderProviders(): Promise<TemplateResult[]> {
switch (this.type) {
case OutpostTypeEnum.Proxy:
return new ProvidersApi(DEFAULT_CONFIG).providersProxyList({
ordering: "pk"
}).then(providers => {
return providers.results.map(provider => {
const selected = Array.from(this.instance?.providers || []).some(sp => {
return sp == provider.pk;
return new ProvidersApi(DEFAULT_CONFIG)
.providersProxyList({
ordering: "pk",
})
.then((providers) => {
return providers.results.map((provider) => {
const selected = Array.from(this.instance?.providers || []).some(
(sp) => {
return sp == provider.pk;
},
);
return html`<option
value=${ifDefined(provider.pk)}
?selected=${selected}
>
${provider.verboseName} ${provider.name}
</option>`;
});
return html`<option value=${ifDefined(provider.pk)} ?selected=${selected}>${provider.verboseName} ${provider.name}</option>`;
});
});
case OutpostTypeEnum.Ldap:
return new ProvidersApi(DEFAULT_CONFIG).providersLdapList({
ordering: "pk"
}).then(providers => {
return providers.results.map(provider => {
const selected = Array.from(this.instance?.providers || []).some(sp => {
return sp == provider.pk;
return new ProvidersApi(DEFAULT_CONFIG)
.providersLdapList({
ordering: "pk",
})
.then((providers) => {
return providers.results.map((provider) => {
const selected = Array.from(this.instance?.providers || []).some(
(sp) => {
return sp == provider.pk;
},
);
return html`<option
value=${ifDefined(provider.pk)}
?selected=${selected}
>
${provider.verboseName} ${provider.name}
</option>`;
});
return html`<option value=${ifDefined(provider.pk)} ?selected=${selected}>${provider.verboseName} ${provider.name}</option>`;
});
});
}
}
renderForm(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
<ak-form-element-horizontal
label=${t`Name`}
?required=${true}
name="name">
<input type="text" value="${ifDefined(this.instance?.name)}" class="pf-c-form-control" required>
<ak-form-element-horizontal label=${t`Name`} ?required=${true} name="name">
<input
type="text"
value="${ifDefined(this.instance?.name)}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Type`}
?required=${true}
name="type">
<select class="pf-c-form-control" @change=${(ev: Event) => {
const target = ev.target as HTMLSelectElement;
this.type = target.selectedOptions[0].value as OutpostTypeEnum;
}}>
<option value=${OutpostTypeEnum.Proxy} ?selected=${this.instance?.type === OutpostTypeEnum.Proxy}>${t`Proxy`}</option>
<option value=${OutpostTypeEnum.Ldap} ?selected=${this.instance?.type === OutpostTypeEnum.Ldap}>${t`LDAP (Technical preview)`}</option>
<ak-form-element-horizontal label=${t`Type`} ?required=${true} name="type">
<select
class="pf-c-form-control"
@change=${(ev: Event) => {
const target = ev.target as HTMLSelectElement;
this.type = target.selectedOptions[0].value as OutpostTypeEnum;
}}
>
<option
value=${OutpostTypeEnum.Proxy}
?selected=${this.instance?.type === OutpostTypeEnum.Proxy}
>
${t`Proxy`}
</option>
<option
value=${OutpostTypeEnum.Ldap}
?selected=${this.instance?.type === OutpostTypeEnum.Ldap}
>
${t`LDAP (Technical preview)`}
</option>
</select>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Integration`}
name="serviceConnection">
<ak-form-element-horizontal label=${t`Integration`} name="serviceConnection">
<select class="pf-c-form-control">
<option value="" ?selected=${this.instance?.serviceConnection === undefined}>---------</option>
${until(new OutpostsApi(DEFAULT_CONFIG).outpostsServiceConnectionsAllList({
ordering: "pk"
}).then(scs => {
return scs.results.map(sc => {
let selected = this.instance?.serviceConnection === sc.pk;
if (scs.results.length === 1 && !this.instance) {
selected = true;
}
return html`<option value=${ifDefined(sc.pk)} ?selected=${selected}>
${sc.name} (${sc.verboseName})
</option>`;
});
}), html`<option>${t`Loading...`}</option>`)}
<option value="" ?selected=${this.instance?.serviceConnection === undefined}>
---------
</option>
${until(
new OutpostsApi(DEFAULT_CONFIG)
.outpostsServiceConnectionsAllList({
ordering: "pk",
})
.then((scs) => {
return scs.results.map((sc) => {
let selected = this.instance?.serviceConnection === sc.pk;
if (scs.results.length === 1 && !this.instance) {
selected = true;
}
return html`<option
value=${ifDefined(sc.pk)}
?selected=${selected}
>
${sc.name} (${sc.verboseName})
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
<p class="pf-c-form__helper-text">
${t`Selecting an integration enables the management of the outpost by authentik.`}
</p>
<p class="pf-c-form__helper-text">
See <a target="_blank" href="https://goauthentik.io/docs/outposts/outposts">documentation</a>.
See
<a target="_blank" href="https://goauthentik.io/docs/outposts/outposts"
>documentation</a
>.
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Providers`}
?required=${true}
name="providers">
<ak-form-element-horizontal label=${t`Providers`} ?required=${true} name="providers">
<select class="pf-c-form-control" multiple>
${until(this.renderProviders(), html`<option>${t`Loading...`}</option>`)}
</select>
<p class="pf-c-form__helper-text">${t`You can only select providers that match the type of the outpost.`}</p>
<p class="pf-c-form__helper-text">${t`Hold control/command to select multiple items.`}</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Configuration`}
name="config">
<ak-codemirror mode="yaml" value="${until(new OutpostsApi(DEFAULT_CONFIG).outpostsInstancesDefaultSettingsRetrieve().then(config => {
let fc = config.config;
if (this.instance) {
fc = this.instance.config;
}
return YAML.stringify(fc);
}))}"></ak-codemirror>
<p class="pf-c-form__helper-text">${t`Set custom attributes using YAML or JSON.`}</p>
<p class="pf-c-form__helper-text">
See <a target="_blank" href="https://goauthentik.io/docs/outposts/outposts#configuration">documentation</a>.
${t`You can only select providers that match the type of the outpost.`}
</p>
<p class="pf-c-form__helper-text">
${t`Hold control/command to select multiple items.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${t`Configuration`} name="config">
<ak-codemirror
mode="yaml"
value="${until(
new OutpostsApi(DEFAULT_CONFIG)
.outpostsInstancesDefaultSettingsRetrieve()
.then((config) => {
let fc = config.config;
if (this.instance) {
fc = this.instance.config;
}
return YAML.stringify(fc);
}),
)}"
></ak-codemirror>
<p class="pf-c-form__helper-text">
${t`Set custom attributes using YAML or JSON.`}
</p>
<p class="pf-c-form__helper-text">
See
<a
target="_blank"
href="https://goauthentik.io/docs/outposts/outposts#configuration"
>documentation</a
>.
</p>
</ak-form-element-horizontal>
</form>`;
}
}

View File

@ -10,14 +10,13 @@ import { EVENT_REFRESH } from "../../constants";
@customElement("ak-outpost-health")
export class OutpostHealthElement extends LitElement {
@property()
outpostId?: string;
@property({attribute: false})
@property({ attribute: false })
outpostHealth?: OutpostHealth[];
@property({attribute: false})
@property({ attribute: false })
showVersion = true;
static get styles(): CSSResult[] {
@ -34,11 +33,13 @@ export class OutpostHealthElement extends LitElement {
firstUpdated(): void {
if (!this.outpostId) return;
new OutpostsApi(DEFAULT_CONFIG).outpostsInstancesHealthList({
uuid: this.outpostId
}).then(health => {
this.outpostHealth = health;
});
new OutpostsApi(DEFAULT_CONFIG)
.outpostsInstancesHealthList({
uuid: this.outpostId,
})
.then((health) => {
this.outpostHealth = health;
});
}
render(): TemplateResult {
@ -46,29 +47,38 @@ export class OutpostHealthElement extends LitElement {
return html`<ak-spinner></ak-spinner>`;
}
if (this.outpostHealth.length === 0) {
return html`
<ul>
<li role="cell">
<ak-label color=${PFColor.Grey} text=${t`Not available`}></ak-label>
</li>
</ul>`;
return html` <ul>
<li role="cell">
<ak-label color=${PFColor.Grey} text=${t`Not available`}></ak-label>
</li>
</ul>`;
}
return html`<ul>${this.outpostHealth.map((h) => {
return html`<li>
<ul>
<li role="cell">
<ak-label color=${PFColor.Green} text=${t`Last seen: ${h.lastSeen?.toLocaleTimeString()}`}></ak-label>
</li>
${this.showVersion ?
html`<li role="cell">
${h.versionOutdated ?
html`<ak-label color=${PFColor.Red}
text=${t`${h.version}, should be ${h.versionShould}`}></ak-label>` :
html`<ak-label color=${PFColor.Green} text=${t`Version: ${h.version || ""}`}></ak-label>`}
</li>` : html``}
</ul>
</li>`;
})}</ul>`;
return html`<ul>
${this.outpostHealth.map((h) => {
return html`<li>
<ul>
<li role="cell">
<ak-label
color=${PFColor.Green}
text=${t`Last seen: ${h.lastSeen?.toLocaleTimeString()}`}
></ak-label>
</li>
${this.showVersion
? html`<li role="cell">
${h.versionOutdated
? html`<ak-label
color=${PFColor.Red}
text=${t`${h.version}, should be ${h.versionShould}`}
></ak-label>`
: html`<ak-label
color=${PFColor.Green}
text=${t`Version: ${h.version || ""}`}
></ak-label>`}
</li>`
: html``}
</ul>
</li>`;
})}
</ul>`;
}
}

View File

@ -58,90 +58,78 @@ export class OutpostListPage extends TablePage<Outpost> {
}
return [
html`${item.name}`,
html`<ul>${item.providersObj?.map((p) => {
return html`<li><a href="#/core/providers/${p.pk}">${p.name}</a></li>`;
})}</ul>`,
html`<ul>
${item.providersObj?.map((p) => {
return html`<li>
<a href="#/core/providers/${p.pk}">${p.name}</a>
</li>`;
})}
</ul>`,
html`${item.serviceConnectionObj?.name || t`No integration active`}`,
html`<ak-outpost-health outpostId=${ifDefined(item.pk)}></ak-outpost-health>`,
html`
<ak-forms-modal>
<span slot="submit">
${t`Update`}
</span>
<span slot="header">
${t`Update Outpost`}
</span>
<ak-outpost-form slot="form" .instancePk=${item.pk}>
</ak-outpost-form>
<button slot="trigger" class="pf-c-button pf-m-secondary">
${t`Edit`}
</button>
</ak-forms-modal>
<ak-forms-delete
.obj=${item}
objectLabel=${t`Outpost`}
.usedBy=${() => {
return new OutpostsApi(DEFAULT_CONFIG).outpostsInstancesUsedByList({
uuid: item.pk
});
}}
.delete=${() => {
return new OutpostsApi(DEFAULT_CONFIG).outpostsInstancesDestroy({
uuid: item.pk
});
}}>
<button slot="trigger" class="pf-c-button pf-m-danger">
${t`Delete`}
</button>
</ak-forms-delete>
<ak-outpost-deployment-modal .outpost=${item} size=${PFSize.Medium}>
<button slot="trigger" class="pf-c-button pf-m-tertiary">
${t`View Deployment Info`}
</button>
</ak-outpost-deployment-modal>`,
html` <ak-forms-modal>
<span slot="submit"> ${t`Update`} </span>
<span slot="header"> ${t`Update Outpost`} </span>
<ak-outpost-form slot="form" .instancePk=${item.pk}> </ak-outpost-form>
<button slot="trigger" class="pf-c-button pf-m-secondary">${t`Edit`}</button>
</ak-forms-modal>
<ak-forms-delete
.obj=${item}
objectLabel=${t`Outpost`}
.usedBy=${() => {
return new OutpostsApi(DEFAULT_CONFIG).outpostsInstancesUsedByList({
uuid: item.pk,
});
}}
.delete=${() => {
return new OutpostsApi(DEFAULT_CONFIG).outpostsInstancesDestroy({
uuid: item.pk,
});
}}
>
<button slot="trigger" class="pf-c-button pf-m-danger">${t`Delete`}</button>
</ak-forms-delete>
<ak-outpost-deployment-modal .outpost=${item} size=${PFSize.Medium}>
<button slot="trigger" class="pf-c-button pf-m-tertiary">
${t`View Deployment Info`}
</button>
</ak-outpost-deployment-modal>`,
];
}
rowInbuilt(item: Outpost): TemplateResult[] {
return [
html`${item.name}`,
html`<ul>${item.providersObj?.map((p) => {
return html`<li><a href="#/core/providers/${p.pk}">${p.name}</a></li>`;
})}</ul>`,
html`<ul>
${item.providersObj?.map((p) => {
return html`<li>
<a href="#/core/providers/${p.pk}">${p.name}</a>
</li>`;
})}
</ul>`,
html`${item.serviceConnectionObj?.name || t`No integration active`}`,
html`<ak-outpost-health .showVersion=${false} outpostId=${ifDefined(item.pk)}></ak-outpost-health>`,
html`<ak-outpost-health
.showVersion=${false}
outpostId=${ifDefined(item.pk)}
></ak-outpost-health>`,
html`<ak-forms-modal>
<span slot="submit">
${t`Update`}
</span>
<span slot="header">
${t`Update Outpost`}
</span>
<ak-outpost-form slot="form" .instancePk=${item.pk}>
</ak-outpost-form>
<button slot="trigger" class="pf-c-button pf-m-secondary">
${t`Edit`}
</button>
<span slot="submit"> ${t`Update`} </span>
<span slot="header"> ${t`Update Outpost`} </span>
<ak-outpost-form slot="form" .instancePk=${item.pk}> </ak-outpost-form>
<button slot="trigger" class="pf-c-button pf-m-secondary">${t`Edit`}</button>
</ak-forms-modal>`,
];
}
renderToolbar(): TemplateResult {
return html`
<ak-forms-modal>
<span slot="submit">
${t`Create`}
</span>
<span slot="header">
${t`Create Outpost`}
</span>
<ak-outpost-form slot="form">
</ak-outpost-form>
<button slot="trigger" class="pf-c-button pf-m-primary">
${t`Create`}
</button>
</ak-forms-modal>
${super.renderToolbar()}
<ak-forms-modal>
<span slot="submit"> ${t`Create`} </span>
<span slot="header"> ${t`Create Outpost`} </span>
<ak-outpost-form slot="form"> </ak-outpost-form>
<button slot="trigger" class="pf-c-button pf-m-primary">${t`Create`}</button>
</ak-forms-modal>
${super.renderToolbar()}
`;
}
}

View File

@ -11,7 +11,6 @@ import { ModelForm } from "../../elements/forms/ModelForm";
@customElement("ak-service-connection-docker-form")
export class ServiceConnectionDockerForm extends ModelForm<DockerServiceConnection, string> {
loadInstance(pk: string): Promise<DockerServiceConnection> {
return new OutpostsApi(DEFAULT_CONFIG).outpostsServiceConnectionsDockerRetrieve({
uuid: pk,
@ -30,70 +29,109 @@ export class ServiceConnectionDockerForm extends ModelForm<DockerServiceConnecti
if (this.instance) {
return new OutpostsApi(DEFAULT_CONFIG).outpostsServiceConnectionsDockerUpdate({
uuid: this.instance.pk || "",
dockerServiceConnectionRequest: data
dockerServiceConnectionRequest: data,
});
} else {
return new OutpostsApi(DEFAULT_CONFIG).outpostsServiceConnectionsDockerCreate({
dockerServiceConnectionRequest: data
dockerServiceConnectionRequest: data,
});
}
};
renderForm(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
<ak-form-element-horizontal
label=${t`Name`}
?required=${true}
name="name">
<input type="text" value="${ifDefined(this.instance?.name)}" class="pf-c-form-control" required>
<ak-form-element-horizontal label=${t`Name`} ?required=${true} name="name">
<input
type="text"
value="${ifDefined(this.instance?.name)}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal name="local">
<div class="pf-c-check">
<input type="checkbox" class="pf-c-check__input" ?checked=${first(this.instance?.local, false)}>
<label class="pf-c-check__label">
${t`Local`}
</label>
<input
type="checkbox"
class="pf-c-check__input"
?checked=${first(this.instance?.local, false)}
/>
<label class="pf-c-check__label"> ${t`Local`} </label>
</div>
<p class="pf-c-form__helper-text">${t`If enabled, use the local connection. Required Docker socket/Kubernetes Integration.`}</p>
<p class="pf-c-form__helper-text">
${t`If enabled, use the local connection. Required Docker socket/Kubernetes Integration.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Docker URL`}
?required=${true}
name="url">
<input type="text" value="${ifDefined(this.instance?.url)}" class="pf-c-form-control" required>
<p class="pf-c-form__helper-text">${t`Can be in the format of 'unix://' when connecting to a local docker daemon, or 'https://:2376' when connecting to a remote system.`}</p>
<ak-form-element-horizontal label=${t`Docker URL`} ?required=${true} name="url">
<input
type="text"
value="${ifDefined(this.instance?.url)}"
class="pf-c-form-control"
required
/>
<p class="pf-c-form__helper-text">
${t`Can be in the format of 'unix://' when connecting to a local docker daemon, or 'https://:2376' when connecting to a remote system.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`TLS Verification Certificate`}
name="tlsVerification">
name="tlsVerification"
>
<select class="pf-c-form-control">
<option value="" ?selected=${this.instance?.tlsVerification === undefined}>---------</option>
${until(new CryptoApi(DEFAULT_CONFIG).cryptoCertificatekeypairsList({
ordering: "pk"
}).then(certs => {
return certs.results.map(cert => {
return html`<option value=${ifDefined(cert.pk)} ?selected=${this.instance?.tlsVerification === cert.pk}>${cert.name}</option>`;
});
}), html`<option>${t`Loading...`}</option>`)}
<option value="" ?selected=${this.instance?.tlsVerification === undefined}>
---------
</option>
${until(
new CryptoApi(DEFAULT_CONFIG)
.cryptoCertificatekeypairsList({
ordering: "pk",
})
.then((certs) => {
return certs.results.map((cert) => {
return html`<option
value=${ifDefined(cert.pk)}
?selected=${this.instance?.tlsVerification === cert.pk}
>
${cert.name}
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
<p class="pf-c-form__helper-text">${t`CA which the endpoint's Certificate is verified against. Can be left empty for no validation.`}</p>
<p class="pf-c-form__helper-text">
${t`CA which the endpoint's Certificate is verified against. Can be left empty for no validation.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`TLS Authentication Certificate`}
name="tlsAuthentication">
name="tlsAuthentication"
>
<select class="pf-c-form-control">
<option value="" ?selected=${this.instance?.tlsAuthentication === undefined}>---------</option>
${until(new CryptoApi(DEFAULT_CONFIG).cryptoCertificatekeypairsList({
ordering: "pk"
}).then(certs => {
return certs.results.map(cert => {
return html`<option value=${ifDefined(cert.pk)} ?selected=${this.instance?.tlsAuthentication === cert.pk}>${cert.name}</option>`;
});
}), html`<option>${t`Loading...`}</option>`)}
<option value="" ?selected=${this.instance?.tlsAuthentication === undefined}>
---------
</option>
${until(
new CryptoApi(DEFAULT_CONFIG)
.cryptoCertificatekeypairsList({
ordering: "pk",
})
.then((certs) => {
return certs.results.map((cert) => {
return html`<option
value=${ifDefined(cert.pk)}
?selected=${this.instance?.tlsAuthentication === cert.pk}
>
${cert.name}
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
<p class="pf-c-form__helper-text">${t`Certificate/Key used for authentication. Can be left empty for no authentication.`}</p>
<p class="pf-c-form__helper-text">
${t`Certificate/Key used for authentication. Can be left empty for no authentication.`}
</p>
</ak-form-element-horizontal>
</form>`;
}
}

View File

@ -11,8 +11,10 @@ import { first } from "../../utils";
import { ModelForm } from "../../elements/forms/ModelForm";
@customElement("ak-service-connection-kubernetes-form")
export class ServiceConnectionKubernetesForm extends ModelForm<KubernetesServiceConnection, string> {
export class ServiceConnectionKubernetesForm extends ModelForm<
KubernetesServiceConnection,
string
> {
loadInstance(pk: string): Promise<KubernetesServiceConnection> {
return new OutpostsApi(DEFAULT_CONFIG).outpostsServiceConnectionsKubernetesRetrieve({
uuid: pk,
@ -31,40 +33,48 @@ export class ServiceConnectionKubernetesForm extends ModelForm<KubernetesService
if (this.instance) {
return new OutpostsApi(DEFAULT_CONFIG).outpostsServiceConnectionsKubernetesUpdate({
uuid: this.instance.pk || "",
kubernetesServiceConnectionRequest: data
kubernetesServiceConnectionRequest: data,
});
} else {
return new OutpostsApi(DEFAULT_CONFIG).outpostsServiceConnectionsKubernetesCreate({
kubernetesServiceConnectionRequest: data
kubernetesServiceConnectionRequest: data,
});
}
};
renderForm(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
<ak-form-element-horizontal
label=${t`Name`}
?required=${true}
name="name">
<input type="text" value="${ifDefined(this.instance?.name)}" class="pf-c-form-control" required>
<ak-form-element-horizontal label=${t`Name`} ?required=${true} name="name">
<input
type="text"
value="${ifDefined(this.instance?.name)}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal name="local">
<div class="pf-c-check">
<input type="checkbox" class="pf-c-check__input" ?checked=${first(this.instance?.local, false)}>
<label class="pf-c-check__label">
${t`Local`}
</label>
<input
type="checkbox"
class="pf-c-check__input"
?checked=${first(this.instance?.local, false)}
/>
<label class="pf-c-check__label"> ${t`Local`} </label>
</div>
<p class="pf-c-form__helper-text">${t`If enabled, use the local connection. Required Docker socket/Kubernetes Integration.`}</p>
<p class="pf-c-form__helper-text">
${t`If enabled, use the local connection. Required Docker socket/Kubernetes Integration.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Kubeconfig`}
name="kubeconfig">
<ak-codemirror mode="yaml" value="${YAML.stringify(first(this.instance?.kubeconfig, {}))}">
<ak-form-element-horizontal label=${t`Kubeconfig`} name="kubeconfig">
<ak-codemirror
mode="yaml"
value="${YAML.stringify(first(this.instance?.kubeconfig, {}))}"
>
</ak-codemirror>
<p class="pf-c-form__helper-text">${t`Set custom attributes using YAML or JSON.`}</p>
<p class="pf-c-form__helper-text">
${t`Set custom attributes using YAML or JSON.`}
</p>
</ak-form-element-horizontal>
</form>`;
}
}

View File

@ -63,86 +63,90 @@ export class OutpostServiceConnectionListPage extends TablePage<ServiceConnectio
html`${item.verboseName}`,
html`${item.local ? t`Yes` : t`No`}`,
html`${until(
new OutpostsApi(DEFAULT_CONFIG).outpostsServiceConnectionsAllStateRetrieve({
uuid: item.pk || ""
}).then((state) => {
if (state.healthy) {
return html`<ak-label color=${PFColor.Green} text=${ifDefined(state.version)}></ak-label>`;
}
return html`<ak-label color=${PFColor.Red} text=${t`Unhealthy`}></ak-label>`;
}), html`<ak-spinner></ak-spinner>`)}`,
html`
<ak-forms-modal>
<span slot="submit">
${t`Update`}
</span>
<span slot="header">
${t`Update ${item.verboseName}`}
</span>
<ak-proxy-form
slot="form"
.args=${{
"instancePk": item.pk
new OutpostsApi(DEFAULT_CONFIG)
.outpostsServiceConnectionsAllStateRetrieve({
uuid: item.pk || "",
})
.then((state) => {
if (state.healthy) {
return html`<ak-label
color=${PFColor.Green}
text=${ifDefined(state.version)}
></ak-label>`;
}
return html`<ak-label
color=${PFColor.Red}
text=${t`Unhealthy`}
></ak-label>`;
}),
html`<ak-spinner></ak-spinner>`,
)}`,
html` <ak-forms-modal>
<span slot="submit"> ${t`Update`} </span>
<span slot="header"> ${t`Update ${item.verboseName}`} </span>
<ak-proxy-form
slot="form"
.args=${{
instancePk: item.pk,
}}
type=${ifDefined(item.component)}
>
</ak-proxy-form>
<button slot="trigger" class="pf-c-button pf-m-secondary">${t`Edit`}</button>
</ak-forms-modal>
<ak-forms-delete
.obj=${item}
objectLabel=${t`Outpost Service-connection`}
.usedBy=${() => {
return new OutpostsApi(
DEFAULT_CONFIG,
).outpostsServiceConnectionsAllUsedByList({
uuid: item.pk,
});
}}
type=${ifDefined(item.component)}>
</ak-proxy-form>
<button slot="trigger" class="pf-c-button pf-m-secondary">
${t`Edit`}
</button>
</ak-forms-modal>
<ak-forms-delete
.obj=${item}
objectLabel=${t`Outpost Service-connection`}
.usedBy=${() => {
return new OutpostsApi(DEFAULT_CONFIG).outpostsServiceConnectionsAllUsedByList({
uuid: item.pk
});
}}
.delete=${() => {
return new OutpostsApi(DEFAULT_CONFIG).outpostsServiceConnectionsAllDestroy({
uuid: item.pk
});
}}>
<button slot="trigger" class="pf-c-button pf-m-danger">
${t`Delete`}
</button>
</ak-forms-delete>`,
.delete=${() => {
return new OutpostsApi(DEFAULT_CONFIG).outpostsServiceConnectionsAllDestroy(
{
uuid: item.pk,
},
);
}}
>
<button slot="trigger" class="pf-c-button pf-m-danger">${t`Delete`}</button>
</ak-forms-delete>`,
];
}
renderToolbar(): TemplateResult {
return html`
<ak-dropdown class="pf-c-dropdown">
<button class="pf-m-primary pf-c-dropdown__toggle" type="button">
<span class="pf-c-dropdown__toggle-text">${t`Create`}</span>
<i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i>
</button>
<ul class="pf-c-dropdown__menu" hidden>
${until(new OutpostsApi(DEFAULT_CONFIG).outpostsServiceConnectionsAllTypesList().then((types) => {
return types.map((type) => {
return html`<li>
<ak-forms-modal>
<span slot="submit">
${t`Create`}
</span>
<span slot="header">
${t`Create ${type.name}`}
</span>
<ak-proxy-form
slot="form"
type=${type.component}>
</ak-proxy-form>
<button slot="trigger" class="pf-c-dropdown__menu-item">
${type.name}<br>
<small>${type.description}</small>
</button>
</ak-forms-modal>
</li>`;
});
}), html`<ak-spinner></ak-spinner>`)}
</ul>
</ak-dropdown>
${super.renderToolbar()}`;
return html` <ak-dropdown class="pf-c-dropdown">
<button class="pf-m-primary pf-c-dropdown__toggle" type="button">
<span class="pf-c-dropdown__toggle-text">${t`Create`}</span>
<i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i>
</button>
<ul class="pf-c-dropdown__menu" hidden>
${until(
new OutpostsApi(DEFAULT_CONFIG)
.outpostsServiceConnectionsAllTypesList()
.then((types) => {
return types.map((type) => {
return html`<li>
<ak-forms-modal>
<span slot="submit"> ${t`Create`} </span>
<span slot="header"> ${t`Create ${type.name}`} </span>
<ak-proxy-form slot="form" type=${type.component}>
</ak-proxy-form>
<button slot="trigger" class="pf-c-dropdown__menu-item">
${type.name}<br />
<small>${type.description}</small>
</button>
</ak-forms-modal>
</li>`;
});
}),
html`<ak-spinner></ak-spinner>`,
)}
</ul>
</ak-dropdown>
${super.renderToolbar()}`;
}
}

View File

@ -25,7 +25,7 @@ export class BoundPoliciesList extends Table<PolicyBinding> {
@property()
target?: string;
@property({type: Boolean})
@property({ type: Boolean })
policyOnly = false;
apiEndpoint(page: number): Promise<AKResponse<PolicyBinding>> {
@ -61,52 +61,32 @@ export class BoundPoliciesList extends Table<PolicyBinding> {
getObjectEditButton(item: PolicyBinding): TemplateResult {
if (item.policy) {
return html`
<ak-forms-modal>
<span slot="submit">
${t`Update`}
</span>
<span slot="header">
${t`Update ${item.policyObj?.name}`}
</span>
return html` <ak-forms-modal>
<span slot="submit"> ${t`Update`} </span>
<span slot="header"> ${t`Update ${item.policyObj?.name}`} </span>
<ak-proxy-form
slot="form"
.args=${{
"instancePk": item.policyObj?.pk
instancePk: item.policyObj?.pk,
}}
type=${ifDefined(item.policyObj?.component)}>
type=${ifDefined(item.policyObj?.component)}
>
</ak-proxy-form>
<button slot="trigger" class="pf-c-button pf-m-secondary">
${t`Edit Policy`}
</button>
<button slot="trigger" class="pf-c-button pf-m-secondary">${t`Edit Policy`}</button>
</ak-forms-modal>`;
} else if (item.group) {
return html`<ak-forms-modal>
<span slot="submit">
${t`Update`}
</span>
<span slot="header">
${t`Update Group`}
</span>
<ak-group-form slot="form" .instancePk=${item.groupObj?.pk}>
</ak-group-form>
<button slot="trigger" class="pf-c-button pf-m-secondary">
${t`Edit Group`}
</button>
<span slot="submit"> ${t`Update`} </span>
<span slot="header"> ${t`Update Group`} </span>
<ak-group-form slot="form" .instancePk=${item.groupObj?.pk}> </ak-group-form>
<button slot="trigger" class="pf-c-button pf-m-secondary">${t`Edit Group`}</button>
</ak-forms-modal>`;
} else if (item.user) {
return html`<ak-forms-modal>
<span slot="submit">
${t`Update`}
</span>
<span slot="header">
${t`Update User`}
</span>
<ak-user-form slot="form" .instancePk=${item.userObj?.pk}>
</ak-user-form>
<button slot="trigger" class="pf-c-button pf-m-secondary">
${t`Edit User`}
</button>
<span slot="submit"> ${t`Update`} </span>
<span slot="header"> ${t`Update User`} </span>
<ak-user-form slot="form" .instancePk=${item.userObj?.pk}> </ak-user-form>
<button slot="trigger" class="pf-c-button pf-m-secondary">${t`Edit User`}</button>
</ak-forms-modal>`;
} else {
return html``;
@ -119,55 +99,57 @@ export class BoundPoliciesList extends Table<PolicyBinding> {
html`${item.enabled ? t`Yes` : t`No`}`,
html`${item.order}`,
html`${item.timeout}`,
html`
${this.getObjectEditButton(item)}
<ak-forms-modal size=${PFSize.Medium}>
<span slot="submit">
${t`Update`}
</span>
<span slot="header">
${t`Update Binding`}
</span>
<ak-policy-binding-form slot="form" .instancePk=${item.pk} targetPk=${ifDefined(this.target)} ?policyOnly=${this.policyOnly}>
</ak-policy-binding-form>
<button slot="trigger" class="pf-c-button pf-m-secondary">
${t`Edit Binding`}
</button>
</ak-forms-modal>
<ak-forms-delete
.obj=${item}
objectLabel=${t`Policy binding`}
.usedBy=${() => {
return new PoliciesApi(DEFAULT_CONFIG).policiesBindingsUsedByList({
policyBindingUuid: item.pk
});
}}
.delete=${() => {
return new PoliciesApi(DEFAULT_CONFIG).policiesBindingsDestroy({
policyBindingUuid: item.pk,
});
}}>
<button slot="trigger" class="pf-c-button pf-m-danger">
${t`Delete Binding`}
</button>
</ak-forms-delete>`,
html` ${this.getObjectEditButton(item)}
<ak-forms-modal size=${PFSize.Medium}>
<span slot="submit"> ${t`Update`} </span>
<span slot="header"> ${t`Update Binding`} </span>
<ak-policy-binding-form
slot="form"
.instancePk=${item.pk}
targetPk=${ifDefined(this.target)}
?policyOnly=${this.policyOnly}
>
</ak-policy-binding-form>
<button slot="trigger" class="pf-c-button pf-m-secondary">
${t`Edit Binding`}
</button>
</ak-forms-modal>
<ak-forms-delete
.obj=${item}
objectLabel=${t`Policy binding`}
.usedBy=${() => {
return new PoliciesApi(DEFAULT_CONFIG).policiesBindingsUsedByList({
policyBindingUuid: item.pk,
});
}}
.delete=${() => {
return new PoliciesApi(DEFAULT_CONFIG).policiesBindingsDestroy({
policyBindingUuid: item.pk,
});
}}
>
<button slot="trigger" class="pf-c-button pf-m-danger">
${t`Delete Binding`}
</button>
</ak-forms-delete>`,
];
}
renderEmpty(): TemplateResult {
return super.renderEmpty(html`<ak-empty-state header=${t`No Policies bound.`} icon="pf-icon-module">
<div slot="body">
${t`No policies are currently bound to this object.`}
</div>
return super.renderEmpty(html`<ak-empty-state
header=${t`No Policies bound.`}
icon="pf-icon-module"
>
<div slot="body">${t`No policies are currently bound to this object.`}</div>
<div slot="primary">
<ak-forms-modal size=${PFSize.Medium}>
<span slot="submit">
${t`Create`}
</span>
<span slot="header">
${t`Create Binding`}
</span>
<ak-policy-binding-form slot="form" targetPk=${ifDefined(this.target)} ?policyOnly=${this.policyOnly}>
<span slot="submit"> ${t`Create`} </span>
<span slot="header"> ${t`Create Binding`} </span>
<ak-policy-binding-form
slot="form"
targetPk=${ifDefined(this.target)}
?policyOnly=${this.policyOnly}
>
</ak-policy-binding-form>
<button slot="trigger" class="pf-c-button pf-m-primary">
${t`Create Binding`}
@ -178,50 +160,46 @@ export class BoundPoliciesList extends Table<PolicyBinding> {
}
renderToolbar(): TemplateResult {
return html`
<ak-forms-modal size=${PFSize.Medium}>
<span slot="submit">
${t`Create`}
</span>
<span slot="header">
${t`Create Binding`}
</span>
<ak-policy-binding-form slot="form" targetPk=${ifDefined(this.target)} ?policyOnly=${this.policyOnly}>
</ak-policy-binding-form>
<button slot="trigger" class="pf-c-button pf-m-primary">
${t`Create Binding`}
</button>
</ak-forms-modal>
<ak-dropdown class="pf-c-dropdown">
<button class="pf-m-secondary pf-c-button pf-c-dropdown__toggle" type="button">
<span class="pf-c-dropdown__toggle-text">${t`Create Policy`}</span>
<i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i>
</button>
<ul class="pf-c-dropdown__menu" hidden>
${until(new PoliciesApi(DEFAULT_CONFIG).policiesAllTypesList().then((types) => {
return types.map((type) => {
return html`<li>
<ak-forms-modal>
<span slot="submit">
${t`Create`}
</span>
<span slot="header">
${t`Create ${type.name}`}
</span>
<ak-proxy-form
slot="form"
type=${type.component}>
</ak-proxy-form>
<button slot="trigger" class="pf-c-dropdown__menu-item">
${type.name}<br>
<small>${type.description}</small>
</button>
</ak-forms-modal>
</li>`;
});
}), html`<ak-spinner></ak-spinner>`)}
</ul>
</ak-dropdown>
${super.renderToolbar()}`;
return html` <ak-forms-modal size=${PFSize.Medium}>
<span slot="submit"> ${t`Create`} </span>
<span slot="header"> ${t`Create Binding`} </span>
<ak-policy-binding-form
slot="form"
targetPk=${ifDefined(this.target)}
?policyOnly=${this.policyOnly}
>
</ak-policy-binding-form>
<button slot="trigger" class="pf-c-button pf-m-primary">
${t`Create Binding`}
</button>
</ak-forms-modal>
<ak-dropdown class="pf-c-dropdown">
<button class="pf-m-secondary pf-c-button pf-c-dropdown__toggle" type="button">
<span class="pf-c-dropdown__toggle-text">${t`Create Policy`}</span>
<i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i>
</button>
<ul class="pf-c-dropdown__menu" hidden>
${until(
new PoliciesApi(DEFAULT_CONFIG).policiesAllTypesList().then((types) => {
return types.map((type) => {
return html`<li>
<ak-forms-modal>
<span slot="submit"> ${t`Create`} </span>
<span slot="header"> ${t`Create ${type.name}`} </span>
<ak-proxy-form slot="form" type=${type.component}>
</ak-proxy-form>
<button slot="trigger" class="pf-c-dropdown__menu-item">
${type.name}<br />
<small>${type.description}</small>
</button>
</ak-forms-modal>
</li>`;
});
}),
html`<ak-spinner></ak-spinner>`,
)}
</ul>
</ak-dropdown>
${super.renderToolbar()}`;
}
}

View File

@ -12,36 +12,39 @@ import PFContent from "@patternfly/patternfly/components/Content/content.css";
import { ModelForm } from "../../elements/forms/ModelForm";
enum target {
policy, group, user
policy,
group,
user,
}
@customElement("ak-policy-binding-form")
export class PolicyBindingForm extends ModelForm<PolicyBinding, string> {
loadInstance(pk: string): Promise<PolicyBinding> {
return new PoliciesApi(DEFAULT_CONFIG).policiesBindingsRetrieve({
policyBindingUuid: pk
}).then(binding => {
if (binding?.policyObj) {
this.policyGroupUser = target.policy;
}
if (binding?.groupObj) {
this.policyGroupUser = target.group;
}
if (binding?.userObj) {
this.policyGroupUser = target.user;
}
return binding;
});
return new PoliciesApi(DEFAULT_CONFIG)
.policiesBindingsRetrieve({
policyBindingUuid: pk,
})
.then((binding) => {
if (binding?.policyObj) {
this.policyGroupUser = target.policy;
}
if (binding?.groupObj) {
this.policyGroupUser = target.group;
}
if (binding?.userObj) {
this.policyGroupUser = target.user;
}
return binding;
});
}
@property()
targetPk?: string;
@property({type: Number})
@property({ type: Number })
policyGroupUser: target = target.policy;
@property({type: Boolean})
@property({ type: Boolean })
policyOnly = false;
getSuccessMessage(): string {
@ -53,33 +56,39 @@ export class PolicyBindingForm extends ModelForm<PolicyBinding, string> {
}
static get styles(): CSSResult[] {
return super.styles.concat(PFToggleGroup, PFContent, css`
.pf-c-toggle-group {
justify-content: center;
}
`);
return super.styles.concat(
PFToggleGroup,
PFContent,
css`
.pf-c-toggle-group {
justify-content: center;
}
`,
);
}
send = (data: PolicyBinding): Promise<PolicyBinding> => {
if (this.instance) {
return new PoliciesApi(DEFAULT_CONFIG).policiesBindingsUpdate({
policyBindingUuid: this.instance.pk || "",
policyBindingRequest: data
policyBindingRequest: data,
});
} else {
return new PoliciesApi(DEFAULT_CONFIG).policiesBindingsCreate({
policyBindingRequest: data
policyBindingRequest: data,
});
}
};
groupPolicies(policies: Policy[]): TemplateResult {
return html`
${groupBy<Policy>(policies, (p => p.verboseName || "")).map(([group, policies]) => {
${groupBy<Policy>(policies, (p) => p.verboseName || "").map(([group, policies]) => {
return html`<optgroup label=${group}>
${policies.map(p => {
const selected = (this.instance?.policy === p.pk);
return html`<option ?selected=${selected} value=${ifDefined(p.pk)}>${p.name}</option>`;
${policies.map((p) => {
const selected = this.instance?.policy === p.pk;
return html`<option ?selected=${selected} value=${ifDefined(p.pk)}>
${p.name}
</option>`;
})}
</optgroup>`;
})}
@ -90,48 +99,66 @@ export class PolicyBindingForm extends ModelForm<PolicyBinding, string> {
if (this.instance) {
return Promise.resolve(this.instance.order);
}
return new PoliciesApi(DEFAULT_CONFIG).policiesBindingsList({
target: this.targetPk || "",
}).then(bindings => {
const orders = bindings.results.map(binding => binding.order);
if (orders.length < 1) {
return 0;
}
return Math.max(...orders) + 1;
});
return new PoliciesApi(DEFAULT_CONFIG)
.policiesBindingsList({
target: this.targetPk || "",
})
.then((bindings) => {
const orders = bindings.results.map((binding) => binding.order);
if (orders.length < 1) {
return 0;
}
return Math.max(...orders) + 1;
});
}
renderModeSelector(): TemplateResult {
if (this.policyOnly) {
this.policyGroupUser = target.policy;
return html`
<div class="pf-c-toggle-group__item">
<button class="pf-c-toggle-group__button pf-m-selected" type="button">
<span class="pf-c-toggle-group__text">${t`Policy`}</span>
</button>
</div>`;
return html` <div class="pf-c-toggle-group__item">
<button class="pf-c-toggle-group__button pf-m-selected" type="button">
<span class="pf-c-toggle-group__text">${t`Policy`}</span>
</button>
</div>`;
}
return html`
<div class="pf-c-toggle-group__item">
<button class="pf-c-toggle-group__button ${this.policyGroupUser === target.policy ? "pf-m-selected": ""}" type="button" @click=${() => {
this.policyGroupUser = target.policy;
}}>
return html` <div class="pf-c-toggle-group__item">
<button
class="pf-c-toggle-group__button ${this.policyGroupUser === target.policy
? "pf-m-selected"
: ""}"
type="button"
@click=${() => {
this.policyGroupUser = target.policy;
}}
>
<span class="pf-c-toggle-group__text">${t`Policy`}</span>
</button>
</div>
<div class="pf-c-divider pf-m-vertical" role="separator"></div>
<div class="pf-c-toggle-group__item">
<button class="pf-c-toggle-group__button ${this.policyGroupUser === target.group ? "pf-m-selected" : ""}" type="button" @click=${() => {
this.policyGroupUser = target.group;
}}>
<button
class="pf-c-toggle-group__button ${this.policyGroupUser === target.group
? "pf-m-selected"
: ""}"
type="button"
@click=${() => {
this.policyGroupUser = target.group;
}}
>
<span class="pf-c-toggle-group__text">${t`Group`}</span>
</button>
</div>
<div class="pf-c-divider pf-m-vertical" role="separator"></div>
<div class="pf-c-toggle-group__item">
<button class="pf-c-toggle-group__button ${this.policyGroupUser === target.user ? "pf-m-selected" : ""}" type="button" @click=${() => {
this.policyGroupUser = target.user;
}}>
<button
class="pf-c-toggle-group__button ${this.policyGroupUser === target.user
? "pf-m-selected"
: ""}"
type="button"
@click=${() => {
this.policyGroupUser = target.user;
}}
>
<span class="pf-c-toggle-group__text">${t`User`}</span>
</button>
</div>`;
@ -141,89 +168,133 @@ export class PolicyBindingForm extends ModelForm<PolicyBinding, string> {
return html`<form class="pf-c-form pf-m-horizontal">
<div class="pf-c-card pf-m-selectable pf-m-selected">
<div class="pf-c-card__body">
<div class="pf-c-toggle-group">
${this.renderModeSelector()}
</div>
<div class="pf-c-toggle-group">${this.renderModeSelector()}</div>
</div>
<div class="pf-c-card__footer">
<ak-form-element-horizontal
label=${t`Policy`}
name="policy"
?hidden=${this.policyGroupUser !== target.policy}>
?hidden=${this.policyGroupUser !== target.policy}
>
<select class="pf-c-form-control">
<option value="" ?selected=${this.instance?.policy === undefined}>---------</option>
${until(new PoliciesApi(DEFAULT_CONFIG).policiesAllList({
ordering: "pk"
}).then(policies => {
return this.groupPolicies(policies.results);
}), html`<option>${t`Loading...`}</option>`)}
<option value="" ?selected=${this.instance?.policy === undefined}>
---------
</option>
${until(
new PoliciesApi(DEFAULT_CONFIG)
.policiesAllList({
ordering: "pk",
})
.then((policies) => {
return this.groupPolicies(policies.results);
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Group`}
name="group"
?hidden=${this.policyGroupUser !== target.group}>
?hidden=${this.policyGroupUser !== target.group}
>
<select class="pf-c-form-control">
<option value="" ?selected=${this.instance?.group === undefined}>---------</option>
${until(new CoreApi(DEFAULT_CONFIG).coreGroupsList({
ordering: "pk"
}).then(groups => {
return groups.results.map(group => {
return html`<option value=${ifDefined(group.pk)} ?selected=${group.pk === this.instance?.group}>${group.name}</option>`;
});
}), html`<option>${t`Loading...`}</option>`)}
<option value="" ?selected=${this.instance?.group === undefined}>
---------
</option>
${until(
new CoreApi(DEFAULT_CONFIG)
.coreGroupsList({
ordering: "pk",
})
.then((groups) => {
return groups.results.map((group) => {
return html`<option
value=${ifDefined(group.pk)}
?selected=${group.pk === this.instance?.group}
>
${group.name}
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`User`}
name="user"
?hidden=${this.policyGroupUser !== target.user}>
?hidden=${this.policyGroupUser !== target.user}
>
<select class="pf-c-form-control">
<option value="" ?selected=${this.instance?.user === undefined}>---------</option>
${until(new CoreApi(DEFAULT_CONFIG).coreUsersList({
ordering: "pk"
}).then(users => {
return users.results.map(user => {
return html`<option value=${ifDefined(user.pk)} ?selected=${user.pk === this.instance?.user}>${user.name}</option>`;
});
}), html`<option>${t`Loading...`}</option>`)}
<option value="" ?selected=${this.instance?.user === undefined}>
---------
</option>
${until(
new CoreApi(DEFAULT_CONFIG)
.coreUsersList({
ordering: "pk",
})
.then((users) => {
return users.results.map((user) => {
return html`<option
value=${ifDefined(user.pk)}
?selected=${user.pk === this.instance?.user}
>
${user.name}
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
</ak-form-element-horizontal>
</div>
</div>
<input required name="target" type="hidden" value=${ifDefined(this.instance?.target || this.targetPk)}>
<input
required
name="target"
type="hidden"
value=${ifDefined(this.instance?.target || this.targetPk)}
/>
<ak-form-element-horizontal name="enabled">
<div class="pf-c-check">
<input type="checkbox" class="pf-c-check__input" ?checked=${first(this.instance?.enabled, true)}>
<label class="pf-c-check__label">
${t`Enabled`}
</label>
<input
type="checkbox"
class="pf-c-check__input"
?checked=${first(this.instance?.enabled, true)}
/>
<label class="pf-c-check__label"> ${t`Enabled`} </label>
</div>
</ak-form-element-horizontal>
<ak-form-element-horizontal name="negate">
<div class="pf-c-check">
<input type="checkbox" class="pf-c-check__input" ?checked=${first(this.instance?.negate, false)}>
<label class="pf-c-check__label">
${t`Negate result`}
</label>
<input
type="checkbox"
class="pf-c-check__input"
?checked=${first(this.instance?.negate, false)}
/>
<label class="pf-c-check__label"> ${t`Negate result`} </label>
</div>
<p class="pf-c-form__helper-text">
${t`Negates the outcome of the binding. Messages are unaffected.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Order`}
?required=${true}
name="order">
<input type="number" value="${until(this.getOrder())}" class="pf-c-form-control" required>
<ak-form-element-horizontal label=${t`Order`} ?required=${true} name="order">
<input
type="number"
value="${until(this.getOrder())}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Timeout`}
?required=${true}
name="timeout">
<input type="number" value="${first(this.instance?.timeout, 30)}" class="pf-c-form-control" required>
<ak-form-element-horizontal label=${t`Timeout`} ?required=${true} name="timeout">
<input
type="number"
value="${first(this.instance?.timeout, 30)}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
</form>`;
}
}

View File

@ -52,130 +52,104 @@ export class PolicyListPage extends TablePage<Policy> {
}
columns(): TableColumn[] {
return [
new TableColumn(t`Name`, "name"),
new TableColumn(t`Type`),
new TableColumn(""),
];
return [new TableColumn(t`Name`, "name"), new TableColumn(t`Type`), new TableColumn("")];
}
row(item: Policy): TemplateResult[] {
return [
html`<div>
<div>${item.name}</div>
${(item.boundTo || 0) > 0 ?
html`<i class="pf-icon pf-icon-ok"></i>
<small>
${t`Assigned to ${item.boundTo} objects.`}
</small>`:
html`<i class="pf-icon pf-icon-warning-triangle"></i>
<small>${t`Warning: Policy is not assigned.`}</small>`}
${(item.boundTo || 0) > 0
? html`<i class="pf-icon pf-icon-ok"></i>
<small> ${t`Assigned to ${item.boundTo} objects.`} </small>`
: html`<i class="pf-icon pf-icon-warning-triangle"></i>
<small>${t`Warning: Policy is not assigned.`}</small>`}
</div>`,
html`${item.verboseName}`,
html`
<ak-forms-modal>
<span slot="submit">
${t`Update`}
</span>
<span slot="header">
${t`Update ${item.verboseName}`}
</span>
<ak-proxy-form
slot="form"
.args=${{
"instancePk": item.pk
html` <ak-forms-modal>
<span slot="submit"> ${t`Update`} </span>
<span slot="header"> ${t`Update ${item.verboseName}`} </span>
<ak-proxy-form
slot="form"
.args=${{
instancePk: item.pk,
}}
type=${ifDefined(item.component)}
>
</ak-proxy-form>
<button slot="trigger" class="pf-c-button pf-m-secondary">${t`Edit`}</button>
</ak-forms-modal>
<ak-forms-modal .closeAfterSuccessfulSubmit=${false}>
<span slot="submit"> ${t`Test`} </span>
<span slot="header"> ${t`Test Policy`} </span>
<ak-policy-test-form slot="form" .policy=${item}> </ak-policy-test-form>
<button slot="trigger" class="pf-c-button pf-m-secondary">${t`Test`}</button>
</ak-forms-modal>
<ak-forms-delete
.obj=${item}
objectLabel=${t`Policy`}
.usedBy=${() => {
return new PoliciesApi(DEFAULT_CONFIG).policiesAllUsedByList({
policyUuid: item.pk,
});
}}
type=${ifDefined(item.component)}>
</ak-proxy-form>
<button slot="trigger" class="pf-c-button pf-m-secondary">
${t`Edit`}
</button>
</ak-forms-modal>
<ak-forms-modal .closeAfterSuccessfulSubmit=${false}>
<span slot="submit">
${t`Test`}
</span>
<span slot="header">
${t`Test Policy`}
</span>
<ak-policy-test-form slot="form" .policy=${item}>
</ak-policy-test-form>
<button slot="trigger" class="pf-c-button pf-m-secondary">
${t`Test`}
</button>
</ak-forms-modal>
<ak-forms-delete
.obj=${item}
objectLabel=${t`Policy`}
.usedBy=${() => {
return new PoliciesApi(DEFAULT_CONFIG).policiesAllUsedByList({
policyUuid: item.pk
});
}}
.delete=${() => {
return new PoliciesApi(DEFAULT_CONFIG).policiesAllDestroy({
policyUuid: item.pk
});
}}>
<button slot="trigger" class="pf-c-button pf-m-danger">
${t`Delete`}
</button>
</ak-forms-delete>`,
.delete=${() => {
return new PoliciesApi(DEFAULT_CONFIG).policiesAllDestroy({
policyUuid: item.pk,
});
}}
>
<button slot="trigger" class="pf-c-button pf-m-danger">${t`Delete`}</button>
</ak-forms-delete>`,
];
}
renderToolbar(): TemplateResult {
return html`
<ak-dropdown class="pf-c-dropdown">
return html` <ak-dropdown class="pf-c-dropdown">
<button class="pf-m-primary pf-c-dropdown__toggle" type="button">
<span class="pf-c-dropdown__toggle-text">${t`Create`}</span>
<i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i>
</button>
<ul class="pf-c-dropdown__menu" hidden>
${until(new PoliciesApi(DEFAULT_CONFIG).policiesAllTypesList().then((types) => {
return types.map((type) => {
return html`<li>
<ak-forms-modal>
<span slot="submit">
${t`Create`}
</span>
<span slot="header">
${t`Create ${type.name}`}
</span>
<ak-proxy-form
slot="form"
type=${type.component}>
</ak-proxy-form>
<button slot="trigger" class="pf-c-dropdown__menu-item">
${type.name}<br>
<small>${type.description}</small>
</button>
</ak-forms-modal>
</li>`;
});
}), html`<ak-spinner></ak-spinner>`)}
</ul>
</ak-dropdown>
${super.renderToolbar()}
<ak-forms-confirm
successMessage=${t`Successfully cleared policy cache`}
errorMessage=${t`Failed to delete policy cache`}
action=${t`Clear cache`}
.onConfirm=${() => {
return new PoliciesApi(DEFAULT_CONFIG).policiesAllCacheClearCreate();
}}>
<span slot="header">
${t`Clear Policy cache`}
</span>
<p slot="body">
${t`Are you sure you want to clear the policy cache?
${until(
new PoliciesApi(DEFAULT_CONFIG).policiesAllTypesList().then((types) => {
return types.map((type) => {
return html`<li>
<ak-forms-modal>
<span slot="submit"> ${t`Create`} </span>
<span slot="header"> ${t`Create ${type.name}`} </span>
<ak-proxy-form slot="form" type=${type.component}>
</ak-proxy-form>
<button slot="trigger" class="pf-c-dropdown__menu-item">
${type.name}<br />
<small>${type.description}</small>
</button>
</ak-forms-modal>
</li>`;
});
}),
html`<ak-spinner></ak-spinner>`,
)}
</ul>
</ak-dropdown>
${super.renderToolbar()}
<ak-forms-confirm
successMessage=${t`Successfully cleared policy cache`}
errorMessage=${t`Failed to delete policy cache`}
action=${t`Clear cache`}
.onConfirm=${() => {
return new PoliciesApi(DEFAULT_CONFIG).policiesAllCacheClearCreate();
}}
>
<span slot="header"> ${t`Clear Policy cache`} </span>
<p slot="body">
${t`Are you sure you want to clear the policy cache?
This will cause all policies to be re-evaluated on their next usage.`}
</p>
<button slot="trigger" class="pf-c-button pf-m-secondary" type="button">
${t`Clear cache`}
</button>
<div slot="modal"></div>
</ak-forms-confirm>`;
</p>
<button slot="trigger" class="pf-c-button pf-m-secondary" type="button">
${t`Clear cache`}
</button>
<div slot="modal"></div>
</ak-forms-confirm>`;
}
}

View File

@ -12,14 +12,13 @@ import { first } from "../../utils";
@customElement("ak-policy-test-form")
export class PolicyTestForm extends Form<PolicyTestRequest> {
@property({attribute: false})
@property({ attribute: false })
policy?: Policy;
@property({ attribute: false})
@property({ attribute: false })
result?: PolicyTestResult;
@property({ attribute: false})
@property({ attribute: false })
request?: PolicyTestRequest;
getSuccessMessage(): string {
@ -28,32 +27,37 @@ export class PolicyTestForm extends Form<PolicyTestRequest> {
send = (data: PolicyTestRequest): Promise<PolicyTestResult> => {
this.request = data;
return new PoliciesApi(DEFAULT_CONFIG).policiesAllTestCreate({
policyUuid: this.policy?.pk || "",
policyTestRequest: data
}).then(result => this.result = result);
return new PoliciesApi(DEFAULT_CONFIG)
.policiesAllTestCreate({
policyUuid: this.policy?.pk || "",
policyTestRequest: data,
})
.then((result) => (this.result = result));
};
renderResult(): TemplateResult {
return html`
<ak-form-element-horizontal
label=${t`Passing`}>
return html` <ak-form-element-horizontal label=${t`Passing`}>
<div class="pf-c-form__group-label">
<div class="c-form__horizontal-group">
<span class="pf-c-form__label-text">${this.result?.passing ? t`Yes` : t`No`}</span>
<span class="pf-c-form__label-text"
>${this.result?.passing ? t`Yes` : t`No`}</span
>
</div>
</div>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Messages`}>
<ak-form-element-horizontal label=${t`Messages`}>
<div class="pf-c-form__group-label">
<div class="c-form__horizontal-group">
<ul>
${(this.result?.messages || []).length > 0 ?
this.result?.messages?.map(m => {
return html`<li><span class="pf-c-form__label-text">${m}</span></li>`;
}) :
html`<li><span class="pf-c-form__label-text">-</span></li>`}
${(this.result?.messages || []).length > 0
? this.result?.messages?.map((m) => {
return html`<li>
<span class="pf-c-form__label-text">${m}</span>
</li>`;
})
: html`<li>
<span class="pf-c-form__label-text">-</span>
</li>`}
</ul>
</div>
</div>
@ -62,29 +66,37 @@ export class PolicyTestForm extends Form<PolicyTestRequest> {
renderForm(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
<ak-form-element-horizontal
label=${t`User`}
?required=${true}
name="user">
<ak-form-element-horizontal label=${t`User`} ?required=${true} name="user">
<select class="pf-c-form-control">
${until(new CoreApi(DEFAULT_CONFIG).coreUsersList({
ordering: "username",
}).then(users => {
return users.results.map(user => {
return html`<option ?selected=${this.request?.user.toString() === user.pk.toString()} value=${user.pk}>${user.username}</option>`;
});
}), html`<option>${t`Loading...`}</option>`)}
${until(
new CoreApi(DEFAULT_CONFIG)
.coreUsersList({
ordering: "username",
})
.then((users) => {
return users.results.map((user) => {
return html`<option
?selected=${this.request?.user.toString() ===
user.pk.toString()}
value=${user.pk}
>
${user.username}
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Context`}
name="context">
<ak-codemirror mode="yaml" value=${YAML.stringify(first(this.request?.context, {}))}>>
<ak-form-element-horizontal label=${t`Context`} name="context">
<ak-codemirror mode="yaml" value=${YAML.stringify(first(this.request?.context, {}))}
>>
</ak-codemirror>
<p class="pf-c-form__helper-text">${t`Set custom attributes using YAML or JSON.`}</p>
<p class="pf-c-form__helper-text">
${t`Set custom attributes using YAML or JSON.`}
</p>
</ak-form-element-horizontal>
${this.result ? this.renderResult(): html``}
${this.result ? this.renderResult() : html``}
</form>`;
}
}

View File

@ -11,7 +11,6 @@ import { ModelForm } from "../../../elements/forms/ModelForm";
@customElement("ak-policy-dummy-form")
export class DummyPolicyForm extends ModelForm<DummyPolicy, string> {
loadInstance(pk: string): Promise<DummyPolicy> {
return new PoliciesApi(DEFAULT_CONFIG).policiesDummyRetrieve({
policyUuid: pk,
@ -30,11 +29,11 @@ export class DummyPolicyForm extends ModelForm<DummyPolicy, string> {
if (this.instance) {
return new PoliciesApi(DEFAULT_CONFIG).policiesDummyUpdate({
policyUuid: this.instance.pk || "",
dummyPolicyRequest: data
dummyPolicyRequest: data,
});
} else {
return new PoliciesApi(DEFAULT_CONFIG).policiesDummyCreate({
dummyPolicyRequest: data
dummyPolicyRequest: data,
});
}
};
@ -44,52 +43,69 @@ export class DummyPolicyForm extends ModelForm<DummyPolicy, string> {
<div class="form-help-text">
${t`A policy used for testing. Always returns the same result as specified below after waiting a random duration.`}
</div>
<ak-form-element-horizontal
label=${t`Name`}
?required=${true}
name="name">
<input type="text" value="${ifDefined(this.instance?.name || "")}" class="pf-c-form-control" required>
<ak-form-element-horizontal label=${t`Name`} ?required=${true} name="name">
<input
type="text"
value="${ifDefined(this.instance?.name || "")}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal name="executionLogging">
<div class="pf-c-check">
<input type="checkbox" class="pf-c-check__input" ?checked=${first(this.instance?.executionLogging, false)}>
<label class="pf-c-check__label">
${t`Execution logging`}
</label>
<input
type="checkbox"
class="pf-c-check__input"
?checked=${first(this.instance?.executionLogging, false)}
/>
<label class="pf-c-check__label"> ${t`Execution logging`} </label>
</div>
<p class="pf-c-form__helper-text">
${t`When this option is enabled, all executions of this policy will be logged. By default, only execution errors are logged.`}
</p>
</ak-form-element-horizontal>
<ak-form-group .expanded=${true}>
<span slot="header">
${t`Policy-specific settings`}
</span>
<span slot="header"> ${t`Policy-specific settings`} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal name="result">
<div class="pf-c-check">
<input type="checkbox" class="pf-c-check__input" ?checked=${first(this.instance?.result, false)}>
<label class="pf-c-check__label">
${t`Pass policy?`}
</label>
<input
type="checkbox"
class="pf-c-check__input"
?checked=${first(this.instance?.result, false)}
/>
<label class="pf-c-check__label"> ${t`Pass policy?`} </label>
</div>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Wait (min)`}
?required=${true}
name="waitMin">
<input type="number" value="${first(this.instance?.waitMin, 1)}" class="pf-c-form-control" required>
<p class="pf-c-form__helper-text">${t`The policy takes a random time to execute. This controls the minimum time it will take.`}</p>
name="waitMin"
>
<input
type="number"
value="${first(this.instance?.waitMin, 1)}"
class="pf-c-form-control"
required
/>
<p class="pf-c-form__helper-text">
${t`The policy takes a random time to execute. This controls the minimum time it will take.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Wait (max)`}
?required=${true}
name="waitMax">
<input type="number" value="${first(this.instance?.waitMax, 5)}" class="pf-c-form-control" required>
name="waitMax"
>
<input
type="number"
value="${first(this.instance?.waitMax, 5)}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
</div>
</ak-form-group>
</form>`;
}
}

View File

@ -12,7 +12,6 @@ import { ModelForm } from "../../../elements/forms/ModelForm";
@customElement("ak-policy-event-matcher-form")
export class EventMatcherPolicyForm extends ModelForm<EventMatcherPolicy, string> {
loadInstance(pk: string): Promise<EventMatcherPolicy> {
return new PoliciesApi(DEFAULT_CONFIG).policiesEventMatcherRetrieve({
policyUuid: pk,
@ -31,11 +30,11 @@ export class EventMatcherPolicyForm extends ModelForm<EventMatcherPolicy, string
if (this.instance) {
return new PoliciesApi(DEFAULT_CONFIG).policiesEventMatcherUpdate({
policyUuid: this.instance.pk || "",
eventMatcherPolicyRequest: data
eventMatcherPolicyRequest: data,
});
} else {
return new PoliciesApi(DEFAULT_CONFIG).policiesEventMatcherCreate({
eventMatcherPolicyRequest: data
eventMatcherPolicyRequest: data,
});
}
};
@ -45,63 +44,91 @@ export class EventMatcherPolicyForm extends ModelForm<EventMatcherPolicy, string
<div class="form-help-text">
${t`Matches an event against a set of criteria. If any of the configured values match, the policy passes.`}
</div>
<ak-form-element-horizontal
label=${t`Name`}
?required=${true}
name="name">
<input type="text" value="${ifDefined(this.instance?.name || "")}" class="pf-c-form-control" required>
<ak-form-element-horizontal label=${t`Name`} ?required=${true} name="name">
<input
type="text"
value="${ifDefined(this.instance?.name || "")}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal name="executionLogging">
<div class="pf-c-check">
<input type="checkbox" class="pf-c-check__input" ?checked=${first(this.instance?.executionLogging, false)}>
<label class="pf-c-check__label">
${t`Execution logging`}
</label>
<input
type="checkbox"
class="pf-c-check__input"
?checked=${first(this.instance?.executionLogging, false)}
/>
<label class="pf-c-check__label"> ${t`Execution logging`} </label>
</div>
<p class="pf-c-form__helper-text">
${t`When this option is enabled, all executions of this policy will be logged. By default, only execution errors are logged.`}
</p>
</ak-form-element-horizontal>
<ak-form-group .expanded=${true}>
<span slot="header">
${t`Policy-specific settings`}
</span>
<span slot="header"> ${t`Policy-specific settings`} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${t`Action`}
name="action">
<ak-form-element-horizontal label=${t`Action`} name="action">
<select class="pf-c-form-control">
<option value="" ?selected=${this.instance?.action === undefined}>---------</option>
${until(new EventsApi(DEFAULT_CONFIG).eventsEventsActionsList().then(actions => {
return actions.map(action => {
return html`<option value=${action.component} ?selected=${this.instance?.action === action.component}>${action.name}</option>`;
});
}), html`<option>${t`Loading...`}</option>`)}
<option value="" ?selected=${this.instance?.action === undefined}>
---------
</option>
${until(
new EventsApi(DEFAULT_CONFIG)
.eventsEventsActionsList()
.then((actions) => {
return actions.map((action) => {
return html`<option
value=${action.component}
?selected=${this.instance?.action ===
action.component}
>
${action.name}
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
<p class="pf-c-form__helper-text">${t`Match created events with this action type. When left empty, all action types will be matched.`}</p>
<p class="pf-c-form__helper-text">
${t`Match created events with this action type. When left empty, all action types will be matched.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Client IP`}
name="clientIp">
<input type="text" value="${ifDefined(this.instance?.clientIp || "")}" class="pf-c-form-control">
<p class="pf-c-form__helper-text">${t`Matches Event's Client IP (strict matching, for network matching use an Expression Policy.`}</p>
<ak-form-element-horizontal label=${t`Client IP`} name="clientIp">
<input
type="text"
value="${ifDefined(this.instance?.clientIp || "")}"
class="pf-c-form-control"
/>
<p class="pf-c-form__helper-text">
${t`Matches Event's Client IP (strict matching, for network matching use an Expression Policy.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`App`}
name="app">
<ak-form-element-horizontal label=${t`App`} name="app">
<select class="pf-c-form-control">
<option value="" ?selected=${this.instance?.app === undefined}>---------</option>
${until(new AdminApi(DEFAULT_CONFIG).adminAppsList().then(apps => {
return apps.map(app => {
return html`<option value=${app.name} ?selected=${this.instance?.app === app.name}>${app.label}</option>`;
});
}), html`<option>${t`Loading...`}</option>`)}
<option value="" ?selected=${this.instance?.app === undefined}>
---------
</option>
${until(
new AdminApi(DEFAULT_CONFIG).adminAppsList().then((apps) => {
return apps.map((app) => {
return html`<option
value=${app.name}
?selected=${this.instance?.app === app.name}
>
${app.label}
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
<p class="pf-c-form__helper-text">${t`Match events created by selected application. When left empty, all applications are matched.`}</p>
<p class="pf-c-form__helper-text">
${t`Match events created by selected application. When left empty, all applications are matched.`}
</p>
</ak-form-element-horizontal>
</div>
</ak-form-group>
</form>`;
}
}

View File

@ -11,7 +11,6 @@ import { ModelForm } from "../../../elements/forms/ModelForm";
@customElement("ak-policy-password-expiry-form")
export class PasswordExpiryPolicyForm extends ModelForm<PasswordExpiryPolicy, string> {
loadInstance(pk: string): Promise<PasswordExpiryPolicy> {
return new PoliciesApi(DEFAULT_CONFIG).policiesPasswordExpiryRetrieve({
policyUuid: pk,
@ -30,11 +29,11 @@ export class PasswordExpiryPolicyForm extends ModelForm<PasswordExpiryPolicy, st
if (this.instance) {
return new PoliciesApi(DEFAULT_CONFIG).policiesPasswordExpiryUpdate({
policyUuid: this.instance.pk || "",
passwordExpiryPolicyRequest: data
passwordExpiryPolicyRequest: data,
});
} else {
return new PoliciesApi(DEFAULT_CONFIG).policiesPasswordExpiryCreate({
passwordExpiryPolicyRequest: data
passwordExpiryPolicyRequest: data,
});
}
};
@ -44,37 +43,49 @@ export class PasswordExpiryPolicyForm extends ModelForm<PasswordExpiryPolicy, st
<div class="form-help-text">
${t`Checks if the request's user's password has been changed in the last x days, and denys based on settings.`}
</div>
<ak-form-element-horizontal
label=${t`Name`}
?required=${true}
name="name">
<input type="text" value="${ifDefined(this.instance?.name || "")}" class="pf-c-form-control" required>
<ak-form-element-horizontal label=${t`Name`} ?required=${true} name="name">
<input
type="text"
value="${ifDefined(this.instance?.name || "")}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal name="executionLogging">
<div class="pf-c-check">
<input type="checkbox" class="pf-c-check__input" ?checked=${first(this.instance?.executionLogging, false)}>
<label class="pf-c-check__label">
${t`Execution logging`}
</label>
<input
type="checkbox"
class="pf-c-check__input"
?checked=${first(this.instance?.executionLogging, false)}
/>
<label class="pf-c-check__label"> ${t`Execution logging`} </label>
</div>
<p class="pf-c-form__helper-text">
${t`When this option is enabled, all executions of this policy will be logged. By default, only execution errors are logged.`}
</p>
</ak-form-element-horizontal>
<ak-form-group .expanded=${true}>
<span slot="header">
${t`Policy-specific settings`}
</span>
<span slot="header"> ${t`Policy-specific settings`} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${t`Maximum age (in days)`}
?required=${true}
name="days">
<input type="number" value="${ifDefined(this.instance?.days || "")}" class="pf-c-form-control" required>
name="days"
>
<input
type="number"
value="${ifDefined(this.instance?.days || "")}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal name="denyOnly">
<div class="pf-c-check">
<input type="checkbox" class="pf-c-check__input" ?checked=${first(this.instance?.denyOnly, false)}>
<input
type="checkbox"
class="pf-c-check__input"
?checked=${first(this.instance?.denyOnly, false)}
/>
<label class="pf-c-check__label">
${t`Only fail the policy, don't invalidate user's password.`}
</label>
@ -84,5 +95,4 @@ export class PasswordExpiryPolicyForm extends ModelForm<PasswordExpiryPolicy, st
</ak-form-group>
</form>`;
}
}

View File

@ -12,7 +12,6 @@ import { ModelForm } from "../../../elements/forms/ModelForm";
@customElement("ak-policy-expression-form")
export class ExpressionPolicyForm extends ModelForm<ExpressionPolicy, string> {
loadInstance(pk: string): Promise<ExpressionPolicy> {
return new PoliciesApi(DEFAULT_CONFIG).policiesExpressionRetrieve({
policyUuid: pk,
@ -31,11 +30,11 @@ export class ExpressionPolicyForm extends ModelForm<ExpressionPolicy, string> {
if (this.instance) {
return new PoliciesApi(DEFAULT_CONFIG).policiesExpressionUpdate({
policyUuid: this.instance.pk || "",
expressionPolicyRequest: data
expressionPolicyRequest: data,
});
} else {
return new PoliciesApi(DEFAULT_CONFIG).policiesExpressionCreate({
expressionPolicyRequest: data
expressionPolicyRequest: data,
});
}
};
@ -45,37 +44,46 @@ export class ExpressionPolicyForm extends ModelForm<ExpressionPolicy, string> {
<div class="form-help-text">
${t`Executes the python snippet to determine whether to allow or deny a request.`}
</div>
<ak-form-element-horizontal
label=${t`Name`}
?required=${true}
name="name">
<input type="text" value="${ifDefined(this.instance?.name || "")}" class="pf-c-form-control" required>
<ak-form-element-horizontal label=${t`Name`} ?required=${true} name="name">
<input
type="text"
value="${ifDefined(this.instance?.name || "")}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal name="executionLogging">
<div class="pf-c-check">
<input type="checkbox" class="pf-c-check__input" ?checked=${first(this.instance?.executionLogging, false)}>
<label class="pf-c-check__label">
${t`Execution logging`}
</label>
<input
type="checkbox"
class="pf-c-check__input"
?checked=${first(this.instance?.executionLogging, false)}
/>
<label class="pf-c-check__label"> ${t`Execution logging`} </label>
</div>
<p class="pf-c-form__helper-text">
${t`When this option is enabled, all executions of this policy will be logged. By default, only execution errors are logged.`}
</p>
</ak-form-element-horizontal>
<ak-form-group .expanded=${true}>
<span slot="header">
${t`Policy-specific settings`}
</span>
<span slot="header"> ${t`Policy-specific settings`} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${t`Expression`}
?required=${true}
name="expression">
<ak-codemirror mode="python" value="${ifDefined(this.instance?.expression)}">
name="expression"
>
<ak-codemirror
mode="python"
value="${ifDefined(this.instance?.expression)}"
>
</ak-codemirror>
<p class="pf-c-form__helper-text">
${t`Expression using Python.`}
<a target="_blank" href="https://goauthentik.io/docs/policies/expression">
<a
target="_blank"
href="https://goauthentik.io/docs/policies/expression"
>
${t`See documentation for a list of all variables.`}
</a>
</p>
@ -84,5 +92,4 @@ export class ExpressionPolicyForm extends ModelForm<ExpressionPolicy, string> {
</ak-form-group>
</form>`;
}
}

View File

@ -11,7 +11,6 @@ import { ModelForm } from "../../../elements/forms/ModelForm";
@customElement("ak-policy-hibp-form")
export class HaveIBeenPwnedPolicyForm extends ModelForm<HaveIBeenPwendPolicy, string> {
loadInstance(pk: string): Promise<HaveIBeenPwendPolicy> {
return new PoliciesApi(DEFAULT_CONFIG).policiesHaveibeenpwnedRetrieve({
policyUuid: pk,
@ -30,11 +29,11 @@ export class HaveIBeenPwnedPolicyForm extends ModelForm<HaveIBeenPwendPolicy, st
if (this.instance) {
return new PoliciesApi(DEFAULT_CONFIG).policiesHaveibeenpwnedUpdate({
policyUuid: this.instance.pk || "",
haveIBeenPwendPolicyRequest: data
haveIBeenPwendPolicyRequest: data,
});
} else {
return new PoliciesApi(DEFAULT_CONFIG).policiesHaveibeenpwnedCreate({
haveIBeenPwendPolicyRequest: data
haveIBeenPwendPolicyRequest: data,
});
}
};
@ -45,45 +44,62 @@ export class HaveIBeenPwnedPolicyForm extends ModelForm<HaveIBeenPwendPolicy, st
${t`Checks a value from the policy request against the Have I been Pwned API, and denys the request based upon that.
Note that only a part of the hash of the password is sent, the full comparison is done clientside.`}
</div>
<ak-form-element-horizontal
label=${t`Name`}
?required=${true}
name="name">
<input type="text" value="${ifDefined(this.instance?.name || "")}" class="pf-c-form-control" required>
<ak-form-element-horizontal label=${t`Name`} ?required=${true} name="name">
<input
type="text"
value="${ifDefined(this.instance?.name || "")}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal name="executionLogging">
<div class="pf-c-check">
<input type="checkbox" class="pf-c-check__input" ?checked=${first(this.instance?.executionLogging, false)}>
<label class="pf-c-check__label">
${t`Execution logging`}
</label>
<input
type="checkbox"
class="pf-c-check__input"
?checked=${first(this.instance?.executionLogging, false)}
/>
<label class="pf-c-check__label"> ${t`Execution logging`} </label>
</div>
<p class="pf-c-form__helper-text">
${t`When this option is enabled, all executions of this policy will be logged. By default, only execution errors are logged.`}
</p>
</ak-form-element-horizontal>
<ak-form-group .expanded=${true}>
<span slot="header">
${t`Policy-specific settings`}
</span>
<span slot="header"> ${t`Policy-specific settings`} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${t`Password field`}
?required=${true}
name="passwordField">
<input type="text" value="${ifDefined(this.instance?.passwordField || "password")}" class="pf-c-form-control" required>
<p class="pf-c-form__helper-text">${t`Field key to check, field keys defined in Prompt stages are available.`}</p>
name="passwordField"
>
<input
type="text"
value="${ifDefined(this.instance?.passwordField || "password")}"
class="pf-c-form-control"
required
/>
<p class="pf-c-form__helper-text">
${t`Field key to check, field keys defined in Prompt stages are available.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Allowed count`}
?required=${true}
name="allowedCount">
<input type="number" value="${first(this.instance?.allowedCount, 0)}" class="pf-c-form-control" required>
<p class="pf-c-form__helper-text">${t`Allow up to N occurrences in the HIBP database.`}</p>
name="allowedCount"
>
<input
type="number"
value="${first(this.instance?.allowedCount, 0)}"
class="pf-c-form-control"
required
/>
<p class="pf-c-form__helper-text">
${t`Allow up to N occurrences in the HIBP database.`}
</p>
</ak-form-element-horizontal>
</div>
</ak-form-group>
</form>`;
}
}

View File

@ -11,7 +11,6 @@ import { ModelForm } from "../../../elements/forms/ModelForm";
@customElement("ak-policy-password-form")
export class PasswordPolicyForm extends ModelForm<PasswordPolicy, string> {
loadInstance(pk: string): Promise<PasswordPolicy> {
return new PoliciesApi(DEFAULT_CONFIG).policiesPasswordRetrieve({
policyUuid: pk,
@ -30,11 +29,11 @@ export class PasswordPolicyForm extends ModelForm<PasswordPolicy, string> {
if (this.instance) {
return new PoliciesApi(DEFAULT_CONFIG).policiesPasswordUpdate({
policyUuid: this.instance.pk || "",
passwordPolicyRequest: data
passwordPolicyRequest: data,
});
} else {
return new PoliciesApi(DEFAULT_CONFIG).policiesPasswordCreate({
passwordPolicyRequest: data
passwordPolicyRequest: data,
});
}
};
@ -44,83 +43,131 @@ export class PasswordPolicyForm extends ModelForm<PasswordPolicy, string> {
<div class="form-help-text">
${t`Checks the value from the policy request against several rules, mostly used to ensure password strength.`}
</div>
<ak-form-element-horizontal
label=${t`Name`}
?required=${true}
name="name">
<input type="text" value="${ifDefined(this.instance?.name || "")}" class="pf-c-form-control" required>
<ak-form-element-horizontal label=${t`Name`} ?required=${true} name="name">
<input
type="text"
value="${ifDefined(this.instance?.name || "")}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal name="executionLogging">
<div class="pf-c-check">
<input type="checkbox" class="pf-c-check__input" ?checked=${first(this.instance?.executionLogging, false)}>
<label class="pf-c-check__label">
${t`Execution logging`}
</label>
<input
type="checkbox"
class="pf-c-check__input"
?checked=${first(this.instance?.executionLogging, false)}
/>
<label class="pf-c-check__label"> ${t`Execution logging`} </label>
</div>
<p class="pf-c-form__helper-text">
${t`When this option is enabled, all executions of this policy will be logged. By default, only execution errors are logged.`}
</p>
</ak-form-element-horizontal>
<ak-form-group .expanded=${true}>
<span slot="header">
${t`Policy-specific settings`}
</span>
<span slot="header"> ${t`Policy-specific settings`} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${t`Password field`}
?required=${true}
name="passwordField">
<input type="text" value="${ifDefined(this.instance?.passwordField || "password")}" class="pf-c-form-control" required>
<p class="pf-c-form__helper-text">${t`Field key to check, field keys defined in Prompt stages are available.`}</p>
name="passwordField"
>
<input
type="text"
value="${ifDefined(this.instance?.passwordField || "password")}"
class="pf-c-form-control"
required
/>
<p class="pf-c-form__helper-text">
${t`Field key to check, field keys defined in Prompt stages are available.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Minimum length`}
?required=${true}
name="lengthMin">
<input type="number" value="${first(this.instance?.lengthMin, 10)}" class="pf-c-form-control" required>
name="lengthMin"
>
<input
type="number"
value="${first(this.instance?.lengthMin, 10)}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Minimum amount of Uppercase Characters`}
?required=${true}
name="amountUppercase">
<input type="number" value="${first(this.instance?.amountUppercase, 2)}" class="pf-c-form-control" required>
name="amountUppercase"
>
<input
type="number"
value="${first(this.instance?.amountUppercase, 2)}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Minimum amount of Lowercase Characters`}
?required=${true}
name="amountLowercase">
<input type="number" value="${first(this.instance?.amountLowercase, 2)}" class="pf-c-form-control" required>
name="amountLowercase"
>
<input
type="number"
value="${first(this.instance?.amountLowercase, 2)}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Minimum amount of Symbols Characters`}
?required=${true}
name="amountSymbols">
<input type="number" value="${first(this.instance?.amountSymbols, 2)}" class="pf-c-form-control" required>
name="amountSymbols"
>
<input
type="number"
value="${first(this.instance?.amountSymbols, 2)}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Error message`}
?required=${true}
name="errorMessage">
<input type="text" value="${ifDefined(this.instance?.errorMessage)}" class="pf-c-form-control" required>
name="errorMessage"
>
<input
type="text"
value="${ifDefined(this.instance?.errorMessage)}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
</div>
</ak-form-group>
<ak-form-group>
<span slot="header">
${t`Advanced settings`}
</span>
<span slot="header"> ${t`Advanced settings`} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${t`Symbol charset`}
?required=${true}
name="symbolCharset">
<input type="text" value="${ifDefined(this.instance?.symbolCharset || "!\\\"#$%&'()*+,-./:;<=>?@[]^_`{|}~ ")}" class="pf-c-form-control" required>
<p class="pf-c-form__helper-text">${t`Characters which are considered as symbols.`}</p>
name="symbolCharset"
>
<input
type="text"
value="${ifDefined(
this.instance?.symbolCharset ||
"!\\\"#$%&'()*+,-./:;<=>?@[]^_`{|}~ ",
)}"
class="pf-c-form-control"
required
/>
<p class="pf-c-form__helper-text">
${t`Characters which are considered as symbols.`}
</p>
</ak-form-element-horizontal>
</div>
</ak-form-group>
</form>`;
}
}

View File

@ -51,25 +51,22 @@ export class IPReputationListPage extends TablePage<IPReputation> {
return [
html`${item.ip}`,
html`${item.score}`,
html`
<ak-forms-delete
html` <ak-forms-delete
.obj=${item}
objectLabel=${t`IP Reputation`}
.usedBy=${() => {
return new PoliciesApi(DEFAULT_CONFIG).policiesReputationIpsUsedByList({
id: item.pk
id: item.pk,
});
}}
.delete=${() => {
return new PoliciesApi(DEFAULT_CONFIG).policiesReputationIpsDestroy({
id: item.pk,
});
}}>
<button slot="trigger" class="pf-c-button pf-m-danger">
${t`Delete`}
</button>
}}
>
<button slot="trigger" class="pf-c-button pf-m-danger">${t`Delete`}</button>
</ak-forms-delete>`,
];
}
}

View File

@ -11,7 +11,6 @@ import { ModelForm } from "../../../elements/forms/ModelForm";
@customElement("ak-policy-reputation-form")
export class ReputationPolicyForm extends ModelForm<ReputationPolicy, string> {
loadInstance(pk: string): Promise<ReputationPolicy> {
return new PoliciesApi(DEFAULT_CONFIG).policiesReputationRetrieve({
policyUuid: pk,
@ -30,11 +29,11 @@ export class ReputationPolicyForm extends ModelForm<ReputationPolicy, string> {
if (this.instance) {
return new PoliciesApi(DEFAULT_CONFIG).policiesReputationUpdate({
policyUuid: this.instance.pk || "",
reputationPolicyRequest: data
reputationPolicyRequest: data,
});
} else {
return new PoliciesApi(DEFAULT_CONFIG).policiesReputationCreate({
reputationPolicyRequest: data
reputationPolicyRequest: data,
});
}
};
@ -49,53 +48,64 @@ export class ReputationPolicyForm extends ModelForm<ReputationPolicy, string> {
doesn't pass when either or both of the selected options are equal or less than the
threshold.`}
</div>
<ak-form-element-horizontal
label=${t`Name`}
?required=${true}
name="name">
<input type="text" value="${ifDefined(this.instance?.name || "")}" class="pf-c-form-control" required>
<ak-form-element-horizontal label=${t`Name`} ?required=${true} name="name">
<input
type="text"
value="${ifDefined(this.instance?.name || "")}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal name="executionLogging">
<div class="pf-c-check">
<input type="checkbox" class="pf-c-check__input" ?checked=${first(this.instance?.executionLogging, false)}>
<label class="pf-c-check__label">
${t`Execution logging`}
</label>
<input
type="checkbox"
class="pf-c-check__input"
?checked=${first(this.instance?.executionLogging, false)}
/>
<label class="pf-c-check__label"> ${t`Execution logging`} </label>
</div>
<p class="pf-c-form__helper-text">
${t`When this option is enabled, all executions of this policy will be logged. By default, only execution errors are logged.`}
</p>
</ak-form-element-horizontal>
<ak-form-group .expanded=${true}>
<span slot="header">
${t`Policy-specific settings`}
</span>
<span slot="header"> ${t`Policy-specific settings`} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal name="checkIp">
<div class="pf-c-check">
<input type="checkbox" class="pf-c-check__input" ?checked=${first(this.instance?.checkIp, false)}>
<label class="pf-c-check__label">
${t`Check IP`}
</label>
<input
type="checkbox"
class="pf-c-check__input"
?checked=${first(this.instance?.checkIp, false)}
/>
<label class="pf-c-check__label"> ${t`Check IP`} </label>
</div>
</ak-form-element-horizontal>
<ak-form-element-horizontal name="checkUsername">
<div class="pf-c-check">
<input type="checkbox" class="pf-c-check__input" ?checked=${first(this.instance?.checkUsername, false)}>
<label class="pf-c-check__label">
${t`Check Username`}
</label>
<input
type="checkbox"
class="pf-c-check__input"
?checked=${first(this.instance?.checkUsername, false)}
/>
<label class="pf-c-check__label"> ${t`Check Username`} </label>
</div>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Threshold`}
?required=${true}
name="threshold">
<input type="number" value="${ifDefined(this.instance?.threshold || -5)}" class="pf-c-form-control" required>
name="threshold"
>
<input
type="number"
value="${ifDefined(this.instance?.threshold || -5)}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
</div>
</ak-form-group>
</form>`;
}
}

View File

@ -51,25 +51,22 @@ export class UserReputationListPage extends TablePage<UserReputation> {
return [
html`${item.username}`,
html`${item.score}`,
html`
<ak-forms-delete
html` <ak-forms-delete
.obj=${item}
objectLabel=${t`User Reputation`}
.usedBy=${() => {
return new PoliciesApi(DEFAULT_CONFIG).policiesReputationUsersUsedByList({
id: item.pk
id: item.pk,
});
}}
.delete=${() => {
return new PoliciesApi(DEFAULT_CONFIG).policiesReputationUsersDestroy({
id: item.pk,
});
}}>
<button slot="trigger" class="pf-c-button pf-m-danger">
${t`Delete`}
</button>
}}
>
<button slot="trigger" class="pf-c-button pf-m-danger">${t`Delete`}</button>
</ak-forms-delete>`,
];
}
}

View File

@ -10,7 +10,6 @@ import { ModelForm } from "../../elements/forms/ModelForm";
@customElement("ak-property-mapping-ldap-form")
export class PropertyMappingLDAPForm extends ModelForm<LDAPPropertyMapping, string> {
loadInstance(pk: string): Promise<LDAPPropertyMapping> {
return new PropertymappingsApi(DEFAULT_CONFIG).propertymappingsLdapRetrieve({
pmUuid: pk,
@ -29,44 +28,53 @@ export class PropertyMappingLDAPForm extends ModelForm<LDAPPropertyMapping, stri
if (this.instance) {
return new PropertymappingsApi(DEFAULT_CONFIG).propertymappingsLdapUpdate({
pmUuid: this.instance.pk || "",
lDAPPropertyMappingRequest: data
lDAPPropertyMappingRequest: data,
});
} else {
return new PropertymappingsApi(DEFAULT_CONFIG).propertymappingsLdapCreate({
lDAPPropertyMappingRequest: data
lDAPPropertyMappingRequest: data,
});
}
};
renderForm(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
<ak-form-element-horizontal
label=${t`Name`}
?required=${true}
name="name">
<input type="text" value="${ifDefined(this.instance?.name)}" class="pf-c-form-control" required>
<ak-form-element-horizontal label=${t`Name`} ?required=${true} name="name">
<input
type="text"
value="${ifDefined(this.instance?.name)}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Object field`}
?required=${true}
name="objectField">
<input type="text" value="${ifDefined(this.instance?.objectField)}" class="pf-c-form-control" required>
<p class="pf-c-form__helper-text">${t`Field of the user object this value is written to.`}</p>
name="objectField"
>
<input
type="text"
value="${ifDefined(this.instance?.objectField)}"
class="pf-c-form-control"
required
/>
<p class="pf-c-form__helper-text">
${t`Field of the user object this value is written to.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Expression`}
?required=${true}
name="expression">
<ak-form-element-horizontal label=${t`Expression`} ?required=${true} name="expression">
<ak-codemirror mode="python" value="${ifDefined(this.instance?.expression)}">
</ak-codemirror>
<p class="pf-c-form__helper-text">
${t`Expression using Python.`}
<a target="_blank" href="https://goauthentik.io/docs/property-mappings/expression/">
<a
target="_blank"
href="https://goauthentik.io/docs/property-mappings/expression/"
>
${t`See documentation for a list of all variables.`}
</a>
</p>
</ak-form-element-horizontal>
</form>`;
}
}

View File

@ -37,7 +37,7 @@ export class PropertyMappingListPage extends TablePage<PropertyMapping> {
@property()
order = "name";
@property({type: Boolean})
@property({ type: Boolean })
hideManaged = false;
apiEndpoint(page: number): Promise<AKResponse<PropertyMapping>> {
@ -62,108 +62,104 @@ export class PropertyMappingListPage extends TablePage<PropertyMapping> {
return [
html`${item.name}`,
html`${item.verboseName}`,
html`
<ak-forms-modal>
<span slot="submit">
${t`Update`}
</span>
<span slot="header">
${t`Update ${item.verboseName}`}
</span>
<ak-proxy-form
slot="form"
.args=${{
"instancePk": item.pk
html` <ak-forms-modal>
<span slot="submit"> ${t`Update`} </span>
<span slot="header"> ${t`Update ${item.verboseName}`} </span>
<ak-proxy-form
slot="form"
.args=${{
instancePk: item.pk,
}}
type=${ifDefined(item.component)}
>
</ak-proxy-form>
<button slot="trigger" class="pf-c-button pf-m-secondary">${t`Edit`}</button>
</ak-forms-modal>
<ak-forms-modal .closeAfterSuccessfulSubmit=${false}>
<span slot="submit"> ${t`Test`} </span>
<span slot="header"> ${t`Test Property Mapping`} </span>
<ak-property-mapping-test-form slot="form" .mapping=${item}>
</ak-property-mapping-test-form>
<button slot="trigger" class="pf-c-button pf-m-secondary">${t`Test`}</button>
</ak-forms-modal>
<ak-forms-delete
.obj=${item}
objectLabel=${t`Property Mapping`}
.usedBy=${() => {
return new PropertymappingsApi(
DEFAULT_CONFIG,
).propertymappingsAllUsedByList({
pmUuid: item.pk,
});
}}
type=${ifDefined(item.component)}>
</ak-proxy-form>
<button slot="trigger" class="pf-c-button pf-m-secondary">
${t`Edit`}
</button>
</ak-forms-modal>
<ak-forms-modal .closeAfterSuccessfulSubmit=${false}>
<span slot="submit">
${t`Test`}
</span>
<span slot="header">
${t`Test Property Mapping`}
</span>
<ak-property-mapping-test-form slot="form" .mapping=${item}>
</ak-property-mapping-test-form>
<button slot="trigger" class="pf-c-button pf-m-secondary">
${t`Test`}
</button>
</ak-forms-modal>
<ak-forms-delete
.obj=${item}
objectLabel=${t`Property Mapping`}
.usedBy=${() => {
return new PropertymappingsApi(DEFAULT_CONFIG).propertymappingsAllUsedByList({
pmUuid: item.pk
});
}}
.delete=${() => {
return new PropertymappingsApi(DEFAULT_CONFIG).propertymappingsAllDestroy({
pmUuid: item.pk
});
}}>
<button slot="trigger" class="pf-c-button pf-m-danger">
${t`Delete`}
</button>
</ak-forms-delete>`,
.delete=${() => {
return new PropertymappingsApi(DEFAULT_CONFIG).propertymappingsAllDestroy({
pmUuid: item.pk,
});
}}
>
<button slot="trigger" class="pf-c-button pf-m-danger">${t`Delete`}</button>
</ak-forms-delete>`,
];
}
renderToolbar(): TemplateResult {
return html`
<ak-dropdown class="pf-c-dropdown">
<button class="pf-m-primary pf-c-dropdown__toggle" type="button">
<span class="pf-c-dropdown__toggle-text">${t`Create`}</span>
<i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i>
</button>
<ul class="pf-c-dropdown__menu" hidden>
${until(new PropertymappingsApi(DEFAULT_CONFIG).propertymappingsAllTypesList().then((types) => {
return types.map((type) => {
return html`<li>
<ak-forms-modal>
<span slot="submit">
${t`Create`}
</span>
<span slot="header">
${t`Create ${type.name}`}
</span>
<ak-proxy-form
slot="form"
type=${type.component}>
</ak-proxy-form>
<button slot="trigger" class="pf-c-dropdown__menu-item">
${type.name}<br>
<small>${type.description}</small>
</button>
</ak-forms-modal>
</li>`;
});
}), html`<ak-spinner></ak-spinner>`)}
</ul>
</ak-dropdown>
${super.renderToolbar()}`;
return html` <ak-dropdown class="pf-c-dropdown">
<button class="pf-m-primary pf-c-dropdown__toggle" type="button">
<span class="pf-c-dropdown__toggle-text">${t`Create`}</span>
<i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i>
</button>
<ul class="pf-c-dropdown__menu" hidden>
${until(
new PropertymappingsApi(DEFAULT_CONFIG)
.propertymappingsAllTypesList()
.then((types) => {
return types.map((type) => {
return html`<li>
<ak-forms-modal>
<span slot="submit"> ${t`Create`} </span>
<span slot="header"> ${t`Create ${type.name}`} </span>
<ak-proxy-form slot="form" type=${type.component}>
</ak-proxy-form>
<button slot="trigger" class="pf-c-dropdown__menu-item">
${type.name}<br />
<small>${type.description}</small>
</button>
</ak-forms-modal>
</li>`;
});
}),
html`<ak-spinner></ak-spinner>`,
)}
</ul>
</ak-dropdown>
${super.renderToolbar()}`;
}
renderToolbarAfter(): TemplateResult {
return html`&nbsp;
<div class="pf-c-toolbar__group pf-m-filter-group">
<div class="pf-c-toolbar__item pf-m-search-filter">
<div class="pf-c-input-group">
<div class="pf-c-check">
<input class="pf-c-check__input" type="checkbox" id="hide-managed" name="hide-managed" ?checked=${this.hideManaged} @change=${() => {
this.hideManaged = !this.hideManaged;
this.page = 1;
this.fetch();
}} />
<label class="pf-c-check__label" for="hide-managed">${t`Hide managed mappings`}</label>
<div class="pf-c-toolbar__group pf-m-filter-group">
<div class="pf-c-toolbar__item pf-m-search-filter">
<div class="pf-c-input-group">
<div class="pf-c-check">
<input
class="pf-c-check__input"
type="checkbox"
id="hide-managed"
name="hide-managed"
?checked=${this.hideManaged}
@change=${() => {
this.hideManaged = !this.hideManaged;
this.page = 1;
this.fetch();
}}
/>
<label class="pf-c-check__label" for="hide-managed"
>${t`Hide managed mappings`}</label
>
</div>
</div>
</div>
</div>
</div>`;
</div>`;
}
}

View File

@ -28,54 +28,63 @@ export class PropertyMappingLDAPForm extends ModelForm<SAMLPropertyMapping, stri
if (this.instance) {
return new PropertymappingsApi(DEFAULT_CONFIG).propertymappingsSamlUpdate({
pmUuid: this.instance.pk || "",
sAMLPropertyMappingRequest: data
sAMLPropertyMappingRequest: data,
});
} else {
return new PropertymappingsApi(DEFAULT_CONFIG).propertymappingsSamlCreate({
sAMLPropertyMappingRequest: data
sAMLPropertyMappingRequest: data,
});
}
};
renderForm(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
<ak-form-element-horizontal
label=${t`Name`}
?required=${true}
name="name">
<input type="text" value="${ifDefined(this.instance?.name)}" class="pf-c-form-control" required>
<ak-form-element-horizontal label=${t`Name`} ?required=${true} name="name">
<input
type="text"
value="${ifDefined(this.instance?.name)}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`SAML Attribute Name`}
?required=${true}
name="samlName">
<input type="text" value="${ifDefined(this.instance?.samlName)}" class="pf-c-form-control" required>
name="samlName"
>
<input
type="text"
value="${ifDefined(this.instance?.samlName)}"
class="pf-c-form-control"
required
/>
<p class="pf-c-form__helper-text">
${t`Attribute name used for SAML Assertions. Can be a URN OID, a schema reference, or a any other string. If this property mapping is used for NameID Property, this field is discarded.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Friendly Name`}
name="friendlyName">
<input type="text" value="${ifDefined(this.instance?.friendlyName || "")}" class="pf-c-form-control">
<ak-form-element-horizontal label=${t`Friendly Name`} name="friendlyName">
<input
type="text"
value="${ifDefined(this.instance?.friendlyName || "")}"
class="pf-c-form-control"
/>
<p class="pf-c-form__helper-text">
${t`Optionally set the 'FriendlyName' value of the Assertion attribute.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Expression`}
?required=${true}
name="expression">
<ak-form-element-horizontal label=${t`Expression`} ?required=${true} name="expression">
<ak-codemirror mode="python" value="${ifDefined(this.instance?.expression)}">
</ak-codemirror>
<p class="pf-c-form__helper-text">
${t`Expression using Python.`}
<a target="_blank" href="https://goauthentik.io/docs/property-mappings/expression/">
<a
target="_blank"
href="https://goauthentik.io/docs/property-mappings/expression/"
>
${t`See documentation for a list of all variables.`}
</a>
</p>
</ak-form-element-horizontal>
</form>`;
}
}

View File

@ -10,7 +10,6 @@ import "../../elements/CodeMirror";
@customElement("ak-property-mapping-scope-form")
export class PropertyMappingScopeForm extends ModelForm<ScopeMapping, string> {
loadInstance(pk: string): Promise<ScopeMapping> {
return new PropertymappingsApi(DEFAULT_CONFIG).propertymappingsScopeRetrieve({
pmUuid: pk,
@ -29,51 +28,64 @@ export class PropertyMappingScopeForm extends ModelForm<ScopeMapping, string> {
if (this.instance) {
return new PropertymappingsApi(DEFAULT_CONFIG).propertymappingsScopeUpdate({
pmUuid: this.instance.pk || "",
scopeMappingRequest: data
scopeMappingRequest: data,
});
} else {
return new PropertymappingsApi(DEFAULT_CONFIG).propertymappingsScopeCreate({
scopeMappingRequest: data
scopeMappingRequest: data,
});
}
};
renderForm(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
<ak-form-element-horizontal
label=${t`Name`}
?required=${true}
name="name">
<input type="text" value="${ifDefined(this.instance?.name)}" class="pf-c-form-control" required>
<ak-form-element-horizontal label=${t`Name`} ?required=${true} name="name">
<input
type="text"
value="${ifDefined(this.instance?.name)}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Scope name`}
?required=${true}
name="scopeName">
<input type="text" value="${ifDefined(this.instance?.scopeName)}" class="pf-c-form-control" required>
<p class="pf-c-form__helper-text">${t`Scope which the client can specify to access these properties.`}</p>
<ak-form-element-horizontal label=${t`Scope name`} ?required=${true} name="scopeName">
<input
type="text"
value="${ifDefined(this.instance?.scopeName)}"
class="pf-c-form-control"
required
/>
<p class="pf-c-form__helper-text">
${t`Scope which the client can specify to access these properties.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Description`}
?required=${true}
name="description">
<input type="text" value="${ifDefined(this.instance?.description)}" class="pf-c-form-control" required>
<p class="pf-c-form__helper-text">${t`Description shown to the user when consenting. If left empty, the user won't be informed.`}</p>
name="description"
>
<input
type="text"
value="${ifDefined(this.instance?.description)}"
class="pf-c-form-control"
required
/>
<p class="pf-c-form__helper-text">
${t`Description shown to the user when consenting. If left empty, the user won't be informed.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Expression`}
?required=${true}
name="expression">
<ak-form-element-horizontal label=${t`Expression`} ?required=${true} name="expression">
<ak-codemirror mode="python" value="${ifDefined(this.instance?.expression)}">
</ak-codemirror>
<p class="pf-c-form__helper-text">
${t`Expression using Python.`}
<a target="_blank" href="https://goauthentik.io/docs/property-mappings/expression/">
<a
target="_blank"
href="https://goauthentik.io/docs/property-mappings/expression/"
>
${t`See documentation for a list of all variables.`}
</a>
</p>
</ak-form-element-horizontal>
</form>`;
}
}

View File

@ -1,4 +1,10 @@
import { CoreApi, PolicyTestRequest, PropertyMapping, PropertymappingsApi, PropertyMappingTestResult } from "authentik-api";
import {
CoreApi,
PolicyTestRequest,
PropertyMapping,
PropertymappingsApi,
PropertyMappingTestResult,
} from "authentik-api";
import { t } from "@lingui/macro";
import { customElement, property } from "lit-element";
import { html, TemplateResult } from "lit-html";
@ -13,11 +19,10 @@ import { first } from "../../utils";
@customElement("ak-property-mapping-test-form")
export class PolicyTestForm extends Form<PolicyTestRequest> {
@property({attribute: false})
@property({ attribute: false })
mapping?: PropertyMapping;
@property({ attribute: false})
@property({ attribute: false })
result?: PropertyMappingTestResult;
@property({ attribute: false })
@ -29,52 +34,62 @@ export class PolicyTestForm extends Form<PolicyTestRequest> {
send = (data: PolicyTestRequest): Promise<PropertyMappingTestResult> => {
this.request = data;
return new PropertymappingsApi(DEFAULT_CONFIG).propertymappingsAllTestCreate({
pmUuid: this.mapping?.pk || "",
policyTestRequest: data,
formatResult: true,
}).then(result => this.result = result);
return new PropertymappingsApi(DEFAULT_CONFIG)
.propertymappingsAllTestCreate({
pmUuid: this.mapping?.pk || "",
policyTestRequest: data,
formatResult: true,
})
.then((result) => (this.result = result));
};
renderResult(): TemplateResult {
return html`<ak-form-element-horizontal
label=${t`Result`}>
${this.result?.successful ?
html`<ak-codemirror mode="javascript" ?readOnly=${true} value="${ifDefined(this.result?.result)}">
</ak-codemirror>`:
html`
<div class="pf-c-form__group-label">
<div class="c-form__horizontal-group">
<span class="pf-c-form__label-text">${this.result?.result}</span>
</div>
</div>`}
return html`<ak-form-element-horizontal label=${t`Result`}>
${this.result?.successful
? html`<ak-codemirror
mode="javascript"
?readOnly=${true}
value="${ifDefined(this.result?.result)}"
>
</ak-codemirror>`
: html` <div class="pf-c-form__group-label">
<div class="c-form__horizontal-group">
<span class="pf-c-form__label-text">${this.result?.result}</span>
</div>
</div>`}
</ak-form-element-horizontal>`;
}
renderForm(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
<ak-form-element-horizontal
label=${t`User`}
?required=${true}
name="user">
<ak-form-element-horizontal label=${t`User`} ?required=${true} name="user">
<select class="pf-c-form-control">
${until(new CoreApi(DEFAULT_CONFIG).coreUsersList({
ordering: "username",
}).then(users => {
return users.results.map(user => {
return html`<option ?selected=${this.request?.user.toString() === user.pk.toString()} value=${user.pk}>${user.username}</option>`;
});
}), html`<option>${t`Loading...`}</option>`)}
${until(
new CoreApi(DEFAULT_CONFIG)
.coreUsersList({
ordering: "username",
})
.then((users) => {
return users.results.map((user) => {
return html`<option
?selected=${this.request?.user.toString() ===
user.pk.toString()}
value=${user.pk}
>
${user.username}
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Context`}
name="context">
<ak-codemirror mode="yaml" value=${YAML.stringify(first(this.request?.context, {}))}>>
<ak-form-element-horizontal label=${t`Context`} name="context">
<ak-codemirror mode="yaml" value=${YAML.stringify(first(this.request?.context, {}))}
>>
</ak-codemirror>
</ak-form-element-horizontal>
${this.result ? this.renderResult(): html``}
${this.result ? this.renderResult() : html``}
</form>`;
}
}

View File

@ -58,88 +58,76 @@ export class ProviderListPage extends TablePage<Provider> {
row(item: Provider): TemplateResult[] {
return [
html`<a href="#/core/providers/${item.pk}">
${item.name}
</a>`,
item.assignedApplicationName ?
html`<i class="pf-icon pf-icon-ok pf-m-success"></i>
${t`Assigned to application `}
<a href="#/core/applications/${item.assignedApplicationSlug}">${item.assignedApplicationName}</a>` :
html`<i class="pf-icon pf-icon-warning-triangle pf-m-warning"></i>
${t`Warning: Provider not assigned to any application.`}`,
html`<a href="#/core/providers/${item.pk}"> ${item.name} </a>`,
item.assignedApplicationName
? html`<i class="pf-icon pf-icon-ok pf-m-success"></i>
${t`Assigned to application `}
<a href="#/core/applications/${item.assignedApplicationSlug}"
>${item.assignedApplicationName}</a
>`
: html`<i class="pf-icon pf-icon-warning-triangle pf-m-warning"></i>
${t`Warning: Provider not assigned to any application.`}`,
html`${item.verboseName}`,
html`
<ak-forms-modal>
<span slot="submit">
${t`Update`}
</span>
<span slot="header">
${t`Update ${item.verboseName}`}
</span>
<ak-proxy-form
slot="form"
.args=${{
"instancePk": item.pk
html` <ak-forms-modal>
<span slot="submit"> ${t`Update`} </span>
<span slot="header"> ${t`Update ${item.verboseName}`} </span>
<ak-proxy-form
slot="form"
.args=${{
instancePk: item.pk,
}}
type=${ifDefined(item.component)}
>
</ak-proxy-form>
<button slot="trigger" class="pf-c-button pf-m-secondary">${t`Edit`}</button>
</ak-forms-modal>
<ak-forms-delete
.obj=${item}
objectLabel=${t`Provider`}
.usedBy=${() => {
return new ProvidersApi(DEFAULT_CONFIG).providersAllUsedByList({
id: item.pk,
});
}}
type=${ifDefined(item.component)}>
</ak-proxy-form>
<button slot="trigger" class="pf-c-button pf-m-secondary">
${t`Edit`}
</button>
</ak-forms-modal>
<ak-forms-delete
.obj=${item}
objectLabel=${t`Provider`}
.usedBy=${() => {
return new ProvidersApi(DEFAULT_CONFIG).providersAllUsedByList({
id: item.pk
});
}}
.delete=${() => {
return new ProvidersApi(DEFAULT_CONFIG).providersAllDestroy({
id: item.pk
});
}}>
<button slot="trigger" class="pf-c-button pf-m-danger">
${t`Delete`}
</button>
</ak-forms-delete>`,
.delete=${() => {
return new ProvidersApi(DEFAULT_CONFIG).providersAllDestroy({
id: item.pk,
});
}}
>
<button slot="trigger" class="pf-c-button pf-m-danger">${t`Delete`}</button>
</ak-forms-delete>`,
];
}
renderToolbar(): TemplateResult {
return html`
<ak-dropdown class="pf-c-dropdown">
<button class="pf-m-primary pf-c-dropdown__toggle" type="button">
<span class="pf-c-dropdown__toggle-text">${t`Create`}</span>
<i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i>
</button>
<ul class="pf-c-dropdown__menu" hidden>
${until(new ProvidersApi(DEFAULT_CONFIG).providersAllTypesList().then((types) => {
return types.map((type) => {
return html`<li>
<ak-forms-modal>
<span slot="submit">
${t`Create`}
</span>
<span slot="header">
${t`Create ${type.name}`}
</span>
<ak-proxy-form
slot="form"
type=${type.component}>
</ak-proxy-form>
<button slot="trigger" class="pf-c-dropdown__menu-item">
${type.name}<br>
<small>${type.description}</small>
</button>
</ak-forms-modal>
</li>`;
});
}), html`<ak-spinner></ak-spinner>`)}
</ul>
</ak-dropdown>
${super.renderToolbar()}`;
return html` <ak-dropdown class="pf-c-dropdown">
<button class="pf-m-primary pf-c-dropdown__toggle" type="button">
<span class="pf-c-dropdown__toggle-text">${t`Create`}</span>
<i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i>
</button>
<ul class="pf-c-dropdown__menu" hidden>
${until(
new ProvidersApi(DEFAULT_CONFIG).providersAllTypesList().then((types) => {
return types.map((type) => {
return html`<li>
<ak-forms-modal>
<span slot="submit"> ${t`Create`} </span>
<span slot="header"> ${t`Create ${type.name}`} </span>
<ak-proxy-form slot="form" type=${type.component}>
</ak-proxy-form>
<button slot="trigger" class="pf-c-dropdown__menu-item">
${type.name}<br />
<small>${type.description}</small>
</button>
</ak-forms-modal>
</li>`;
});
}),
html`<ak-spinner></ak-spinner>`,
)}
</ul>
</ak-dropdown>
${super.renderToolbar()}`;
}
}

View File

@ -14,12 +14,13 @@ import { ifDefined } from "lit-html/directives/if-defined";
@customElement("ak-provider-view")
export class ProviderViewPage extends LitElement {
@property({type: Number})
@property({ type: Number })
set providerID(value: number) {
new ProvidersApi(DEFAULT_CONFIG).providersAllRetrieve({
id: value,
}).then((prov) => (this.provider = prov));
new ProvidersApi(DEFAULT_CONFIG)
.providersAllRetrieve({
id: value,
})
.then((prov) => (this.provider = prov));
}
@property({ attribute: false })
@ -31,13 +32,21 @@ export class ProviderViewPage extends LitElement {
}
switch (this.provider?.component) {
case "ak-provider-saml-form":
return html`<ak-provider-saml-view providerID=${ifDefined(this.provider.pk)}></ak-provider-saml-view>`;
return html`<ak-provider-saml-view
providerID=${ifDefined(this.provider.pk)}
></ak-provider-saml-view>`;
case "ak-provider-oauth2-form":
return html`<ak-provider-oauth2-view providerID=${ifDefined(this.provider.pk)}></ak-provider-oauth2-view>`;
return html`<ak-provider-oauth2-view
providerID=${ifDefined(this.provider.pk)}
></ak-provider-oauth2-view>`;
case "ak-provider-proxy-form":
return html`<ak-provider-proxy-view providerID=${ifDefined(this.provider.pk)}></ak-provider-proxy-view>`;
return html`<ak-provider-proxy-view
providerID=${ifDefined(this.provider.pk)}
></ak-provider-proxy-view>`;
case "ak-provider-ldap-form":
return html`<ak-provider-ldap-view providerID=${ifDefined(this.provider.pk)}></ak-provider-ldap-view>`;
return html`<ak-provider-ldap-view
providerID=${ifDefined(this.provider.pk)}
></ak-provider-ldap-view>`;
default:
return html`<p>Invalid provider type ${this.provider?.component}</p>`;
}
@ -45,10 +54,11 @@ export class ProviderViewPage extends LitElement {
render(): TemplateResult {
return html`<ak-page-header
icon="pf-icon pf-icon-integration"
header=${ifDefined(this.provider?.name)}
description=${ifDefined(this.provider?.verboseName)}>
</ak-page-header>
${this.renderProvider()}`;
icon="pf-icon pf-icon-integration"
header=${ifDefined(this.provider?.name)}
description=${ifDefined(this.provider?.verboseName)}
>
</ak-page-header>
${this.renderProvider()}`;
}
}

View File

@ -10,12 +10,11 @@ import "../../pages/applications/ApplicationForm";
@customElement("ak-provider-related-application")
export class RelatedApplicationButton extends LitElement {
static get styles(): CSSResult[] {
return [PFBase, PFButton];
}
@property({attribute: false})
@property({ attribute: false })
provider?: Provider;
render(): TemplateResult {
@ -25,18 +24,10 @@ export class RelatedApplicationButton extends LitElement {
</a>`;
}
return html`<ak-forms-modal>
<span slot="submit">
${t`Create`}
</span>
<span slot="header">
${t`Create Application`}
</span>
<ak-application-form slot="form" .provider=${this.provider?.pk}>
</ak-application-form>
<button slot="trigger" class="pf-c-button pf-m-primary">
${t`Create`}
</button>
<span slot="submit"> ${t`Create`} </span>
<span slot="header"> ${t`Create Application`} </span>
<ak-application-form slot="form" .provider=${this.provider?.pk}> </ak-application-form>
<button slot="trigger" class="pf-c-button pf-m-primary">${t`Create`}</button>
</ak-forms-modal>`;
}
}

View File

@ -1,4 +1,11 @@
import { FlowsApi, ProvidersApi, LDAPProvider, CoreApi, FlowsInstancesListDesignationEnum, CryptoApi } from "authentik-api";
import {
FlowsApi,
ProvidersApi,
LDAPProvider,
CoreApi,
FlowsInstancesListDesignationEnum,
CryptoApi,
} from "authentik-api";
import { t } from "@lingui/macro";
import { customElement } from "lit-element";
import { html, TemplateResult } from "lit-html";
@ -12,7 +19,6 @@ import { first } from "../../../utils";
@customElement("ak-provider-ldap-form")
export class LDAPProviderFormPage extends ModelForm<LDAPProvider, number> {
loadInstance(pk: number): Promise<LDAPProvider> {
return new ProvidersApi(DEFAULT_CONFIG).providersLdapRetrieve({
id: pk,
@ -31,109 +37,165 @@ export class LDAPProviderFormPage extends ModelForm<LDAPProvider, number> {
if (this.instance) {
return new ProvidersApi(DEFAULT_CONFIG).providersLdapUpdate({
id: this.instance.pk || 0,
lDAPProviderRequest: data
lDAPProviderRequest: data,
});
} else {
return new ProvidersApi(DEFAULT_CONFIG).providersLdapCreate({
lDAPProviderRequest: data
lDAPProviderRequest: data,
});
}
};
renderForm(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
<ak-form-element-horizontal
label=${t`Name`}
?required=${true}
name="name">
<input type="text" value="${ifDefined(this.instance?.name)}" class="pf-c-form-control" required>
<ak-form-element-horizontal label=${t`Name`} ?required=${true} name="name">
<input
type="text"
value="${ifDefined(this.instance?.name)}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Bind flow`}
?required=${true}
name="authorizationFlow">
name="authorizationFlow"
>
<select class="pf-c-form-control">
${until(tenant().then(t => {
return new FlowsApi(DEFAULT_CONFIG).flowsInstancesList({
ordering: "pk",
designation: FlowsInstancesListDesignationEnum.Authentication,
}).then(flows => {
return flows.results.map(flow => {
let selected = flow.pk === t.flowAuthentication;
if (this.instance?.authorizationFlow === flow.pk) {
selected = true;
}
return html`<option value=${ifDefined(flow.pk)} ?selected=${selected}>${flow.name} (${flow.slug})</option>`;
});
});
}), html`<option>${t`Loading...`}</option>`)}
${until(
tenant().then((t) => {
return new FlowsApi(DEFAULT_CONFIG)
.flowsInstancesList({
ordering: "pk",
designation: FlowsInstancesListDesignationEnum.Authentication,
})
.then((flows) => {
return flows.results.map((flow) => {
let selected = flow.pk === t.flowAuthentication;
if (this.instance?.authorizationFlow === flow.pk) {
selected = true;
}
return html`<option
value=${ifDefined(flow.pk)}
?selected=${selected}
>
${flow.name} (${flow.slug})
</option>`;
});
});
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
<p class="pf-c-form__helper-text">${t`Flow used for users to authenticate. Currently only identification and password stages are supported.`}</p>
<p class="pf-c-form__helper-text">
${t`Flow used for users to authenticate. Currently only identification and password stages are supported.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Group`}
name="searchGroup">
<ak-form-element-horizontal label=${t`Group`} name="searchGroup">
<select class="pf-c-form-control">
<option value="" ?selected=${this.instance?.searchGroup === undefined}>---------</option>
${until(new CoreApi(DEFAULT_CONFIG).coreGroupsList({}).then(groups => {
return groups.results.map(group => {
return html`<option value=${ifDefined(group.pk)} ?selected=${this.instance?.searchGroup === group.pk}>${group.name}</option>`;
});
}), html`<option>${t`Loading...`}</option>`)}
<option value="" ?selected=${this.instance?.searchGroup === undefined}>
---------
</option>
${until(
new CoreApi(DEFAULT_CONFIG).coreGroupsList({}).then((groups) => {
return groups.results.map((group) => {
return html`<option
value=${ifDefined(group.pk)}
?selected=${this.instance?.searchGroup === group.pk}
>
${group.name}
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
<p class="pf-c-form__helper-text">${t`Users in the selected group can do search queries. If no group is selected, no LDAP Searches are allowed.`}</p>
<p class="pf-c-form__helper-text">
${t`Users in the selected group can do search queries. If no group is selected, no LDAP Searches are allowed.`}
</p>
</ak-form-element-horizontal>
<ak-form-group .expanded=${true}>
<span slot="header">
${t`Protocol settings`}
</span>
<span slot="header"> ${t`Protocol settings`} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${t`Base DN`}
?required=${true}
name="baseDn">
<input type="text" value="${first(this.instance?.baseDn, "DC=ldap,DC=goauthentik,DC=io")}" class="pf-c-form-control" required>
<p class="pf-c-form__helper-text">${t`LDAP DN under which bind requests and search requests can be made.`}</p>
<ak-form-element-horizontal label=${t`Base DN`} ?required=${true} name="baseDn">
<input
type="text"
value="${first(this.instance?.baseDn, "DC=ldap,DC=goauthentik,DC=io")}"
class="pf-c-form-control"
required
/>
<p class="pf-c-form__helper-text">
${t`LDAP DN under which bind requests and search requests can be made.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`TLS Server name`}
name="tlsServerName">
<input type="text" value="${first(this.instance?.tlsServerName, "")}" class="pf-c-form-control">
<p class="pf-c-form__helper-text">${t`Server name for which this provider's certificate is valid for.`}</p>
<ak-form-element-horizontal label=${t`TLS Server name`} name="tlsServerName">
<input
type="text"
value="${first(this.instance?.tlsServerName, "")}"
class="pf-c-form-control"
/>
<p class="pf-c-form__helper-text">
${t`Server name for which this provider's certificate is valid for.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Certificate`}
name="certificate">
<ak-form-element-horizontal label=${t`Certificate`} name="certificate">
<select class="pf-c-form-control">
<option value="" ?selected=${this.instance?.certificate === undefined}>---------</option>
${until(new CryptoApi(DEFAULT_CONFIG).cryptoCertificatekeypairsList({
ordering: "pk",
hasKey: true,
}).then(keys => {
return keys.results.map(key => {
return html`<option value=${ifDefined(key.pk)} ?selected=${this.instance?.certificate === key.pk}>${key.name}</option>`;
});
}), html`<option>${t`Loading...`}</option>`)}
<option value="" ?selected=${this.instance?.certificate === undefined}>
---------
</option>
${until(
new CryptoApi(DEFAULT_CONFIG)
.cryptoCertificatekeypairsList({
ordering: "pk",
hasKey: true,
})
.then((keys) => {
return keys.results.map((key) => {
return html`<option
value=${ifDefined(key.pk)}
?selected=${this.instance?.certificate === key.pk}
>
${key.name}
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`UID start number`}
?required=${true}
name="uidStartNumber">
<input type="number" value="${first(this.instance?.uidStartNumber, 2000)}" class="pf-c-form-control" required>
<p class="pf-c-form__helper-text">${t`The start for uidNumbers, this number is added to the user.Pk to make sure that the numbers aren't too low for POSIX users. Default is 2000 to ensure that we don't collide with local users uidNumber`}</p>
name="uidStartNumber"
>
<input
type="number"
value="${first(this.instance?.uidStartNumber, 2000)}"
class="pf-c-form-control"
required
/>
<p class="pf-c-form__helper-text">
${t`The start for uidNumbers, this number is added to the user.Pk to make sure that the numbers aren't too low for POSIX users. Default is 2000 to ensure that we don't collide with local users uidNumber`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`GID start number`}
?required=${true}
name="gidStartNumber">
<input type="number" value="${first(this.instance?.gidStartNumber, 4000)}" class="pf-c-form-control" required>
<p class="pf-c-form__helper-text">${t`The start for gidNumbers, this number is added to a number generated from the group.Pk to make sure that the numbers aren't too low for POSIX groups. Default is 4000 to ensure that we don't collide with local groups or users primary groups gidNumber`}</p>
name="gidStartNumber"
>
<input
type="number"
value="${first(this.instance?.gidStartNumber, 4000)}"
class="pf-c-form-control"
required
/>
<p class="pf-c-form__helper-text">
${t`The start for gidNumbers, this number is added to a number generated from the group.Pk to make sure that the numbers aren't too low for POSIX groups. Default is 4000 to ensure that we don't collide with local groups or users primary groups gidNumber`}
</p>
</ak-form-element-horizontal>
</div>
</ak-form-group>
</form>`;
}
}

View File

@ -25,24 +25,37 @@ import { EVENT_REFRESH } from "../../../constants";
@customElement("ak-provider-ldap-view")
export class LDAPProviderViewPage extends LitElement {
@property()
set args(value: { [key: string]: number }) {
this.providerID = value.id;
}
@property({type: Number})
@property({ type: Number })
set providerID(value: number) {
new ProvidersApi(DEFAULT_CONFIG).providersLdapRetrieve({
id: value,
}).then((prov) => (this.provider = prov));
new ProvidersApi(DEFAULT_CONFIG)
.providersLdapRetrieve({
id: value,
})
.then((prov) => (this.provider = prov));
}
@property({ attribute: false })
provider?: LDAPProvider;
static get styles(): CSSResult[] {
return [PFBase, PFButton, PFPage, PFFlex, PFDisplay, PFGallery, PFContent, PFCard, PFDescriptionList, PFSizing, AKGlobal];
return [
PFBase,
PFButton,
PFPage,
PFFlex,
PFDisplay,
PFGallery,
PFContent,
PFCard,
PFDescriptionList,
PFSizing,
AKGlobal,
];
}
constructor() {
@ -58,72 +71,90 @@ export class LDAPProviderViewPage extends LitElement {
return html``;
}
return html`<ak-tabs>
<section slot="page-overview" data-tab-title="${t`Overview`}" class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-u-display-flex pf-u-justify-content-center">
<div class="pf-u-w-75">
<div class="pf-c-card">
<div class="pf-c-card__body">
<dl class="pf-c-description-list pf-m-3-col-on-lg">
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${t`Name`}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">${this.provider.name}</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${t`Assigned to application`}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
<ak-provider-related-application .provider=${this.provider}></ak-provider-related-application>
</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${t`Base DN`}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">${this.provider.baseDn}</div>
</dd>
</div>
</dl>
</div>
<div class="pf-c-card__footer">
<ak-forms-modal>
<span slot="submit">
${t`Update`}
</span>
<span slot="header">
${t`Update LDAP Provider`}
</span>
<ak-provider-ldap-form
slot="form"
.instancePk=${this.provider.pk}>
</ak-provider-ldap-form>
<button slot="trigger" class="pf-c-button pf-m-primary">
${t`Edit`}
</button>
</ak-forms-modal>
</div>
<section
slot="page-overview"
data-tab-title="${t`Overview`}"
class="pf-c-page__main-section pf-m-no-padding-mobile"
>
<div class="pf-u-display-flex pf-u-justify-content-center">
<div class="pf-u-w-75">
<div class="pf-c-card">
<div class="pf-c-card__body">
<dl class="pf-c-description-list pf-m-3-col-on-lg">
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text"
>${t`Name`}</span
>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
${this.provider.name}
</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text"
>${t`Assigned to application`}</span
>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
<ak-provider-related-application
.provider=${this.provider}
></ak-provider-related-application>
</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text"
>${t`Base DN`}</span
>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
${this.provider.baseDn}
</div>
</dd>
</div>
</dl>
</div>
<div class="pf-c-card__footer">
<ak-forms-modal>
<span slot="submit"> ${t`Update`} </span>
<span slot="header"> ${t`Update LDAP Provider`} </span>
<ak-provider-ldap-form
slot="form"
.instancePk=${this.provider.pk}
>
</ak-provider-ldap-form>
<button slot="trigger" class="pf-c-button pf-m-primary">
${t`Edit`}
</button>
</ak-forms-modal>
</div>
</div>
</div>
</section>
<section slot="page-changelog" data-tab-title="${t`Changelog`}" class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-c-card">
<div class="pf-c-card__body">
<ak-object-changelog
targetModelPk=${this.provider.pk || ""}
targetModelApp="authentik_providers_ldap"
targetModelName="LDAPProvider">
</ak-object-changelog>
</div>
</div>
</section>
<section
slot="page-changelog"
data-tab-title="${t`Changelog`}"
class="pf-c-page__main-section pf-m-no-padding-mobile"
>
<div class="pf-c-card">
<div class="pf-c-card__body">
<ak-object-changelog
targetModelPk=${this.provider.pk || ""}
targetModelApp="authentik_providers_ldap"
targetModelName="LDAPProvider"
>
</ak-object-changelog>
</div>
</section>
</ak-tabs>`;
</div>
</section>
</ak-tabs>`;
}
}

View File

@ -1,4 +1,15 @@
import { CryptoApi, FlowsApi, OAuth2Provider, ClientTypeEnum, IssuerModeEnum, JwtAlgEnum, SubModeEnum, PropertymappingsApi, ProvidersApi, FlowsInstancesListDesignationEnum } from "authentik-api";
import {
CryptoApi,
FlowsApi,
OAuth2Provider,
ClientTypeEnum,
IssuerModeEnum,
JwtAlgEnum,
SubModeEnum,
PropertymappingsApi,
ProvidersApi,
FlowsInstancesListDesignationEnum,
} from "authentik-api";
import { t } from "@lingui/macro";
import { customElement, property } from "lit-element";
import { html, TemplateResult } from "lit-html";
@ -12,17 +23,18 @@ import { first, randomString } from "../../../utils";
@customElement("ak-provider-oauth2-form")
export class OAuth2ProviderFormPage extends ModelForm<OAuth2Provider, number> {
loadInstance(pk: number): Promise<OAuth2Provider> {
return new ProvidersApi(DEFAULT_CONFIG).providersOauth2Retrieve({
id: pk,
}).then(provider => {
this.showClientSecret = provider.clientType === ClientTypeEnum.Confidential;
return provider;
});
return new ProvidersApi(DEFAULT_CONFIG)
.providersOauth2Retrieve({
id: pk,
})
.then((provider) => {
this.showClientSecret = provider.clientType === ClientTypeEnum.Confidential;
return provider;
});
}
@property({type: Boolean})
@property({ type: Boolean })
showClientSecret = true;
getSuccessMessage(): string {
@ -37,82 +49,122 @@ export class OAuth2ProviderFormPage extends ModelForm<OAuth2Provider, number> {
if (this.instance) {
return new ProvidersApi(DEFAULT_CONFIG).providersOauth2Update({
id: this.instance.pk || 0,
oAuth2ProviderRequest: data
oAuth2ProviderRequest: data,
});
} else {
return new ProvidersApi(DEFAULT_CONFIG).providersOauth2Create({
oAuth2ProviderRequest: data
oAuth2ProviderRequest: data,
});
}
};
renderForm(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
<ak-form-element-horizontal
label=${t`Name`}
?required=${true}
name="name">
<input type="text" value="${ifDefined(this.instance?.name)}" class="pf-c-form-control" required>
<ak-form-element-horizontal label=${t`Name`} ?required=${true} name="name">
<input
type="text"
value="${ifDefined(this.instance?.name)}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Authorization flow`}
?required=${true}
name="authorizationFlow">
name="authorizationFlow"
>
<select class="pf-c-form-control">
${until(new FlowsApi(DEFAULT_CONFIG).flowsInstancesList({
ordering: "pk",
designation: FlowsInstancesListDesignationEnum.Authorization,
}).then(flows => {
return flows.results.map(flow => {
return html`<option value=${ifDefined(flow.pk)} ?selected=${this.instance?.authorizationFlow === flow.pk}>${flow.name} (${flow.slug})</option>`;
});
}), html`<option>${t`Loading...`}</option>`)}
${until(
new FlowsApi(DEFAULT_CONFIG)
.flowsInstancesList({
ordering: "pk",
designation: FlowsInstancesListDesignationEnum.Authorization,
})
.then((flows) => {
return flows.results.map((flow) => {
return html`<option
value=${ifDefined(flow.pk)}
?selected=${this.instance?.authorizationFlow === flow.pk}
>
${flow.name} (${flow.slug})
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
<p class="pf-c-form__helper-text">${t`Flow used when authorizing this provider.`}</p>
<p class="pf-c-form__helper-text">
${t`Flow used when authorizing this provider.`}
</p>
</ak-form-element-horizontal>
<ak-form-group .expanded=${true}>
<span slot="header">
${t`Protocol settings`}
</span>
<span slot="header"> ${t`Protocol settings`} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${t`Client type`}
?required=${true}
name="clientType">
<select class="pf-c-form-control" @change=${(ev: Event) => {
const target = ev.target as HTMLSelectElement;
if (target.selectedOptions[0].value === ClientTypeEnum.Public) {
this.showClientSecret = false;
} else {
this.showClientSecret = true;
}
}}>
<option value=${ClientTypeEnum.Confidential} ?selected=${this.instance?.clientType === ClientTypeEnum.Confidential}>
name="clientType"
>
<select
class="pf-c-form-control"
@change=${(ev: Event) => {
const target = ev.target as HTMLSelectElement;
if (target.selectedOptions[0].value === ClientTypeEnum.Public) {
this.showClientSecret = false;
} else {
this.showClientSecret = true;
}
}}
>
<option
value=${ClientTypeEnum.Confidential}
?selected=${this.instance?.clientType ===
ClientTypeEnum.Confidential}
>
${t`Confidential`}
</option>
<option value=${ClientTypeEnum.Public} ?selected=${this.instance?.clientType === ClientTypeEnum.Public}>
<option
value=${ClientTypeEnum.Public}
?selected=${this.instance?.clientType === ClientTypeEnum.Public}
>
${t`Public`}
</option>
</select>
<p class="pf-c-form__helper-text">${t`Confidential clients are capable of maintaining the confidentiality of their credentials. Public clients are incapable.`}</p>
<p class="pf-c-form__helper-text">
${t`Confidential clients are capable of maintaining the confidentiality of their credentials. Public clients are incapable.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Client ID`}
?required=${true}
name="clientId">
<input type="text" value="${first(this.instance?.clientId, randomString(40))}" class="pf-c-form-control" required>
name="clientId"
>
<input
type="text"
value="${first(this.instance?.clientId, randomString(40))}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal
?hidden=${!this.showClientSecret}
label=${t`Client Secret`}
name="clientSecret">
<input type="text" value="${first(this.instance?.clientSecret, randomString(128))}" class="pf-c-form-control">
name="clientSecret"
>
<input
type="text"
value="${first(this.instance?.clientSecret, randomString(128))}"
class="pf-c-form-control"
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Redirect URIs/Origins`}
name="redirectUris">
<textarea class="pf-c-form-control">${this.instance?.redirectUris}</textarea>
name="redirectUris"
>
<textarea class="pf-c-form-control">
${this.instance?.redirectUris}</textarea
>
<p class="pf-c-form__helper-text">
${t`Valid redirect URLs after a successful authorization flow. Also specify any origins here for Implicit flows.`}
</p>
@ -124,98 +176,167 @@ export class OAuth2ProviderFormPage extends ModelForm<OAuth2Provider, number> {
</ak-form-group>
<ak-form-group>
<span slot="header">
${t`Advanced protocol settings`}
</span>
<span slot="header"> ${t`Advanced protocol settings`} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${t`Access code validity`}
?required=${true}
name="accessCodeValidity">
<input type="text" value="${first(this.instance?.accessCodeValidity, "minutes=1")}" class="pf-c-form-control" required>
<p class="pf-c-form__helper-text">${t`Configure how long access codes are valid for.`}</p>
<p class="pf-c-form__helper-text">${t`(Format: hours=-1;minutes=-2;seconds=-3).`}</p>
name="accessCodeValidity"
>
<input
type="text"
value="${first(this.instance?.accessCodeValidity, "minutes=1")}"
class="pf-c-form-control"
required
/>
<p class="pf-c-form__helper-text">
${t`Configure how long access codes are valid for.`}
</p>
<p class="pf-c-form__helper-text">
${t`(Format: hours=-1;minutes=-2;seconds=-3).`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Token validity`}
?required=${true}
name="tokenValidity">
<input type="text" value="${first(this.instance?.tokenValidity, "minutes=10")}" class="pf-c-form-control" required>
<p class="pf-c-form__helper-text">${t`Configure how long refresh tokens and their id_tokens are valid for.`}</p>
<p class="pf-c-form__helper-text">${t`(Format: hours=-1;minutes=-2;seconds=-3).`}</p>
name="tokenValidity"
>
<input
type="text"
value="${first(this.instance?.tokenValidity, "minutes=10")}"
class="pf-c-form-control"
required
/>
<p class="pf-c-form__helper-text">
${t`Configure how long refresh tokens and their id_tokens are valid for.`}
</p>
<p class="pf-c-form__helper-text">
${t`(Format: hours=-1;minutes=-2;seconds=-3).`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`JWT Algorithm`}
?required=${true}
name="jwtAlg">
name="jwtAlg"
>
<select class="pf-c-form-control">
<option value=${JwtAlgEnum.Rs256} ?selected=${this.instance?.jwtAlg === JwtAlgEnum.Rs256}>
<option
value=${JwtAlgEnum.Rs256}
?selected=${this.instance?.jwtAlg === JwtAlgEnum.Rs256}
>
${t`RS256 (Asymmetric Encryption)`}
</option>
<option value=${JwtAlgEnum.Hs256} ?selected=${this.instance?.jwtAlg === JwtAlgEnum.Hs256}>
<option
value=${JwtAlgEnum.Hs256}
?selected=${this.instance?.jwtAlg === JwtAlgEnum.Hs256}
>
${t`HS256 (Symmetric Encryption)`}
</option>
</select>
<p class="pf-c-form__helper-text">${t`Algorithm used to sign the JWT Tokens.`}</p>
<p class="pf-c-form__helper-text">
${t`Algorithm used to sign the JWT Tokens.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Scopes`}
name="propertyMappings">
<ak-form-element-horizontal label=${t`Scopes`} name="propertyMappings">
<select class="pf-c-form-control" multiple>
${until(new PropertymappingsApi(DEFAULT_CONFIG).propertymappingsScopeList({
ordering: "scope_name"
}).then(scopes => {
return scopes.results.map(scope => {
let selected = false;
if (!this.instance?.propertyMappings) {
selected = scope.managed?.startsWith("goauthentik.io/providers/oauth2/scope-") || false;
} else {
selected = Array.from(this.instance?.propertyMappings).some(su => {
return su == scope.pk;
${until(
new PropertymappingsApi(DEFAULT_CONFIG)
.propertymappingsScopeList({
ordering: "scope_name",
})
.then((scopes) => {
return scopes.results.map((scope) => {
let selected = false;
if (!this.instance?.propertyMappings) {
selected =
scope.managed?.startsWith(
"goauthentik.io/providers/oauth2/scope-",
) || false;
} else {
selected = Array.from(
this.instance?.propertyMappings,
).some((su) => {
return su == scope.pk;
});
}
return html`<option
value=${ifDefined(scope.pk)}
?selected=${selected}
>
${scope.name}
</option>`;
});
}
return html`<option value=${ifDefined(scope.pk)} ?selected=${selected}>${scope.name}</option>`;
});
}), html`<option>${t`Loading...`}</option>`)}
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
<p class="pf-c-form__helper-text">${t`Select which scopes can be used by the client. The client stil has to specify the scope to access the data.`}</p>
<p class="pf-c-form__helper-text">${t`Hold control/command to select multiple items.`}</p>
<p class="pf-c-form__helper-text">
${t`Select which scopes can be used by the client. The client stil has to specify the scope to access the data.`}
</p>
<p class="pf-c-form__helper-text">
${t`Hold control/command to select multiple items.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`RSA Key`}
name="rsaKey">
<ak-form-element-horizontal label=${t`RSA Key`} name="rsaKey">
<select class="pf-c-form-control">
<option value="" ?selected=${this.instance?.rsaKey === undefined}>---------</option>
${until(new CryptoApi(DEFAULT_CONFIG).cryptoCertificatekeypairsList({
ordering: "pk",
hasKey: true,
}).then(keys => {
return keys.results.map(key => {
let selected = this.instance?.rsaKey === key.pk;
if (keys.results.length === 1) {
selected = true;
}
return html`<option value=${ifDefined(key.pk)} ?selected=${selected}>${key.name}</option>`;
});
}), html`<option>${t`Loading...`}</option>`)}
<option value="" ?selected=${this.instance?.rsaKey === undefined}>
---------
</option>
${until(
new CryptoApi(DEFAULT_CONFIG)
.cryptoCertificatekeypairsList({
ordering: "pk",
hasKey: true,
})
.then((keys) => {
return keys.results.map((key) => {
let selected = this.instance?.rsaKey === key.pk;
if (keys.results.length === 1) {
selected = true;
}
return html`<option
value=${ifDefined(key.pk)}
?selected=${selected}
>
${key.name}
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
<p class="pf-c-form__helper-text">${t`Key used to sign the tokens. Only required when JWT Algorithm is set to RS256.`}</p>
<p class="pf-c-form__helper-text">
${t`Key used to sign the tokens. Only required when JWT Algorithm is set to RS256.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Subject mode`}
?required=${true}
name="subMode">
name="subMode"
>
<select class="pf-c-form-control">
<option value="${SubModeEnum.HashedUserId}" ?selected=${this.instance?.subMode === SubModeEnum.HashedUserId}>
<option
value="${SubModeEnum.HashedUserId}"
?selected=${this.instance?.subMode === SubModeEnum.HashedUserId}
>
${t`Based on the Hashed User ID`}
</option>
<option value="${SubModeEnum.UserUsername}" ?selected=${this.instance?.subMode === SubModeEnum.UserUsername}>
<option
value="${SubModeEnum.UserUsername}"
?selected=${this.instance?.subMode === SubModeEnum.UserUsername}
>
${t`Based on the username`}
</option>
<option value="${SubModeEnum.UserEmail}" ?selected=${this.instance?.subMode === SubModeEnum.UserEmail}>
<option
value="${SubModeEnum.UserEmail}"
?selected=${this.instance?.subMode === SubModeEnum.UserEmail}
>
${t`Based on the User's Email. This is recommended over the UPN method.`}
</option>
<option value="${SubModeEnum.UserUpn}" ?selected=${this.instance?.subMode === SubModeEnum.UserUpn}>
<option
value="${SubModeEnum.UserUpn}"
?selected=${this.instance?.subMode === SubModeEnum.UserUpn}
>
${t`Based on the User's UPN, only works if user has a 'upn' attribute set. Use this method only if you have different UPN and Mail domains.`}
</option>
</select>
@ -225,22 +346,36 @@ export class OAuth2ProviderFormPage extends ModelForm<OAuth2Provider, number> {
</ak-form-element-horizontal>
<ak-form-element-horizontal name="includeClaimsInIdToken">
<div class="pf-c-check">
<input type="checkbox" class="pf-c-check__input" ?checked=${first(this.instance?.includeClaimsInIdToken, true)}>
<input
type="checkbox"
class="pf-c-check__input"
?checked=${first(this.instance?.includeClaimsInIdToken, true)}
/>
<label class="pf-c-check__label">
${t`Include claims in id_token`}
</label>
</div>
<p class="pf-c-form__helper-text">${t`Include User claims from scopes in the id_token, for applications that don't access the userinfo endpoint.`}</p>
<p class="pf-c-form__helper-text">
${t`Include User claims from scopes in the id_token, for applications that don't access the userinfo endpoint.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Issuer mode`}
?required=${true}
name="issuerMode">
name="issuerMode"
>
<select class="pf-c-form-control">
<option value="${IssuerModeEnum.PerProvider}" ?selected=${this.instance?.issuerMode === IssuerModeEnum.PerProvider}>
<option
value="${IssuerModeEnum.PerProvider}"
?selected=${this.instance?.issuerMode ===
IssuerModeEnum.PerProvider}
>
${t`Each provider has a different issuer, based on the application slug.`}
</option>
<option value="${IssuerModeEnum.Global}" ?selected=${this.instance?.issuerMode === IssuerModeEnum.Global}>
<option
value="${IssuerModeEnum.Global}"
?selected=${this.instance?.issuerMode === IssuerModeEnum.Global}
>
${t`Same identifier is used for all providers`}
</option>
</select>
@ -252,5 +387,4 @@ export class OAuth2ProviderFormPage extends ModelForm<OAuth2Provider, number> {
</ak-form-group>
</form>`;
}
}

View File

@ -28,17 +28,16 @@ import { EVENT_REFRESH } from "../../../constants";
@customElement("ak-provider-oauth2-view")
export class OAuth2ProviderViewPage extends LitElement {
@property({type: Number})
@property({ type: Number })
set providerID(value: number) {
const api = new ProvidersApi(DEFAULT_CONFIG);
api.providersOauth2Retrieve({
id: value
id: value,
}).then((prov) => {
this.provider = prov;
});
api.providersOauth2SetupUrlsRetrieve({
id: value
id: value,
}).then((prov) => {
this.providerUrls = prov;
});
@ -51,7 +50,21 @@ export class OAuth2ProviderViewPage extends LitElement {
providerUrls?: OAuth2ProviderSetupURLs;
static get styles(): CSSResult[] {
return [PFBase, PFButton, PFPage, PFFlex, PFDisplay, PFGallery, PFContent, PFCard, PFDescriptionList, PFSizing, PFForm, PFFormControl, AKGlobal];
return [
PFBase,
PFButton,
PFPage,
PFFlex,
PFDisplay,
PFGallery,
PFContent,
PFCard,
PFDescriptionList,
PFSizing,
PFForm,
PFFormControl,
AKGlobal,
];
}
constructor() {
@ -67,137 +80,227 @@ export class OAuth2ProviderViewPage extends LitElement {
return html``;
}
return html`<ak-tabs>
<section slot="page-overview" data-tab-title="${t`Overview`}" class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-u-display-flex pf-u-justify-content-center">
<div class="pf-u-w-75">
<div class="pf-c-card">
<div class="pf-c-card__body">
<dl class="pf-c-description-list pf-m-2-col-on-lg">
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${t`Name`}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">${this.provider.name}</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${t`Assigned to application`}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
<ak-provider-related-application .provider=${this.provider}></ak-provider-related-application>
</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${t`Client type`}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">${convertToTitle(this.provider.clientType || "")}</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${t`Client ID`}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">${this.provider.clientId}</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${t`Redirect URIs`}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">${this.provider.redirectUris}</div>
</dd>
</div>
</dl>
</div>
<div class="pf-c-card__footer">
<ak-forms-modal>
<span slot="submit">
${t`Update`}
</span>
<span slot="header">
${t`Update OAuth2 Provider`}
</span>
<ak-provider-oauth2-form
slot="form"
.instancePk=${this.provider.pk || 0}>
</ak-provider-oauth2-form>
<button slot="trigger" class="pf-c-button pf-m-primary">
${t`Edit`}
</button>
</ak-forms-modal>
</div>
<section
slot="page-overview"
data-tab-title="${t`Overview`}"
class="pf-c-page__main-section pf-m-no-padding-mobile"
>
<div class="pf-u-display-flex pf-u-justify-content-center">
<div class="pf-u-w-75">
<div class="pf-c-card">
<div class="pf-c-card__body">
<dl class="pf-c-description-list pf-m-2-col-on-lg">
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text"
>${t`Name`}</span
>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
${this.provider.name}
</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text"
>${t`Assigned to application`}</span
>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
<ak-provider-related-application
.provider=${this.provider}
></ak-provider-related-application>
</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text"
>${t`Client type`}</span
>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
${convertToTitle(this.provider.clientType || "")}
</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text"
>${t`Client ID`}</span
>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
${this.provider.clientId}
</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text"
>${t`Redirect URIs`}</span
>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
${this.provider.redirectUris}
</div>
</dd>
</div>
</dl>
</div>
<div class="pf-c-card__footer">
<ak-forms-modal>
<span slot="submit"> ${t`Update`} </span>
<span slot="header"> ${t`Update OAuth2 Provider`} </span>
<ak-provider-oauth2-form
slot="form"
.instancePk=${this.provider.pk || 0}
>
</ak-provider-oauth2-form>
<button slot="trigger" class="pf-c-button pf-m-primary">
${t`Edit`}
</button>
</ak-forms-modal>
</div>
</div>
</div>
</section>
<section slot="page-changelog" data-tab-title="${t`Changelog`}" class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-c-card">
<div class="pf-c-card__body">
<ak-object-changelog
targetModelPk=${this.provider.pk || ""}
targetModelApp="authentik_providers_oauth2"
targetModelName="oauth2provider">
</ak-object-changelog>
</div>
</div>
</section>
<section
slot="page-changelog"
data-tab-title="${t`Changelog`}"
class="pf-c-page__main-section pf-m-no-padding-mobile"
>
<div class="pf-c-card">
<div class="pf-c-card__body">
<ak-object-changelog
targetModelPk=${this.provider.pk || ""}
targetModelApp="authentik_providers_oauth2"
targetModelName="oauth2provider"
>
</ak-object-changelog>
</div>
</section>
<section slot="page-metadata" data-tab-title="${t`Metadata`}" class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-u-display-flex pf-u-justify-content-center">
<div class="pf-u-w-75">
<div class="pf-c-card">
<div class="pf-c-card__body">
<form class="pf-c-form">
<div class="pf-c-form__group">
<label class="pf-c-form__label" for="help-text-simple-form-name">
<span class="pf-c-form__label-text">${t`OpenID Configuration URL`}</span>
</label>
<input class="pf-c-form-control" readonly type="text" value="${this.providerUrls?.providerInfo || "-"}" />
</div>
<div class="pf-c-form__group">
<label class="pf-c-form__label" for="help-text-simple-form-name">
<span class="pf-c-form__label-text">${t`OpenID Configuration Issuer`}</span>
</label>
<input class="pf-c-form-control" readonly type="text" value="${this.providerUrls?.issuer || "-"}" />
</div>
<hr>
<div class="pf-c-form__group">
<label class="pf-c-form__label" for="help-text-simple-form-name">
<span class="pf-c-form__label-text">${t`Authorize URL`}</span>
</label>
<input class="pf-c-form-control" readonly type="text" value="${this.providerUrls?.authorize || "-"}" />
</div>
<div class="pf-c-form__group">
<label class="pf-c-form__label" for="help-text-simple-form-name">
<span class="pf-c-form__label-text">${t`Token URL`}</span>
</label>
<input class="pf-c-form-control" readonly type="text" value="${this.providerUrls?.token || "-"}" />
</div>
<div class="pf-c-form__group">
<label class="pf-c-form__label" for="help-text-simple-form-name">
<span class="pf-c-form__label-text">${t`Userinfo URL`}</span>
</label>
<input class="pf-c-form-control" readonly type="text" value="${this.providerUrls?.userInfo || "-"}" />
</div>
<div class="pf-c-form__group">
<label class="pf-c-form__label" for="help-text-simple-form-name">
<span class="pf-c-form__label-text">${t`Logout URL`}</span>
</label>
<input class="pf-c-form-control" readonly type="text" value="${this.providerUrls?.logout || "-"}" />
</div>
</form>
</div>
</div>
</section>
<section
slot="page-metadata"
data-tab-title="${t`Metadata`}"
class="pf-c-page__main-section pf-m-no-padding-mobile"
>
<div class="pf-u-display-flex pf-u-justify-content-center">
<div class="pf-u-w-75">
<div class="pf-c-card">
<div class="pf-c-card__body">
<form class="pf-c-form">
<div class="pf-c-form__group">
<label
class="pf-c-form__label"
for="help-text-simple-form-name"
>
<span class="pf-c-form__label-text"
>${t`OpenID Configuration URL`}</span
>
</label>
<input
class="pf-c-form-control"
readonly
type="text"
value="${this.providerUrls?.providerInfo || "-"}"
/>
</div>
<div class="pf-c-form__group">
<label
class="pf-c-form__label"
for="help-text-simple-form-name"
>
<span class="pf-c-form__label-text"
>${t`OpenID Configuration Issuer`}</span
>
</label>
<input
class="pf-c-form-control"
readonly
type="text"
value="${this.providerUrls?.issuer || "-"}"
/>
</div>
<hr />
<div class="pf-c-form__group">
<label
class="pf-c-form__label"
for="help-text-simple-form-name"
>
<span class="pf-c-form__label-text"
>${t`Authorize URL`}</span
>
</label>
<input
class="pf-c-form-control"
readonly
type="text"
value="${this.providerUrls?.authorize || "-"}"
/>
</div>
<div class="pf-c-form__group">
<label
class="pf-c-form__label"
for="help-text-simple-form-name"
>
<span class="pf-c-form__label-text"
>${t`Token URL`}</span
>
</label>
<input
class="pf-c-form-control"
readonly
type="text"
value="${this.providerUrls?.token || "-"}"
/>
</div>
<div class="pf-c-form__group">
<label
class="pf-c-form__label"
for="help-text-simple-form-name"
>
<span class="pf-c-form__label-text"
>${t`Userinfo URL`}</span
>
</label>
<input
class="pf-c-form-control"
readonly
type="text"
value="${this.providerUrls?.userInfo || "-"}"
/>
</div>
<div class="pf-c-form__group">
<label
class="pf-c-form__label"
for="help-text-simple-form-name"
>
<span class="pf-c-form__label-text"
>${t`Logout URL`}</span
>
</label>
<input
class="pf-c-form-control"
readonly
type="text"
value="${this.providerUrls?.logout || "-"}"
/>
</div>
</form>
</div>
</div>
</div>
</section>
</ak-tabs>`;
</div>
</section>
</ak-tabs>`;
}
}

View File

@ -1,4 +1,11 @@
import { CryptoApi, FlowsApi, FlowsInstancesListDesignationEnum, ProvidersApi, ProxyMode, ProxyProvider } from "authentik-api";
import {
CryptoApi,
FlowsApi,
FlowsInstancesListDesignationEnum,
ProvidersApi,
ProxyMode,
ProxyProvider,
} from "authentik-api";
import { t } from "@lingui/macro";
import { css, CSSResult, customElement, property } from "lit-element";
import { html, TemplateResult } from "lit-html";
@ -15,29 +22,35 @@ import { first } from "../../../utils";
@customElement("ak-provider-proxy-form")
export class ProxyProviderFormPage extends ModelForm<ProxyProvider, number> {
static get styles(): CSSResult[] {
return super.styles.concat(PFToggleGroup, PFContent, PFSpacing, css`
.pf-c-toggle-group {
justify-content: center;
}
`);
return super.styles.concat(
PFToggleGroup,
PFContent,
PFSpacing,
css`
.pf-c-toggle-group {
justify-content: center;
}
`,
);
}
loadInstance(pk: number): Promise<ProxyProvider> {
return new ProvidersApi(DEFAULT_CONFIG).providersProxyRetrieve({
id: pk,
}).then(provider => {
this.showHttpBasic = first(provider.basicAuthEnabled, true);
this.mode = first(provider.mode, ProxyMode.Proxy);
return provider;
});
return new ProvidersApi(DEFAULT_CONFIG)
.providersProxyRetrieve({
id: pk,
})
.then((provider) => {
this.showHttpBasic = first(provider.basicAuthEnabled, true);
this.mode = first(provider.mode, ProxyMode.Proxy);
return provider;
});
}
@property({type: Boolean})
@property({ type: Boolean })
showHttpBasic = true;
@property({attribute: false})
@property({ attribute: false })
mode: ProxyMode = ProxyMode.Proxy;
getSuccessMessage(): string {
@ -53,11 +66,11 @@ export class ProxyProviderFormPage extends ModelForm<ProxyProvider, number> {
if (this.instance) {
return new ProvidersApi(DEFAULT_CONFIG).providersProxyUpdate({
id: this.instance.pk || 0,
proxyProviderRequest: data
proxyProviderRequest: data,
});
} else {
return new ProvidersApi(DEFAULT_CONFIG).providersProxyCreate({
proxyProviderRequest: data
proxyProviderRequest: data,
});
}
};
@ -68,40 +81,73 @@ export class ProxyProviderFormPage extends ModelForm<ProxyProvider, number> {
}
return html`<ak-form-element-horizontal
label=${t`HTTP-Basic Username Key`}
name="basicAuthUserAttribute">
<input type="text" value="${ifDefined(this.instance?.basicAuthUserAttribute)}" class="pf-c-form-control">
<p class="pf-c-form__helper-text">${t`User/Group Attribute used for the user part of the HTTP-Basic Header. If not set, the user's Email address is used.`}</p>
name="basicAuthUserAttribute"
>
<input
type="text"
value="${ifDefined(this.instance?.basicAuthUserAttribute)}"
class="pf-c-form-control"
/>
<p class="pf-c-form__helper-text">
${t`User/Group Attribute used for the user part of the HTTP-Basic Header. If not set, the user's Email address is used.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`HTTP-Basic Password Key`}
name="basicAuthPasswordAttribute">
<input type="text" value="${ifDefined(this.instance?.basicAuthPasswordAttribute)}" class="pf-c-form-control">
<p class="pf-c-form__helper-text">${t`User/Group Attribute used for the password part of the HTTP-Basic Header.`}</p>
name="basicAuthPasswordAttribute"
>
<input
type="text"
value="${ifDefined(this.instance?.basicAuthPasswordAttribute)}"
class="pf-c-form-control"
/>
<p class="pf-c-form__helper-text">
${t`User/Group Attribute used for the password part of the HTTP-Basic Header.`}
</p>
</ak-form-element-horizontal>`;
}
renderModeSelector(): TemplateResult {
return html`
<div class="pf-c-toggle-group__item">
<button class="pf-c-toggle-group__button ${this.mode === ProxyMode.Proxy ? "pf-m-selected" : ""}" type="button" @click=${() => {
this.mode = ProxyMode.Proxy;
}}>
return html` <div class="pf-c-toggle-group__item">
<button
class="pf-c-toggle-group__button ${this.mode === ProxyMode.Proxy
? "pf-m-selected"
: ""}"
type="button"
@click=${() => {
this.mode = ProxyMode.Proxy;
}}
>
<span class="pf-c-toggle-group__text">${t`Proxy`}</span>
</button>
</div>
<div class="pf-c-divider pf-m-vertical" role="separator"></div>
<div class="pf-c-toggle-group__item">
<button class="pf-c-toggle-group__button ${this.mode === ProxyMode.ForwardSingle ? "pf-m-selected" : ""}" type="button" @click=${() => {
this.mode = ProxyMode.ForwardSingle;
}}>
<span class="pf-c-toggle-group__text">${t`Forward auth (single application)`}</span>
<button
class="pf-c-toggle-group__button ${this.mode === ProxyMode.ForwardSingle
? "pf-m-selected"
: ""}"
type="button"
@click=${() => {
this.mode = ProxyMode.ForwardSingle;
}}
>
<span class="pf-c-toggle-group__text"
>${t`Forward auth (single application)`}</span
>
</button>
</div>
<div class="pf-c-divider pf-m-vertical" role="separator"></div>
<div class="pf-c-toggle-group__item">
<button class="pf-c-toggle-group__button ${this.mode === ProxyMode.ForwardDomain ? "pf-m-selected" : ""}" type="button" @click=${() => {
this.mode = ProxyMode.ForwardDomain;
}}>
<button
class="pf-c-toggle-group__button ${this.mode === ProxyMode.ForwardDomain
? "pf-m-selected"
: ""}"
type="button"
@click=${() => {
this.mode = ProxyMode.ForwardDomain;
}}
>
<span class="pf-c-toggle-group__text">${t`Forward auth (domain level)`}</span>
</button>
</div>`;
@ -110,146 +156,213 @@ export class ProxyProviderFormPage extends ModelForm<ProxyProvider, number> {
renderSettings(): TemplateResult {
switch (this.mode) {
case ProxyMode.Proxy:
return html`
<p class="pf-u-mb-xl">
return html` <p class="pf-u-mb-xl">
${t`This provider will behave like a transparent reverse-proxy, except requests must be authenticated. If your upstream application uses HTTPS, make sure to connect to the outpost using HTTPS as well.`}
</p>
<ak-form-element-horizontal
label=${t`External host`}
?required=${true}
name="externalHost">
<input type="text" value="${ifDefined(this.instance?.externalHost)}" class="pf-c-form-control" required>
<p class="pf-c-form__helper-text">${t`The external URL you'll access the application at. Include any non-standard port.`}</p>
name="externalHost"
>
<input
type="text"
value="${ifDefined(this.instance?.externalHost)}"
class="pf-c-form-control"
required
/>
<p class="pf-c-form__helper-text">
${t`The external URL you'll access the application at. Include any non-standard port.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Internal host`}
?required=${true}
name="internalHost">
<input type="text" value="${ifDefined(this.instance?.internalHost)}" class="pf-c-form-control" required>
<p class="pf-c-form__helper-text">${t`Upstream host that the requests are forwarded to.`}</p>
name="internalHost"
>
<input
type="text"
value="${ifDefined(this.instance?.internalHost)}"
class="pf-c-form-control"
required
/>
<p class="pf-c-form__helper-text">
${t`Upstream host that the requests are forwarded to.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal name="internalHostSslValidation">
<div class="pf-c-check">
<input type="checkbox" class="pf-c-check__input" ?checked=${first(this.instance?.internalHostSslValidation, true)}>
<input
type="checkbox"
class="pf-c-check__input"
?checked=${first(this.instance?.internalHostSslValidation, true)}
/>
<label class="pf-c-check__label">
${t`Internal host SSL Validation`}
</label>
</div>
<p class="pf-c-form__helper-text">${t`Validate SSL Certificates of upstream servers.`}</p>
<p class="pf-c-form__helper-text">
${t`Validate SSL Certificates of upstream servers.`}
</p>
</ak-form-element-horizontal>`;
case ProxyMode.ForwardSingle:
return html`
<p class="pf-u-mb-xl">
return html` <p class="pf-u-mb-xl">
${t`Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /akprox must be routed to the outpost (when using a manged outpost, this is done for you).`}
</p>
<ak-form-element-horizontal
label=${t`External host`}
?required=${true}
name="externalHost">
<input type="text" value="${ifDefined(this.instance?.externalHost)}" class="pf-c-form-control" required>
<p class="pf-c-form__helper-text">${t`The external URL you'll access the application at. Include any non-standard port.`}</p>
name="externalHost"
>
<input
type="text"
value="${ifDefined(this.instance?.externalHost)}"
class="pf-c-form-control"
required
/>
<p class="pf-c-form__helper-text">
${t`The external URL you'll access the application at. Include any non-standard port.`}
</p>
</ak-form-element-horizontal>`;
case ProxyMode.ForwardDomain:
return html`
<p class="pf-u-mb-xl">
return html` <p class="pf-u-mb-xl">
${t`Use this provider with nginx's auth_request or traefik's forwardAuth. Only a single provider is required per root domain. You can't do per-application authorization, but you don't have to create a provider for each application.`}
</p>
<ak-form-element-horizontal
label=${t`External host`}
?required=${true}
name="externalHost">
<input type="text" value="${first(this.instance?.externalHost, window.location.origin)}" class="pf-c-form-control" required>
<p class="pf-c-form__helper-text">${t`The external URL you'll authenticate at. Can be the same domain as authentik.`}</p>
name="externalHost"
>
<input
type="text"
value="${first(this.instance?.externalHost, window.location.origin)}"
class="pf-c-form-control"
required
/>
<p class="pf-c-form__helper-text">
${t`The external URL you'll authenticate at. Can be the same domain as authentik.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Cookie domain`}
name="cookieDomain">
<input type="text" value="${ifDefined(this.instance?.cookieDomain)}" class="pf-c-form-control" required>
<p class="pf-c-form__helper-text">${t`Optionally set this to your parent domain, if you want authentication and authorization to happen on a domain level. If you're running applications as app1.domain.tld, app2.domain.tld, set this to 'domain.tld'.`}</p>
<ak-form-element-horizontal label=${t`Cookie domain`} name="cookieDomain">
<input
type="text"
value="${ifDefined(this.instance?.cookieDomain)}"
class="pf-c-form-control"
required
/>
<p class="pf-c-form__helper-text">
${t`Optionally set this to your parent domain, if you want authentication and authorization to happen on a domain level. If you're running applications as app1.domain.tld, app2.domain.tld, set this to 'domain.tld'.`}
</p>
</ak-form-element-horizontal>`;
}
}
renderForm(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
<ak-form-element-horizontal
label=${t`Name`}
?required=${true}
name="name">
<input type="text" value="${ifDefined(this.instance?.name)}" class="pf-c-form-control" required>
<ak-form-element-horizontal label=${t`Name`} ?required=${true} name="name">
<input
type="text"
value="${ifDefined(this.instance?.name)}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Authorization flow`}
?required=${true}
name="authorizationFlow">
name="authorizationFlow"
>
<select class="pf-c-form-control">
${until(new FlowsApi(DEFAULT_CONFIG).flowsInstancesList({
ordering: "pk",
designation: FlowsInstancesListDesignationEnum.Authorization,
}).then(flows => {
return flows.results.map(flow => {
return html`<option value=${ifDefined(flow.pk)} ?selected=${this.instance?.authorizationFlow === flow.pk}>${flow.name} (${flow.slug})</option>`;
});
}), html`<option>${t`Loading...`}</option>`)}
${until(
new FlowsApi(DEFAULT_CONFIG)
.flowsInstancesList({
ordering: "pk",
designation: FlowsInstancesListDesignationEnum.Authorization,
})
.then((flows) => {
return flows.results.map((flow) => {
return html`<option
value=${ifDefined(flow.pk)}
?selected=${this.instance?.authorizationFlow === flow.pk}
>
${flow.name} (${flow.slug})
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
<p class="pf-c-form__helper-text">${t`Flow used when authorizing this provider.`}</p>
<p class="pf-c-form__helper-text">
${t`Flow used when authorizing this provider.`}
</p>
</ak-form-element-horizontal>
<div class="pf-c-card pf-m-selectable pf-m-selected">
<div class="pf-c-card__body">
<div class="pf-c-toggle-group">
${this.renderModeSelector()}
</div>
</div>
<div class="pf-c-card__footer">
${this.renderSettings()}
<div class="pf-c-toggle-group">${this.renderModeSelector()}</div>
</div>
<div class="pf-c-card__footer">${this.renderSettings()}</div>
</div>
<ak-form-group>
<span slot="header">
${t`Advanced protocol settings`}
</span>
<span slot="header"> ${t`Advanced protocol settings`} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${t`Certificate`}
name="certificate">
<ak-form-element-horizontal label=${t`Certificate`} name="certificate">
<select class="pf-c-form-control">
<option value="" ?selected=${this.instance?.certificate === undefined}>---------</option>
${until(new CryptoApi(DEFAULT_CONFIG).cryptoCertificatekeypairsList({
ordering: "pk",
hasKey: true,
}).then(keys => {
return keys.results.map(key => {
return html`<option value=${ifDefined(key.pk)} ?selected=${this.instance?.certificate === key.pk}>${key.name}</option>`;
});
}), html`<option>${t`Loading...`}</option>`)}
<option value="" ?selected=${this.instance?.certificate === undefined}>
---------
</option>
${until(
new CryptoApi(DEFAULT_CONFIG)
.cryptoCertificatekeypairsList({
ordering: "pk",
hasKey: true,
})
.then((keys) => {
return keys.results.map((key) => {
return html`<option
value=${ifDefined(key.pk)}
?selected=${this.instance?.certificate === key.pk}
>
${key.name}
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Skip path regex`}
name="skipPathRegex">
<textarea class="pf-c-form-control">${this.instance?.skipPathRegex}</textarea>
<p class="pf-c-form__helper-text">${t`Regular expressions for which authentication is not required. Each new line is interpreted as a new Regular Expression.`}</p>
<ak-form-element-horizontal label=${t`Skip path regex`} name="skipPathRegex">
<textarea class="pf-c-form-control">
${this.instance?.skipPathRegex}</textarea
>
<p class="pf-c-form__helper-text">
${t`Regular expressions for which authentication is not required. Each new line is interpreted as a new Regular Expression.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal name="basicAuthEnabled">
<div class="pf-c-check">
<input type="checkbox" class="pf-c-check__input" ?checked=${first(this.instance?.basicAuthEnabled, false)} @change=${(ev: Event) => {
const el = ev.target as HTMLInputElement;
this.showHttpBasic = el.checked;
}}>
<input
type="checkbox"
class="pf-c-check__input"
?checked=${first(this.instance?.basicAuthEnabled, false)}
@change=${(ev: Event) => {
const el = ev.target as HTMLInputElement;
this.showHttpBasic = el.checked;
}}
/>
<label class="pf-c-check__label">
${t`Set HTTP-Basic Authentication`}
</label>
</div>
<p class="pf-c-form__helper-text">${t`Set a custom HTTP-Basic Authentication header based on values from authentik.`}</p>
<p class="pf-c-form__helper-text">
${t`Set a custom HTTP-Basic Authentication header based on values from authentik.`}
</p>
</ak-form-element-horizontal>
${this.renderHttpBasic()}
</div>
</ak-form-group>
</form>`;
}
}

View File

@ -24,24 +24,35 @@ import { convertToTitle } from "../../../utils";
@customElement("ak-provider-proxy-view")
export class ProxyProviderViewPage extends LitElement {
@property()
set args(value: { [key: string]: number }) {
this.providerID = value.id;
}
@property({type: Number})
@property({ type: Number })
set providerID(value: number) {
new ProvidersApi(DEFAULT_CONFIG).providersProxyRetrieve({
id: value,
}).then((prov) => (this.provider = prov));
new ProvidersApi(DEFAULT_CONFIG)
.providersProxyRetrieve({
id: value,
})
.then((prov) => (this.provider = prov));
}
@property({ attribute: false })
provider?: ProxyProvider;
static get styles(): CSSResult[] {
return [PFBase, PFButton, PFPage, PFGrid, PFGallery, PFContent, PFCard, PFDescriptionList, AKGlobal];
return [
PFBase,
PFButton,
PFPage,
PFGrid,
PFGallery,
PFContent,
PFCard,
PFDescriptionList,
AKGlobal,
];
}
constructor() {
@ -57,128 +68,165 @@ export class ProxyProviderViewPage extends LitElement {
return html``;
}
return html`<ak-tabs>
<section slot="page-overview" data-tab-title="${t`Overview`}" class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-l-grid pf-m-gutter">
<div class="pf-l-grid__item pf-m-6-col">
<div class="pf-c-card">
<div class="pf-c-card__body">
<dl class="pf-c-description-list pf-m-3-col-on-lg">
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${t`Name`}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">${this.provider.name}</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${t`Assigned to application`}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
<ak-provider-related-application .provider=${this.provider}></ak-provider-related-application>
</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${t`Internal Host`}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">${this.provider.internalHost}</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${t`External Host`}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">${this.provider.externalHost}</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${t`Basic-Auth`}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
${this.provider.basicAuthEnabled ?
html`<span class="pf-c-button__icon pf-m-start">
<i class="fas fa-check-circle" aria-hidden="true"></i>&nbsp;
</span>${t`Yes`}`:
html`<span class="pf-c-button__icon pf-m-start">
<i class="fas fa-times-circle" aria-hidden="true"></i>&nbsp;
</span>${t`No`}`
}
</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${t`Mode`}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
${convertToTitle(this.provider.mode || "")}
</div>
</dd>
</div>
</dl>
</div>
<div class="pf-c-card__footer">
<ak-forms-modal>
<span slot="submit">
${t`Update`}
</span>
<span slot="header">
${t`Update Proxy Provider`}
</span>
<ak-provider-proxy-form
slot="form"
.instancePk=${this.provider.pk || 0}>
</ak-provider-proxy-form>
<button slot="trigger" class="pf-c-button pf-m-primary">
${t`Edit`}
</button>
</ak-forms-modal>
</div>
<section
slot="page-overview"
data-tab-title="${t`Overview`}"
class="pf-c-page__main-section pf-m-no-padding-mobile"
>
<div class="pf-l-grid pf-m-gutter">
<div class="pf-l-grid__item pf-m-6-col">
<div class="pf-c-card">
<div class="pf-c-card__body">
<dl class="pf-c-description-list pf-m-3-col-on-lg">
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text"
>${t`Name`}</span
>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
${this.provider.name}
</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text"
>${t`Assigned to application`}</span
>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
<ak-provider-related-application
.provider=${this.provider}
></ak-provider-related-application>
</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text"
>${t`Internal Host`}</span
>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
${this.provider.internalHost}
</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text"
>${t`External Host`}</span
>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
${this.provider.externalHost}
</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text"
>${t`Basic-Auth`}</span
>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
${this.provider.basicAuthEnabled
? html`<span
class="pf-c-button__icon pf-m-start"
>
<i
class="fas fa-check-circle"
aria-hidden="true"
></i
>&nbsp; </span
>${t`Yes`}`
: html`<span
class="pf-c-button__icon pf-m-start"
>
<i
class="fas fa-times-circle"
aria-hidden="true"
></i
>&nbsp; </span
>${t`No`}`}
</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text"
>${t`Mode`}</span
>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
${convertToTitle(this.provider.mode || "")}
</div>
</dd>
</div>
</dl>
</div>
</div>
<div class="pf-l-grid__item pf-m-6-col">
<div class="pf-c-card">
<div class="pf-c-card__title">
${t`Protocol Settings`}
</div>
<div class="pf-c-card__body">
<dl class="pf-c-description-list pf-m-3-col-on-lg">
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${t`Allowed Redirect URIs`}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
${this.provider.redirectUris}
</div>
</dd>
</div>
</dl>
</div>
<div class="pf-c-card__footer">
<ak-forms-modal>
<span slot="submit"> ${t`Update`} </span>
<span slot="header"> ${t`Update Proxy Provider`} </span>
<ak-provider-proxy-form
slot="form"
.instancePk=${this.provider.pk || 0}
>
</ak-provider-proxy-form>
<button slot="trigger" class="pf-c-button pf-m-primary">
${t`Edit`}
</button>
</ak-forms-modal>
</div>
</div>
</div>
</section>
<section slot="page-changelog" data-tab-title="${t`Changelog`}" class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-c-card">
<div class="pf-c-card__body">
<ak-object-changelog
targetModelPk=${this.provider.pk || ""}
targetModelApp="authentik_providers_proxy"
targetModelName="proxyprovider">
</ak-object-changelog>
<div class="pf-l-grid__item pf-m-6-col">
<div class="pf-c-card">
<div class="pf-c-card__title">${t`Protocol Settings`}</div>
<div class="pf-c-card__body">
<dl class="pf-c-description-list pf-m-3-col-on-lg">
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text"
>${t`Allowed Redirect URIs`}</span
>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
${this.provider.redirectUris}
</div>
</dd>
</div>
</dl>
</div>
</div>
</div>
</section>
</ak-tabs>`;
</div>
</section>
<section
slot="page-changelog"
data-tab-title="${t`Changelog`}"
class="pf-c-page__main-section pf-m-no-padding-mobile"
>
<div class="pf-c-card">
<div class="pf-c-card__body">
<ak-object-changelog
targetModelPk=${this.provider.pk || ""}
targetModelApp="authentik_providers_proxy"
targetModelName="proxyprovider"
>
</ak-object-changelog>
</div>
</div>
</section>
</ak-tabs>`;
}
}

View File

@ -1,4 +1,14 @@
import { CryptoApi, FlowsApi, SAMLProvider, ProvidersApi, PropertymappingsApi, SpBindingEnum, DigestAlgorithmEnum, SignatureAlgorithmEnum, FlowsInstancesListDesignationEnum } from "authentik-api";
import {
CryptoApi,
FlowsApi,
SAMLProvider,
ProvidersApi,
PropertymappingsApi,
SpBindingEnum,
DigestAlgorithmEnum,
SignatureAlgorithmEnum,
FlowsInstancesListDesignationEnum,
} from "authentik-api";
import { t } from "@lingui/macro";
import { customElement } from "lit-element";
import { html, TemplateResult } from "lit-html";
@ -11,7 +21,6 @@ import "../../../elements/forms/FormGroup";
@customElement("ak-provider-saml-form")
export class SAMLProviderFormPage extends ModelForm<SAMLProvider, number> {
loadInstance(pk: number): Promise<SAMLProvider> {
return new ProvidersApi(DEFAULT_CONFIG).providersSamlRetrieve({
id: pk,
@ -30,193 +39,331 @@ export class SAMLProviderFormPage extends ModelForm<SAMLProvider, number> {
if (this.instance) {
return new ProvidersApi(DEFAULT_CONFIG).providersSamlUpdate({
id: this.instance.pk || 0,
sAMLProviderRequest: data
sAMLProviderRequest: data,
});
} else {
return new ProvidersApi(DEFAULT_CONFIG).providersSamlCreate({
sAMLProviderRequest: data
sAMLProviderRequest: data,
});
}
};
renderForm(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
<ak-form-element-horizontal
label=${t`Name`}
?required=${true}
name="name">
<input type="text" value="${ifDefined(this.instance?.name)}" class="pf-c-form-control" required>
<ak-form-element-horizontal label=${t`Name`} ?required=${true} name="name">
<input
type="text"
value="${ifDefined(this.instance?.name)}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Authorization flow`}
?required=${true}
name="authorizationFlow">
name="authorizationFlow"
>
<select class="pf-c-form-control">
${until(new FlowsApi(DEFAULT_CONFIG).flowsInstancesList({
ordering: "pk",
designation: FlowsInstancesListDesignationEnum.Authorization,
}).then(flows => {
return flows.results.map(flow => {
return html`<option value=${ifDefined(flow.pk)} ?selected=${this.instance?.authorizationFlow === flow.pk}>${flow.name} (${flow.slug})</option>`;
});
}), html`<option>${t`Loading...`}</option>`)}
${until(
new FlowsApi(DEFAULT_CONFIG)
.flowsInstancesList({
ordering: "pk",
designation: FlowsInstancesListDesignationEnum.Authorization,
})
.then((flows) => {
return flows.results.map((flow) => {
return html`<option
value=${ifDefined(flow.pk)}
?selected=${this.instance?.authorizationFlow === flow.pk}
>
${flow.name} (${flow.slug})
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
<p class="pf-c-form__helper-text">${t`Flow used when authorizing this provider.`}</p>
<p class="pf-c-form__helper-text">
${t`Flow used when authorizing this provider.`}
</p>
</ak-form-element-horizontal>
<ak-form-group .expanded=${true}>
<span slot="header">
${t`Protocol settings`}
</span>
<span slot="header"> ${t`Protocol settings`} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${t`ACS URL`}
?required=${true}
name="acsUrl">
<input type="text" value="${ifDefined(this.instance?.acsUrl)}" class="pf-c-form-control" required>
<ak-form-element-horizontal label=${t`ACS URL`} ?required=${true} name="acsUrl">
<input
type="text"
value="${ifDefined(this.instance?.acsUrl)}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Issuer`}
?required=${true}
name="issuer">
<input type="text" value="${this.instance?.issuer || "authentik"}" class="pf-c-form-control" required>
<ak-form-element-horizontal label=${t`Issuer`} ?required=${true} name="issuer">
<input
type="text"
value="${this.instance?.issuer || "authentik"}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Service Provider Binding`}
?required=${true}
name="spBinding">
name="spBinding"
>
<select class="pf-c-form-control">
<option value=${SpBindingEnum.Redirect} ?selected=${this.instance?.spBinding === SpBindingEnum.Redirect}>
<option
value=${SpBindingEnum.Redirect}
?selected=${this.instance?.spBinding === SpBindingEnum.Redirect}
>
${t`Redirect`}
</option>
<option value=${SpBindingEnum.Post} ?selected=${this.instance?.spBinding === SpBindingEnum.Post}>
<option
value=${SpBindingEnum.Post}
?selected=${this.instance?.spBinding === SpBindingEnum.Post}
>
${t`Post`}
</option>
</select>
<p class="pf-c-form__helper-text">${t`Determines how authentik sends the response back to the Service Provider.`}</p>
<p class="pf-c-form__helper-text">
${t`Determines how authentik sends the response back to the Service Provider.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Audience`}
name="audience">
<input type="text" value="${ifDefined(this.instance?.audience)}" class="pf-c-form-control">
<ak-form-element-horizontal label=${t`Audience`} name="audience">
<input
type="text"
value="${ifDefined(this.instance?.audience)}"
class="pf-c-form-control"
/>
</ak-form-element-horizontal>
</div>
</ak-form-group>
<ak-form-group>
<span slot="header">
${t`Advanced protocol settings`}
</span>
<span slot="header"> ${t`Advanced protocol settings`} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${t`Signing Certificate`}
name="signingKp">
<ak-form-element-horizontal label=${t`Signing Certificate`} name="signingKp">
<select class="pf-c-form-control">
<option value="" ?selected=${this.instance?.signingKp === undefined}>---------</option>
${until(new CryptoApi(DEFAULT_CONFIG).cryptoCertificatekeypairsList({
ordering: "pk",
hasKey: true,
}).then(keys => {
return keys.results.map(key => {
return html`<option value=${ifDefined(key.pk)} ?selected=${this.instance?.signingKp === key.pk}>${key.name}</option>`;
});
}), html`<option>${t`Loading...`}</option>`)}
<option value="" ?selected=${this.instance?.signingKp === undefined}>
---------
</option>
${until(
new CryptoApi(DEFAULT_CONFIG)
.cryptoCertificatekeypairsList({
ordering: "pk",
hasKey: true,
})
.then((keys) => {
return keys.results.map((key) => {
return html`<option
value=${ifDefined(key.pk)}
?selected=${this.instance?.signingKp === key.pk}
>
${key.name}
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
<p class="pf-c-form__helper-text">${t`Certificate used to sign outgoing Responses going to the Service Provider.`}</p>
<p class="pf-c-form__helper-text">
${t`Certificate used to sign outgoing Responses going to the Service Provider.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Verification Certificate`}
name="verificationKp">
name="verificationKp"
>
<select class="pf-c-form-control">
<option value="" ?selected=${this.instance?.verificationKp === undefined}>---------</option>
${until(new CryptoApi(DEFAULT_CONFIG).cryptoCertificatekeypairsList({
ordering: "pk",
}).then(keys => {
return keys.results.map(key => {
return html`<option value=${ifDefined(key.pk)} ?selected=${this.instance?.verificationKp === key.pk}>${key.name}</option>`;
});
}), html`<option>${t`Loading...`}</option>`)}
<option
value=""
?selected=${this.instance?.verificationKp === undefined}
>
---------
</option>
${until(
new CryptoApi(DEFAULT_CONFIG)
.cryptoCertificatekeypairsList({
ordering: "pk",
})
.then((keys) => {
return keys.results.map((key) => {
return html`<option
value=${ifDefined(key.pk)}
?selected=${this.instance?.verificationKp ===
key.pk}
>
${key.name}
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
<p class="pf-c-form__helper-text">${t`When selected, incoming assertion's Signatures will be validated against this certificate. To allow unsigned Requests, leave on default.`}</p>
<p class="pf-c-form__helper-text">
${t`When selected, incoming assertion's Signatures will be validated against this certificate. To allow unsigned Requests, leave on default.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Property mappings`}
?required=${true}
name="propertyMappings">
name="propertyMappings"
>
<select class="pf-c-form-control" multiple>
${until(new PropertymappingsApi(DEFAULT_CONFIG).propertymappingsSamlList({
ordering: "saml_name"
}).then(mappings => {
return mappings.results.map(mapping => {
let selected = false;
if (!this.instance?.propertyMappings) {
selected = mapping.managed?.startsWith("goauthentik.io/providers/saml") || false;
} else {
selected = Array.from(this.instance?.propertyMappings).some(su => {
return su == mapping.pk;
${until(
new PropertymappingsApi(DEFAULT_CONFIG)
.propertymappingsSamlList({
ordering: "saml_name",
})
.then((mappings) => {
return mappings.results.map((mapping) => {
let selected = false;
if (!this.instance?.propertyMappings) {
selected =
mapping.managed?.startsWith(
"goauthentik.io/providers/saml",
) || false;
} else {
selected = Array.from(
this.instance?.propertyMappings,
).some((su) => {
return su == mapping.pk;
});
}
return html`<option
value=${ifDefined(mapping.pk)}
?selected=${selected}
>
${mapping.name}
</option>`;
});
}
return html`<option value=${ifDefined(mapping.pk)} ?selected=${selected}>${mapping.name}</option>`;
});
}), html`<option>${t`Loading...`}</option>`)}
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
<p class="pf-c-form__helper-text">${t`Hold control/command to select multiple items.`}</p>
<p class="pf-c-form__helper-text">
${t`Hold control/command to select multiple items.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`NameID Property Mapping`}
name="nameIdMapping">
name="nameIdMapping"
>
<select class="pf-c-form-control">
<option value="" ?selected=${this.instance?.nameIdMapping === undefined}>---------</option>
${until(new PropertymappingsApi(DEFAULT_CONFIG).propertymappingsSamlList({
ordering: "saml_name"
}).then(mappings => {
return mappings.results.map(mapping => {
return html`<option value=${ifDefined(mapping.pk)} ?selected=${this.instance?.nameIdMapping === mapping.pk}>${mapping.name}</option>`;
});
}), html`<option>${t`Loading...`}</option>`)}
<option
value=""
?selected=${this.instance?.nameIdMapping === undefined}
>
---------
</option>
${until(
new PropertymappingsApi(DEFAULT_CONFIG)
.propertymappingsSamlList({
ordering: "saml_name",
})
.then((mappings) => {
return mappings.results.map((mapping) => {
return html`<option
value=${ifDefined(mapping.pk)}
?selected=${this.instance?.nameIdMapping ===
mapping.pk}
>
${mapping.name}
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
<p class="pf-c-form__helper-text">${t`Configure how the NameID value will be created. When left empty, the NameIDPolicy of the incoming request will be respected.`}</p>
<p class="pf-c-form__helper-text">
${t`Configure how the NameID value will be created. When left empty, the NameIDPolicy of the incoming request will be respected.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Assertion valid not before`}
?required=${true}
name="assertionValidNotBefore">
<input type="text" value="${this.instance?.assertionValidNotBefore || "minutes=-5"}" class="pf-c-form-control" required>
<p class="pf-c-form__helper-text">${t`Configure the maximum allowed time drift for an asseration.`}</p>
<p class="pf-c-form__helper-text">${t`(Format: hours=-1;minutes=-2;seconds=-3).`}</p>
name="assertionValidNotBefore"
>
<input
type="text"
value="${this.instance?.assertionValidNotBefore || "minutes=-5"}"
class="pf-c-form-control"
required
/>
<p class="pf-c-form__helper-text">
${t`Configure the maximum allowed time drift for an asseration.`}
</p>
<p class="pf-c-form__helper-text">
${t`(Format: hours=-1;minutes=-2;seconds=-3).`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Assertion valid not on or after`}
?required=${true}
name="assertionValidNotOnOrAfter">
<input type="text" value="${this.instance?.assertionValidNotOnOrAfter || "minutes=5"}" class="pf-c-form-control" required>
<p class="pf-c-form__helper-text">${t`Assertion not valid on or after current time + this value (Format: hours=1;minutes=2;seconds=3).`}</p>
name="assertionValidNotOnOrAfter"
>
<input
type="text"
value="${this.instance?.assertionValidNotOnOrAfter || "minutes=5"}"
class="pf-c-form-control"
required
/>
<p class="pf-c-form__helper-text">
${t`Assertion not valid on or after current time + this value (Format: hours=1;minutes=2;seconds=3).`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Session valid not on or after`}
?required=${true}
name="sessionValidNotOnOrAfter">
<input type="text" value="${this.instance?.sessionValidNotOnOrAfter || "minutes=86400"}" class="pf-c-form-control" required>
<p class="pf-c-form__helper-text">${t`Session not valid on or after current time + this value (Format: hours=1;minutes=2;seconds=3).`}</p>
name="sessionValidNotOnOrAfter"
>
<input
type="text"
value="${this.instance?.sessionValidNotOnOrAfter || "minutes=86400"}"
class="pf-c-form-control"
required
/>
<p class="pf-c-form__helper-text">
${t`Session not valid on or after current time + this value (Format: hours=1;minutes=2;seconds=3).`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Digest algorithm`}
?required=${true}
name="digestAlgorithm">
name="digestAlgorithm"
>
<select class="pf-c-form-control">
<option value=${DigestAlgorithmEnum._200009Xmldsigsha1} ?selected=${this.instance?.digestAlgorithm === DigestAlgorithmEnum._200009Xmldsigsha1}>
<option
value=${DigestAlgorithmEnum._200009Xmldsigsha1}
?selected=${this.instance?.digestAlgorithm ===
DigestAlgorithmEnum._200009Xmldsigsha1}
>
${t`SHA1`}
</option>
<option value=${DigestAlgorithmEnum._200104Xmlencsha256} ?selected=${this.instance?.digestAlgorithm === DigestAlgorithmEnum._200104Xmlencsha256 || this.instance?.digestAlgorithm === undefined}>
<option
value=${DigestAlgorithmEnum._200104Xmlencsha256}
?selected=${this.instance?.digestAlgorithm ===
DigestAlgorithmEnum._200104Xmlencsha256 ||
this.instance?.digestAlgorithm === undefined}
>
${t`SHA256`}
</option>
<option value=${DigestAlgorithmEnum._200104XmldsigMoresha384} ?selected=${this.instance?.digestAlgorithm === DigestAlgorithmEnum._200104XmldsigMoresha384}>
<option
value=${DigestAlgorithmEnum._200104XmldsigMoresha384}
?selected=${this.instance?.digestAlgorithm ===
DigestAlgorithmEnum._200104XmldsigMoresha384}
>
${t`SHA384`}
</option>
<option value=${DigestAlgorithmEnum._200104Xmlencsha512} ?selected=${this.instance?.digestAlgorithm === DigestAlgorithmEnum._200104Xmlencsha512}>
<option
value=${DigestAlgorithmEnum._200104Xmlencsha512}
?selected=${this.instance?.digestAlgorithm ===
DigestAlgorithmEnum._200104Xmlencsha512}
>
${t`SHA512`}
</option>
</select>
@ -224,21 +371,43 @@ export class SAMLProviderFormPage extends ModelForm<SAMLProvider, number> {
<ak-form-element-horizontal
label=${t`Signature algorithm`}
?required=${true}
name="signatureAlgorithm">
name="signatureAlgorithm"
>
<select class="pf-c-form-control">
<option value=${SignatureAlgorithmEnum._200009XmldsigrsaSha1} ?selected=${this.instance?.signatureAlgorithm === SignatureAlgorithmEnum._200009XmldsigrsaSha1}>
<option
value=${SignatureAlgorithmEnum._200009XmldsigrsaSha1}
?selected=${this.instance?.signatureAlgorithm ===
SignatureAlgorithmEnum._200009XmldsigrsaSha1}
>
${t`RSA-SHA1`}
</option>
<option value=${SignatureAlgorithmEnum._200104XmldsigMorersaSha256} ?selected=${this.instance?.signatureAlgorithm === SignatureAlgorithmEnum._200104XmldsigMorersaSha256 || this.instance?.signatureAlgorithm === undefined}>
<option
value=${SignatureAlgorithmEnum._200104XmldsigMorersaSha256}
?selected=${this.instance?.signatureAlgorithm ===
SignatureAlgorithmEnum._200104XmldsigMorersaSha256 ||
this.instance?.signatureAlgorithm === undefined}
>
${t`RSA-SHA256`}
</option>
<option value=${SignatureAlgorithmEnum._200104XmldsigMorersaSha384} ?selected=${this.instance?.signatureAlgorithm === SignatureAlgorithmEnum._200104XmldsigMorersaSha384}>
<option
value=${SignatureAlgorithmEnum._200104XmldsigMorersaSha384}
?selected=${this.instance?.signatureAlgorithm ===
SignatureAlgorithmEnum._200104XmldsigMorersaSha384}
>
${t`RSA-SHA384`}
</option>
<option value=${SignatureAlgorithmEnum._200104XmldsigMorersaSha512} ?selected=${this.instance?.signatureAlgorithm === SignatureAlgorithmEnum._200104XmldsigMorersaSha512}>
<option
value=${SignatureAlgorithmEnum._200104XmldsigMorersaSha512}
?selected=${this.instance?.signatureAlgorithm ===
SignatureAlgorithmEnum._200104XmldsigMorersaSha512}
>
${t`RSA-SHA512`}
</option>
<option value=${SignatureAlgorithmEnum._200009XmldsigdsaSha1} ?selected=${this.instance?.signatureAlgorithm === SignatureAlgorithmEnum._200009XmldsigdsaSha1}>
<option
value=${SignatureAlgorithmEnum._200009XmldsigdsaSha1}
?selected=${this.instance?.signatureAlgorithm ===
SignatureAlgorithmEnum._200009XmldsigdsaSha1}
>
${t`DSA-SHA1`}
</option>
</select>
@ -247,5 +416,4 @@ export class SAMLProviderFormPage extends ModelForm<SAMLProvider, number> {
</ak-form-group>
</form>`;
}
}

View File

@ -1,4 +1,9 @@
import { FlowsApi, FlowsInstancesListDesignationEnum, ProvidersApi, SAMLProvider } from "authentik-api";
import {
FlowsApi,
FlowsInstancesListDesignationEnum,
ProvidersApi,
SAMLProvider,
} from "authentik-api";
import { t } from "@lingui/macro";
import { customElement } from "lit-element";
import { html, TemplateResult } from "lit-html";
@ -9,7 +14,6 @@ import "../../../elements/forms/HorizontalFormElement";
@customElement("ak-provider-saml-import-form")
export class SAMLProviderImportForm extends Form<SAMLProvider> {
getSuccessMessage(): string {
return t`Successfully imported provider.`;
}
@ -29,35 +33,39 @@ export class SAMLProviderImportForm extends Form<SAMLProvider> {
renderForm(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
<ak-form-element-horizontal
label=${t`Name`}
?required=${true}
name="name">
<input type="text" class="pf-c-form-control" required>
<ak-form-element-horizontal label=${t`Name`} ?required=${true} name="name">
<input type="text" class="pf-c-form-control" required />
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Authorization flow`}
?required=${true}
name="authorizationFlow">
name="authorizationFlow"
>
<select class="pf-c-form-control">
${until(new FlowsApi(DEFAULT_CONFIG).flowsInstancesList({
ordering: "pk",
designation: FlowsInstancesListDesignationEnum.Authorization,
}).then(flows => {
return flows.results.map(flow => {
return html`<option value=${flow.slug}>${flow.name} (${flow.slug})</option>`;
});
}), html`<option>${t`Loading...`}</option>`)}
${until(
new FlowsApi(DEFAULT_CONFIG)
.flowsInstancesList({
ordering: "pk",
designation: FlowsInstancesListDesignationEnum.Authorization,
})
.then((flows) => {
return flows.results.map((flow) => {
return html`<option value=${flow.slug}>
${flow.name} (${flow.slug})
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
<p class="pf-c-form__helper-text">${t`Flow used when authorizing this provider.`}</p>
<p class="pf-c-form__helper-text">
${t`Flow used when authorizing this provider.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Metadata`}
name="flow">
<input type="file" value="" class="pf-c-form-control">
<ak-form-element-horizontal label=${t`Metadata`} name="flow">
<input type="file" value="" class="pf-c-form-control" />
</ak-form-element-horizontal>
</form>`;
}
}

View File

@ -28,24 +28,37 @@ import { ifDefined } from "lit-html/directives/if-defined";
@customElement("ak-provider-saml-view")
export class SAMLProviderViewPage extends LitElement {
@property()
set args(value: { [key: string]: number }) {
this.providerID = value.id;
}
@property({type: Number})
@property({ type: Number })
set providerID(value: number) {
new ProvidersApi(DEFAULT_CONFIG).providersSamlRetrieve({
id: value,
}).then((prov) => (this.provider = prov));
new ProvidersApi(DEFAULT_CONFIG)
.providersSamlRetrieve({
id: value,
})
.then((prov) => (this.provider = prov));
}
@property({ attribute: false })
provider?: SAMLProvider;
static get styles(): CSSResult[] {
return [PFBase, PFPage, PFButton, PFFlex, PFDisplay, PFGallery, PFContent, PFCard, PFDescriptionList, PFSizing, AKGlobal];
return [
PFBase,
PFPage,
PFButton,
PFFlex,
PFDisplay,
PFGallery,
PFContent,
PFCard,
PFDescriptionList,
PFSizing,
AKGlobal,
];
}
constructor() {
@ -61,118 +74,163 @@ export class SAMLProviderViewPage extends LitElement {
return html``;
}
return html`<ak-tabs>
<section slot="page-overview" data-tab-title="${t`Overview`}" class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-u-display-flex pf-u-justify-content-center">
<div class="pf-u-w-75">
<div class="pf-c-card">
<div class="pf-c-card__body">
<dl class="pf-c-description-list pf-m-3-col-on-lg">
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${t`Name`}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">${this.provider.name}</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${t`Assigned to application`}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
<ak-provider-related-application .provider=${this.provider}></ak-provider-related-application>
</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${t`ACS URL`}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">${this.provider.acsUrl}</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${t`Audience`}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">${this.provider.audience}</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${t`Issuer`}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">${this.provider.issuer}</div>
</dd>
</div>
</dl>
</div>
<div class="pf-c-card__footer">
<ak-forms-modal>
<span slot="submit">
${t`Update`}
</span>
<span slot="header">
${t`Update SAML Provider`}
</span>
<ak-provider-saml-form
slot="form"
.instancePk=${this.provider.pk || 0}>
</ak-provider-saml-form>
<button slot="trigger" class="pf-c-button pf-m-primary">
${t`Edit`}
</button>
</ak-forms-modal>
</div>
<section
slot="page-overview"
data-tab-title="${t`Overview`}"
class="pf-c-page__main-section pf-m-no-padding-mobile"
>
<div class="pf-u-display-flex pf-u-justify-content-center">
<div class="pf-u-w-75">
<div class="pf-c-card">
<div class="pf-c-card__body">
<dl class="pf-c-description-list pf-m-3-col-on-lg">
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text"
>${t`Name`}</span
>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
${this.provider.name}
</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text"
>${t`Assigned to application`}</span
>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
<ak-provider-related-application
.provider=${this.provider}
></ak-provider-related-application>
</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text"
>${t`ACS URL`}</span
>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
${this.provider.acsUrl}
</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text"
>${t`Audience`}</span
>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
${this.provider.audience}
</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text"
>${t`Issuer`}</span
>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
${this.provider.issuer}
</div>
</dd>
</div>
</dl>
</div>
<div class="pf-c-card__footer">
<ak-forms-modal>
<span slot="submit"> ${t`Update`} </span>
<span slot="header"> ${t`Update SAML Provider`} </span>
<ak-provider-saml-form
slot="form"
.instancePk=${this.provider.pk || 0}
>
</ak-provider-saml-form>
<button slot="trigger" class="pf-c-button pf-m-primary">
${t`Edit`}
</button>
</ak-forms-modal>
</div>
</div>
</div>
</section>
<section slot="page-changelog" data-tab-title="${t`Changelog`}" class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-c-card">
<div class="pf-c-card__body">
<ak-object-changelog
targetModelPk=${this.provider.pk || ""}
targetModelApp="authentik_providers_saml"
targetModelName="samlprovider">
</ak-object-changelog>
</div>
</div>
</section>
<section
slot="page-changelog"
data-tab-title="${t`Changelog`}"
class="pf-c-page__main-section pf-m-no-padding-mobile"
>
<div class="pf-c-card">
<div class="pf-c-card__body">
<ak-object-changelog
targetModelPk=${this.provider.pk || ""}
targetModelApp="authentik_providers_saml"
targetModelName="samlprovider"
>
</ak-object-changelog>
</div>
</section>
${this.provider.assignedApplicationName ? html`
<section slot="page-metadata" data-tab-title="${t`Metadata`}" class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-u-display-flex pf-u-justify-content-center">
<div class="pf-u-w-75">
<div class="pf-c-card">
<div class="pf-c-card__body">
${until(new ProvidersApi(DEFAULT_CONFIG).providersSamlMetadataRetrieve({
id: this.provider.pk || 0,
}).then(m => {
return html`<ak-codemirror mode="xml" ?readOnly=${true} value="${ifDefined(m.metadata)}"></ak-codemirror>`;
}))}
</div>
<div class="pf-c-card__footer">
<a class="pf-c-button pf-m-primary" target="_blank"
href=${this.provider.metadataDownloadUrl}>
${t`Download`}
</a>
<ak-action-button
class="pf-m-secondary"
.apiRequest=${() => {
const fullUrl = window.location.origin + this.provider?.metadataDownloadUrl;
return navigator.clipboard.writeText(fullUrl);
}}>
${t`Copy download URL`}
</ak-action-button>
</div>
</div>
</div>
</div>
</section>` : html``}
</ak-tabs>`;
</div>
</section>
${this.provider.assignedApplicationName
? html` <section
slot="page-metadata"
data-tab-title="${t`Metadata`}"
class="pf-c-page__main-section pf-m-no-padding-mobile"
>
<div class="pf-u-display-flex pf-u-justify-content-center">
<div class="pf-u-w-75">
<div class="pf-c-card">
<div class="pf-c-card__body">
${until(
new ProvidersApi(DEFAULT_CONFIG)
.providersSamlMetadataRetrieve({
id: this.provider.pk || 0,
})
.then((m) => {
return html`<ak-codemirror
mode="xml"
?readOnly=${true}
value="${ifDefined(m.metadata)}"
></ak-codemirror>`;
}),
)}
</div>
<div class="pf-c-card__footer">
<a
class="pf-c-button pf-m-primary"
target="_blank"
href=${this.provider.metadataDownloadUrl}
>
${t`Download`}
</a>
<ak-action-button
class="pf-m-secondary"
.apiRequest=${() => {
const fullUrl =
window.location.origin +
this.provider?.metadataDownloadUrl;
return navigator.clipboard.writeText(fullUrl);
}}
>
${t`Copy download URL`}
</ak-action-button>
</div>
</div>
</div>
</div>
</section>`
: html``}
</ak-tabs>`;
}
}

View File

@ -14,14 +14,15 @@ import { ifDefined } from "lit-html/directives/if-defined";
@customElement("ak-source-view")
export class SourceViewPage extends LitElement {
@property({ type: String })
set sourceSlug(slug: string) {
new SourcesApi(DEFAULT_CONFIG).sourcesAllRetrieve({
slug: slug
}).then((source) => {
this.source = source;
});
new SourcesApi(DEFAULT_CONFIG)
.sourcesAllRetrieve({
slug: slug,
})
.then((source) => {
this.source = source;
});
}
@property({ attribute: false })
@ -33,13 +34,21 @@ export class SourceViewPage extends LitElement {
}
switch (this.source?.component) {
case "ak-source-ldap-form":
return html`<ak-source-ldap-view sourceSlug=${this.source.slug}></ak-source-ldap-view>`;
return html`<ak-source-ldap-view
sourceSlug=${this.source.slug}
></ak-source-ldap-view>`;
case "ak-source-oauth-form":
return html`<ak-source-oauth-view sourceSlug=${this.source.slug}></ak-source-oauth-view>`;
return html`<ak-source-oauth-view
sourceSlug=${this.source.slug}
></ak-source-oauth-view>`;
case "ak-source-saml-form":
return html`<ak-source-saml-view sourceSlug=${this.source.slug}></ak-source-saml-view>`;
return html`<ak-source-saml-view
sourceSlug=${this.source.slug}
></ak-source-saml-view>`;
case "ak-source-plex-form":
return html`<ak-source-plex-view sourceSlug=${this.source.slug}></ak-source-plex-view>`;
return html`<ak-source-plex-view
sourceSlug=${this.source.slug}
></ak-source-plex-view>`;
default:
return html`<p>Invalid source type ${this.source.component}</p>`;
}
@ -47,10 +56,11 @@ export class SourceViewPage extends LitElement {
render(): TemplateResult {
return html`<ak-page-header
icon="pf-icon pf-icon-middleware"
header=${ifDefined(this.source?.name)}
description=${ifDefined(this.source?.verboseName)}>
</ak-page-header>
${this.renderSource()}`;
icon="pf-icon pf-icon-middleware"
header=${ifDefined(this.source?.name)}
description=${ifDefined(this.source?.verboseName)}
>
</ak-page-header>
${this.renderSource()}`;
}
}

View File

@ -47,11 +47,7 @@ export class SourceListPage extends TablePage<Source> {
}
columns(): TableColumn[] {
return [
new TableColumn(t`Name`, "name"),
new TableColumn(t`Type`),
new TableColumn(""),
];
return [new TableColumn(t`Name`, "name"), new TableColumn(t`Type`), new TableColumn("")];
}
row(item: Source): TemplateResult[] {
@ -64,42 +60,35 @@ export class SourceListPage extends TablePage<Source> {
${item.enabled ? html`` : html`<small>${t`Disabled`}</small>`}
</a>`,
html`${item.verboseName}`,
html`
<ak-forms-modal>
<span slot="submit">
${t`Update`}
</span>
<span slot="header">
${t`Update ${item.verboseName}`}
</span>
<ak-proxy-form
slot="form"
.args=${{
"instancePk": item.slug
html` <ak-forms-modal>
<span slot="submit"> ${t`Update`} </span>
<span slot="header"> ${t`Update ${item.verboseName}`} </span>
<ak-proxy-form
slot="form"
.args=${{
instancePk: item.slug,
}}
type=${ifDefined(item.component)}
>
</ak-proxy-form>
<button slot="trigger" class="pf-c-button pf-m-secondary">${t`Edit`}</button>
</ak-forms-modal>
<ak-forms-delete
.obj=${item}
objectLabel=${t`Source`}
.usedBy=${() => {
return new SourcesApi(DEFAULT_CONFIG).sourcesAllUsedByList({
slug: item.slug,
});
}}
type=${ifDefined(item.component)}>
</ak-proxy-form>
<button slot="trigger" class="pf-c-button pf-m-secondary">
${t`Edit`}
</button>
</ak-forms-modal>
<ak-forms-delete
.obj=${item}
objectLabel=${t`Source`}
.usedBy=${() => {
return new SourcesApi(DEFAULT_CONFIG).sourcesAllUsedByList({
slug: item.slug
});
}}
.delete=${() => {
return new SourcesApi(DEFAULT_CONFIG).sourcesAllDestroy({
slug: item.slug
});
}}>
<button slot="trigger" class="pf-c-button pf-m-danger">
${t`Delete`}
</button>
</ak-forms-delete>`,
.delete=${() => {
return new SourcesApi(DEFAULT_CONFIG).sourcesAllDestroy({
slug: item.slug,
});
}}
>
<button slot="trigger" class="pf-c-button pf-m-danger">${t`Delete`}</button>
</ak-forms-delete>`,
];
}
@ -115,41 +104,39 @@ export class SourceListPage extends TablePage<Source> {
}
renderToolbar(): TemplateResult {
return html`
<ak-dropdown class="pf-c-dropdown">
<button class="pf-m-primary pf-c-dropdown__toggle" type="button">
<span class="pf-c-dropdown__toggle-text">${t`Create`}</span>
<i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i>
</button>
<ul class="pf-c-dropdown__menu" hidden>
${until(new SourcesApi(DEFAULT_CONFIG).sourcesAllTypesList().then((types) => {
return types.map((type) => {
return html`<li>
<ak-forms-modal>
<span slot="submit">
${t`Create`}
</span>
<span slot="header">
${t`Create ${type.name}`}
</span>
<ak-proxy-form
slot="form"
.args=${{
"modelName": type.modelName
}}
type=${type.component}>
</ak-proxy-form>
<button slot="trigger" class="pf-c-dropdown__menu-item">
${type.name}<br>
<small>${type.description}</small>
</button>
</ak-forms-modal>
</li>`;
});
}), html`<ak-spinner></ak-spinner>`)}
</ul>
</ak-dropdown>
${super.renderToolbar()}`;
return html` <ak-dropdown class="pf-c-dropdown">
<button class="pf-m-primary pf-c-dropdown__toggle" type="button">
<span class="pf-c-dropdown__toggle-text">${t`Create`}</span>
<i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i>
</button>
<ul class="pf-c-dropdown__menu" hidden>
${until(
new SourcesApi(DEFAULT_CONFIG).sourcesAllTypesList().then((types) => {
return types.map((type) => {
return html`<li>
<ak-forms-modal>
<span slot="submit"> ${t`Create`} </span>
<span slot="header"> ${t`Create ${type.name}`} </span>
<ak-proxy-form
slot="form"
.args=${{
modelName: type.modelName,
}}
type=${type.component}
>
</ak-proxy-form>
<button slot="trigger" class="pf-c-dropdown__menu-item">
${type.name}<br />
<small>${type.description}</small>
</button>
</ak-forms-modal>
</li>`;
});
}),
html`<ak-spinner></ak-spinner>`,
)}
</ul>
</ak-dropdown>
${super.renderToolbar()}`;
}
}

View File

@ -12,7 +12,6 @@ import { ModelForm } from "../../../elements/forms/ModelForm";
@customElement("ak-source-ldap-form")
export class LDAPSourceForm extends ModelForm<LDAPSource, string> {
loadInstance(pk: string): Promise<LDAPSource> {
return new SourcesApi(DEFAULT_CONFIG).sourcesLdapRetrieve({
slug: pk,
@ -31,199 +30,313 @@ export class LDAPSourceForm extends ModelForm<LDAPSource, string> {
if (this.instance) {
return new SourcesApi(DEFAULT_CONFIG).sourcesLdapPartialUpdate({
slug: this.instance.slug,
patchedLDAPSourceRequest: data
patchedLDAPSourceRequest: data,
});
} else {
return new SourcesApi(DEFAULT_CONFIG).sourcesLdapCreate({
lDAPSourceRequest: data as unknown as LDAPSourceRequest
lDAPSourceRequest: data as unknown as LDAPSourceRequest,
});
}
};
renderForm(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
<ak-form-element-horizontal
label=${t`Name`}
?required=${true}
name="name">
<input type="text" value="${ifDefined(this.instance?.name)}" class="pf-c-form-control" required>
<ak-form-element-horizontal label=${t`Name`} ?required=${true} name="name">
<input
type="text"
value="${ifDefined(this.instance?.name)}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Slug`}
?required=${true}
name="slug">
<input type="text" value="${ifDefined(this.instance?.slug)}" class="pf-c-form-control" required>
<ak-form-element-horizontal label=${t`Slug`} ?required=${true} name="slug">
<input
type="text"
value="${ifDefined(this.instance?.slug)}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal name="enabled">
<div class="pf-c-check">
<input type="checkbox" class="pf-c-check__input" ?checked=${first(this.instance?.enabled, true)}>
<label class="pf-c-check__label">
${t`Enabled`}
</label>
<input
type="checkbox"
class="pf-c-check__input"
?checked=${first(this.instance?.enabled, true)}
/>
<label class="pf-c-check__label"> ${t`Enabled`} </label>
</div>
</ak-form-element-horizontal>
<ak-form-element-horizontal name="syncUsers">
<div class="pf-c-check">
<input type="checkbox" class="pf-c-check__input" ?checked=${first(this.instance?.syncUsers, true)}>
<label class="pf-c-check__label">
${t`Sync users`}
</label>
<input
type="checkbox"
class="pf-c-check__input"
?checked=${first(this.instance?.syncUsers, true)}
/>
<label class="pf-c-check__label"> ${t`Sync users`} </label>
</div>
</ak-form-element-horizontal>
<ak-form-element-horizontal name="syncUsersPassword">
<div class="pf-c-check">
<input type="checkbox" class="pf-c-check__input" ?checked=${first(this.instance?.syncUsersPassword, true)}>
<label class="pf-c-check__label">
${t`User password writeback`}
</label>
<input
type="checkbox"
class="pf-c-check__input"
?checked=${first(this.instance?.syncUsersPassword, true)}
/>
<label class="pf-c-check__label"> ${t`User password writeback`} </label>
</div>
<p class="pf-c-form__helper-text">${t`Login password is synced from LDAP into authentik automatically. Enable this option only to write password changes in authentik back to LDAP.`}</p>
<p class="pf-c-form__helper-text">
${t`Login password is synced from LDAP into authentik automatically. Enable this option only to write password changes in authentik back to LDAP.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal name="syncGroups">
<div class="pf-c-check">
<input type="checkbox" class="pf-c-check__input" ?checked=${first(this.instance?.syncGroups, true)}>
<label class="pf-c-check__label">
${t`Sync groups`}
</label>
<input
type="checkbox"
class="pf-c-check__input"
?checked=${first(this.instance?.syncGroups, true)}
/>
<label class="pf-c-check__label"> ${t`Sync groups`} </label>
</div>
</ak-form-element-horizontal>
<ak-form-group .expanded=${true}>
<span slot="header">
${t`Connection settings`}
</span>
<span slot="header"> ${t`Connection settings`} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${t`Server URI`}
?required=${true}
name="serverUri">
<input type="text" placeholder="ldap://1.2.3.4" value="${ifDefined(this.instance?.serverUri)}" class="pf-c-form-control" required>
name="serverUri"
>
<input
type="text"
placeholder="ldap://1.2.3.4"
value="${ifDefined(this.instance?.serverUri)}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal name="startTls">
<div class="pf-c-check">
<input type="checkbox" class="pf-c-check__input" ?checked=${first(this.instance?.startTls, true)}>
<label class="pf-c-check__label">
${t`Enable StartTLS`}
</label>
<input
type="checkbox"
class="pf-c-check__input"
?checked=${first(this.instance?.startTls, true)}
/>
<label class="pf-c-check__label"> ${t`Enable StartTLS`} </label>
</div>
<p class="pf-c-form__helper-text">${t`To use SSL instead, use 'ldaps://' and disable this option.`}</p>
<p class="pf-c-form__helper-text">
${t`To use SSL instead, use 'ldaps://' and disable this option.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Bind CN`}
?required=${true}
name="bindCn">
<input type="text" value="${ifDefined(this.instance?.bindCn)}" class="pf-c-form-control" required>
<ak-form-element-horizontal label=${t`Bind CN`} ?required=${true} name="bindCn">
<input
type="text"
value="${ifDefined(this.instance?.bindCn)}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Bind Password`}
?required=${true}
?writeOnly=${this.instance !== undefined}
name="bindPassword">
<input type="text" value="" class="pf-c-form-control" required>
name="bindPassword"
>
<input type="text" value="" class="pf-c-form-control" required />
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Base DN`}
?required=${true}
name="baseDn">
<input type="text" value="${ifDefined(this.instance?.baseDn)}" class="pf-c-form-control" required>
<ak-form-element-horizontal label=${t`Base DN`} ?required=${true} name="baseDn">
<input
type="text"
value="${ifDefined(this.instance?.baseDn)}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
</div>
</ak-form-group>
<ak-form-group>
<span slot="header">
${t`Advanced settings`}
</span>
<span slot="header"> ${t`Advanced settings`} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${t`User Property Mappings`}
?required=${true}
name="propertyMappings">
name="propertyMappings"
>
<select class="pf-c-form-control" multiple>
${until(new PropertymappingsApi(DEFAULT_CONFIG).propertymappingsLdapList({
ordering: "managed,object_field"
}).then(mappings => {
return mappings.results.map(mapping => {
let selected = false;
if (!this.instance?.propertyMappings) {
selected = mapping.managed?.startsWith("goauthentik.io/sources/ldap/default") || mapping.managed?.startsWith("goauthentik.io/sources/ldap/ms") || false;
} else {
selected = Array.from(this.instance?.propertyMappings).some(su => {
return su == mapping.pk;
${until(
new PropertymappingsApi(DEFAULT_CONFIG)
.propertymappingsLdapList({
ordering: "managed,object_field",
})
.then((mappings) => {
return mappings.results.map((mapping) => {
let selected = false;
if (!this.instance?.propertyMappings) {
selected =
mapping.managed?.startsWith(
"goauthentik.io/sources/ldap/default",
) ||
mapping.managed?.startsWith(
"goauthentik.io/sources/ldap/ms",
) ||
false;
} else {
selected = Array.from(
this.instance?.propertyMappings,
).some((su) => {
return su == mapping.pk;
});
}
return html`<option
value=${ifDefined(mapping.pk)}
?selected=${selected}
>
${mapping.name}
</option>`;
});
}
return html`<option value=${ifDefined(mapping.pk)} ?selected=${selected}>${mapping.name}</option>`;
});
}), html`<option>${t`Loading...`}</option>`)}
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
<p class="pf-c-form__helper-text">${t`Property mappings used to user creation.`}</p>
<p class="pf-c-form__helper-text">${t`Hold control/command to select multiple items.`}</p>
<p class="pf-c-form__helper-text">
${t`Property mappings used to user creation.`}
</p>
<p class="pf-c-form__helper-text">
${t`Hold control/command to select multiple items.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Group Property Mappings`}
?required=${true}
name="propertyMappingsGroup">
name="propertyMappingsGroup"
>
<select class="pf-c-form-control" multiple>
${until(new PropertymappingsApi(DEFAULT_CONFIG).propertymappingsLdapList({
ordering: "object_field"
}).then(mappings => {
return mappings.results.map(mapping => {
let selected = false;
if (!this.instance?.propertyMappingsGroup) {
selected = mapping.managed === "goauthentik.io/sources/ldap/default-name";
} else {
selected = Array.from(this.instance?.propertyMappingsGroup).some(su => {
return su == mapping.pk;
${until(
new PropertymappingsApi(DEFAULT_CONFIG)
.propertymappingsLdapList({
ordering: "object_field",
})
.then((mappings) => {
return mappings.results.map((mapping) => {
let selected = false;
if (!this.instance?.propertyMappingsGroup) {
selected =
mapping.managed ===
"goauthentik.io/sources/ldap/default-name";
} else {
selected = Array.from(
this.instance?.propertyMappingsGroup,
).some((su) => {
return su == mapping.pk;
});
}
return html`<option
value=${ifDefined(mapping.pk)}
?selected=${selected}
>
${mapping.name}
</option>`;
});
}
return html`<option value=${ifDefined(mapping.pk)} ?selected=${selected}>${mapping.name}</option>`;
});
}), html`<option>${t`Loading...`}</option>`)}
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
<p class="pf-c-form__helper-text">${t`Property mappings used to group creation.`}</p>
<p class="pf-c-form__helper-text">${t`Hold control/command to select multiple items.`}</p>
<p class="pf-c-form__helper-text">
${t`Property mappings used to group creation.`}
</p>
<p class="pf-c-form__helper-text">
${t`Hold control/command to select multiple items.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Addition User DN`}
name="additionalUserDn">
<input type="text" value="${ifDefined(this.instance?.additionalUserDn)}" class="pf-c-form-control">
<p class="pf-c-form__helper-text">${t`Additional user DN, prepended to the Base DN.`}</p>
name="additionalUserDn"
>
<input
type="text"
value="${ifDefined(this.instance?.additionalUserDn)}"
class="pf-c-form-control"
/>
<p class="pf-c-form__helper-text">
${t`Additional user DN, prepended to the Base DN.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Addition Group DN`}
name="additionalGroupDn">
<input type="text" value="${ifDefined(this.instance?.additionalGroupDn)}" class="pf-c-form-control">
<p class="pf-c-form__helper-text">${t`Additional group DN, prepended to the Base DN.`}</p>
name="additionalGroupDn"
>
<input
type="text"
value="${ifDefined(this.instance?.additionalGroupDn)}"
class="pf-c-form-control"
/>
<p class="pf-c-form__helper-text">
${t`Additional group DN, prepended to the Base DN.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`User object filter`}
?required=${true}
name="userObjectFilter">
<input type="text" value="${this.instance?.userObjectFilter || "(objectClass=person)"}" class="pf-c-form-control" required>
<p class="pf-c-form__helper-text">${t`Consider Objects matching this filter to be Users.`}</p>
name="userObjectFilter"
>
<input
type="text"
value="${this.instance?.userObjectFilter || "(objectClass=person)"}"
class="pf-c-form-control"
required
/>
<p class="pf-c-form__helper-text">
${t`Consider Objects matching this filter to be Users.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Group object filter`}
?required=${true}
name="groupObjectFilter">
<input type="text" value="${this.instance?.groupObjectFilter || "(objectClass=group)"}" class="pf-c-form-control" required>
<p class="pf-c-form__helper-text">${t`Consider Objects matching this filter to be Groups.`}</p>
name="groupObjectFilter"
>
<input
type="text"
value="${this.instance?.groupObjectFilter || "(objectClass=group)"}"
class="pf-c-form-control"
required
/>
<p class="pf-c-form__helper-text">
${t`Consider Objects matching this filter to be Groups.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Group membership field`}
?required=${true}
name="groupMembershipField">
<input type="text" value="${this.instance?.groupMembershipField || "member"}" class="pf-c-form-control" required>
<p class="pf-c-form__helper-text">${t`Field which contains members of a group.`}</p>
name="groupMembershipField"
>
<input
type="text"
value="${this.instance?.groupMembershipField || "member"}"
class="pf-c-form-control"
required
/>
<p class="pf-c-form__helper-text">
${t`Field which contains members of a group.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Object uniqueness field`}
?required=${true}
name="objectUniquenessField">
<input type="text" value="${this.instance?.objectUniquenessField || "objectSid"}" class="pf-c-form-control" required>
<p class="pf-c-form__helper-text">${t`Field which contains a unique Identifier.`}</p>
name="objectUniquenessField"
>
<input
type="text"
value="${this.instance?.objectUniquenessField || "objectSid"}"
class="pf-c-form-control"
required
/>
<p class="pf-c-form__helper-text">
${t`Field which contains a unique Identifier.`}
</p>
</ak-form-element-horizontal>
</div>
</ak-form-group>
</form>`;
}
}

View File

@ -27,21 +27,34 @@ import { EVENT_REFRESH } from "../../../constants";
@customElement("ak-source-ldap-view")
export class LDAPSourceViewPage extends LitElement {
@property({ type: String })
set sourceSlug(slug: string) {
new SourcesApi(DEFAULT_CONFIG).sourcesLdapRetrieve({
slug: slug
}).then((source) => {
this.source = source;
});
new SourcesApi(DEFAULT_CONFIG)
.sourcesLdapRetrieve({
slug: slug,
})
.then((source) => {
this.source = source;
});
}
@property({ attribute: false })
source!: LDAPSource;
static get styles(): CSSResult[] {
return [PFBase, PFPage, PFButton, PFFlex, PFDisplay, PFGallery, PFContent, PFCard, PFDescriptionList, PFSizing, AKGlobal];
return [
PFBase,
PFPage,
PFButton,
PFFlex,
PFDisplay,
PFGallery,
PFContent,
PFCard,
PFDescriptionList,
PFSizing,
AKGlobal,
];
}
constructor() {
@ -57,120 +70,155 @@ export class LDAPSourceViewPage extends LitElement {
return html``;
}
return html`<ak-tabs>
<section slot="page-overview" data-tab-title="${t`Overview`}" class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-u-display-flex pf-u-justify-content-center">
<div class="pf-u-w-75">
<div class="pf-c-card">
<div class="pf-c-card__body">
<dl class="pf-c-description-list pf-m-2-col-on-lg">
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${t`Name`}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">${this.source.name}</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${t`Server URI`}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">${this.source.serverUri}</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${t`Base DN`}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
<ul>
<li>${this.source.baseDn}</li>
</ul>
</div>
</dd>
</div>
</dl>
</div>
<div class="pf-c-card__footer">
<ak-forms-modal>
<span slot="submit">
${t`Update`}
</span>
<span slot="header">
${t`Update LDAP Source`}
</span>
<ak-source-ldap-form
slot="form"
.instancePk=${this.source.slug}>
</ak-source-ldap-form>
<button slot="trigger" class="pf-c-button pf-m-primary">
${t`Edit`}
</button>
</ak-forms-modal>
</div>
<section
slot="page-overview"
data-tab-title="${t`Overview`}"
class="pf-c-page__main-section pf-m-no-padding-mobile"
>
<div class="pf-u-display-flex pf-u-justify-content-center">
<div class="pf-u-w-75">
<div class="pf-c-card">
<div class="pf-c-card__body">
<dl class="pf-c-description-list pf-m-2-col-on-lg">
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text"
>${t`Name`}</span
>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
${this.source.name}
</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text"
>${t`Server URI`}</span
>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
${this.source.serverUri}
</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text"
>${t`Base DN`}</span
>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
<ul>
<li>${this.source.baseDn}</li>
</ul>
</div>
</dd>
</div>
</dl>
</div>
<div class="pf-c-card__footer">
<ak-forms-modal>
<span slot="submit"> ${t`Update`} </span>
<span slot="header"> ${t`Update LDAP Source`} </span>
<ak-source-ldap-form
slot="form"
.instancePk=${this.source.slug}
>
</ak-source-ldap-form>
<button slot="trigger" class="pf-c-button pf-m-primary">
${t`Edit`}
</button>
</ak-forms-modal>
</div>
</div>
</div>
</section>
<section slot="page-changelog" data-tab-title="${t`Changelog`}" class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-c-card">
<div class="pf-c-card__body">
<ak-object-changelog
targetModelPk=${this.source.pk || ""}
targetModelApp="authentik_sources_ldap"
targetModelName="ldapsource">
</ak-object-changelog>
</div>
</div>
</section>
<section
slot="page-changelog"
data-tab-title="${t`Changelog`}"
class="pf-c-page__main-section pf-m-no-padding-mobile"
>
<div class="pf-c-card">
<div class="pf-c-card__body">
<ak-object-changelog
targetModelPk=${this.source.pk || ""}
targetModelApp="authentik_sources_ldap"
targetModelName="ldapsource"
>
</ak-object-changelog>
</div>
</section>
<section slot="page-sync" data-tab-title="${t`Sync`}" class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-u-display-flex pf-u-justify-content-center">
<div class="pf-u-w-75">
<div class="pf-c-card">
<div class="pf-c-card__title">
<p>${t`Sync status`}</p>
</div>
<div class="pf-c-card__body">
${until(new SourcesApi(DEFAULT_CONFIG).sourcesLdapSyncStatusRetrieve({
slug: this.source.slug
}).then((ls) => {
let header = html``;
if (ls.status === StatusEnum.Warning) {
header = html`<p>${t`Task finished with warnings`}</p>`;
} else if (status === StatusEnum.Error) {
header = html`<p>${t`Task finished with errors`}</p>`;
} else {
header = html`<p>${t`Last sync: ${ls.taskFinishTimestamp.toLocaleString()}`}</p>`;
}
return html`
${header}
<ul>
${ls.messages.map(m => {
return html`<li>${m}</li>`;
})}
</ul>
`;
}).catch(() => {
return html`<p>${t`Not synced yet.`}</p>`;
}), "loading")}
</div>
<div class="pf-c-card__footer">
<ak-action-button
.apiRequest=${() => {
return new SourcesApi(DEFAULT_CONFIG).sourcesLdapPartialUpdate({
slug: this.source?.slug || "",
patchedLDAPSourceRequest: this.source,
});
}}>
${t`Retry Task`}
</ak-action-button>
</div>
</div>
</section>
<section
slot="page-sync"
data-tab-title="${t`Sync`}"
class="pf-c-page__main-section pf-m-no-padding-mobile"
>
<div class="pf-u-display-flex pf-u-justify-content-center">
<div class="pf-u-w-75">
<div class="pf-c-card">
<div class="pf-c-card__title">
<p>${t`Sync status`}</p>
</div>
<div class="pf-c-card__body">
${until(
new SourcesApi(DEFAULT_CONFIG)
.sourcesLdapSyncStatusRetrieve({
slug: this.source.slug,
})
.then((ls) => {
let header = html``;
if (ls.status === StatusEnum.Warning) {
header = html`<p>
${t`Task finished with warnings`}
</p>`;
} else if (status === StatusEnum.Error) {
header = html`<p>
${t`Task finished with errors`}
</p>`;
} else {
header = html`<p>
${t`Last sync: ${ls.taskFinishTimestamp.toLocaleString()}`}
</p>`;
}
return html`
${header}
<ul>
${ls.messages.map((m) => {
return html`<li>${m}</li>`;
})}
</ul>
`;
})
.catch(() => {
return html`<p>${t`Not synced yet.`}</p>`;
}),
"loading",
)}
</div>
<div class="pf-c-card__footer">
<ak-action-button
.apiRequest=${() => {
return new SourcesApi(
DEFAULT_CONFIG,
).sourcesLdapPartialUpdate({
slug: this.source?.slug || "",
patchedLDAPSourceRequest: this.source,
});
}}
>
${t`Retry Task`}
</ak-action-button>
</div>
</div>
</div>
</section>
</ak-tabs>`;
</div>
</section>
</ak-tabs>`;
}
}

View File

@ -1,4 +1,11 @@
import { OAuthSource, SourcesApi, FlowsApi, UserMatchingModeEnum, OAuthSourceRequest, FlowsInstancesListDesignationEnum } from "authentik-api";
import {
OAuthSource,
SourcesApi,
FlowsApi,
UserMatchingModeEnum,
OAuthSourceRequest,
FlowsInstancesListDesignationEnum,
} from "authentik-api";
import { t } from "@lingui/macro";
import { customElement, property } from "lit-element";
import { html, TemplateResult } from "lit-html";
@ -12,23 +19,24 @@ import { ModelForm } from "../../../elements/forms/ModelForm";
@customElement("ak-source-oauth-form")
export class OAuthSourceForm extends ModelForm<OAuthSource, string> {
loadInstance(pk: string): Promise<OAuthSource> {
return new SourcesApi(DEFAULT_CONFIG).sourcesOauthRetrieve({
slug: pk,
}).then(source => {
this.showUrlOptions = first(source.type?.urlsCustomizable, false);
return source;
});
return new SourcesApi(DEFAULT_CONFIG)
.sourcesOauthRetrieve({
slug: pk,
})
.then((source) => {
this.showUrlOptions = first(source.type?.urlsCustomizable, false);
return source;
});
}
@property()
modelName?: string;
@property({type: Boolean})
@property({ type: Boolean })
showUrlOptions = false;
@property({type: Boolean})
@property({ type: Boolean })
showRequestTokenURL = false;
getSuccessMessage(): string {
@ -43,11 +51,11 @@ export class OAuthSourceForm extends ModelForm<OAuthSource, string> {
if (this.instance?.slug) {
return new SourcesApi(DEFAULT_CONFIG).sourcesOauthPartialUpdate({
slug: this.instance.slug,
patchedOAuthSourceRequest: data
patchedOAuthSourceRequest: data,
});
} else {
return new SourcesApi(DEFAULT_CONFIG).sourcesOauthCreate({
oAuthSourceRequest: data as unknown as OAuthSourceRequest
oAuthSourceRequest: data as unknown as OAuthSourceRequest,
});
}
};
@ -56,194 +64,301 @@ export class OAuthSourceForm extends ModelForm<OAuthSource, string> {
if (!this.showUrlOptions) {
return html``;
}
return html`
<ak-form-group>
<span slot="header">
${t`URL settings`}
</span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${t`Authorization URL`}
?required=${true}
name="authorizationUrl">
<input type="text" value="${first(this.instance?.authorizationUrl, "")}" class="pf-c-form-control" required>
<p class="pf-c-form__helper-text">${t`URL the user is redirect to to consent the authorization.`}</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Access token URL`}
?required=${true}
name="accessTokenUrl">
<input type="text" value="${first(this.instance?.accessTokenUrl, "")}" class="pf-c-form-control" required>
<p class="pf-c-form__helper-text">${t`URL used by authentik to retrieve tokens.`}</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Profile URL`}
?required=${true}
name="profileUrl">
<input type="text" value="${first(this.instance?.profileUrl, "")}" class="pf-c-form-control" required>
<p class="pf-c-form__helper-text">${t`URL used by authentik to get user information.`}</p>
</ak-form-element-horizontal>
${this.showRequestTokenURL ? html`<ak-form-element-horizontal
label=${t`Request token URL`}
name="requestTokenUrl">
<input type="text" value="${first(this.instance?.requestTokenUrl, "")}" class="pf-c-form-control">
<p class="pf-c-form__helper-text">${t`URL used to request the initial token. This URL is only required for OAuth 1.`}</p>
</ak-form-element-horizontal>
` : html``}
</div>
</ak-form-group>`;
return html` <ak-form-group>
<span slot="header"> ${t`URL settings`} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${t`Authorization URL`}
?required=${true}
name="authorizationUrl"
>
<input
type="text"
value="${first(this.instance?.authorizationUrl, "")}"
class="pf-c-form-control"
required
/>
<p class="pf-c-form__helper-text">
${t`URL the user is redirect to to consent the authorization.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Access token URL`}
?required=${true}
name="accessTokenUrl"
>
<input
type="text"
value="${first(this.instance?.accessTokenUrl, "")}"
class="pf-c-form-control"
required
/>
<p class="pf-c-form__helper-text">
${t`URL used by authentik to retrieve tokens.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Profile URL`}
?required=${true}
name="profileUrl"
>
<input
type="text"
value="${first(this.instance?.profileUrl, "")}"
class="pf-c-form-control"
required
/>
<p class="pf-c-form__helper-text">
${t`URL used by authentik to get user information.`}
</p>
</ak-form-element-horizontal>
${this.showRequestTokenURL
? html`<ak-form-element-horizontal
label=${t`Request token URL`}
name="requestTokenUrl"
>
<input
type="text"
value="${first(this.instance?.requestTokenUrl, "")}"
class="pf-c-form-control"
/>
<p class="pf-c-form__helper-text">
${t`URL used to request the initial token. This URL is only required for OAuth 1.`}
</p>
</ak-form-element-horizontal> `
: html``}
</div>
</ak-form-group>`;
}
renderForm(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
<ak-form-element-horizontal
label=${t`Name`}
?required=${true}
name="name">
<input type="text" value="${ifDefined(this.instance?.name)}" class="pf-c-form-control" required>
<ak-form-element-horizontal label=${t`Name`} ?required=${true} name="name">
<input
type="text"
value="${ifDefined(this.instance?.name)}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Slug`}
?required=${true}
name="slug">
<input type="text" value="${ifDefined(this.instance?.slug)}" class="pf-c-form-control" required>
<ak-form-element-horizontal label=${t`Slug`} ?required=${true} name="slug">
<input
type="text"
value="${ifDefined(this.instance?.slug)}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal name="enabled">
<div class="pf-c-check">
<input type="checkbox" class="pf-c-check__input" ?checked=${first(this.instance?.enabled, true)}>
<label class="pf-c-check__label">
${t`Enabled`}
</label>
<input
type="checkbox"
class="pf-c-check__input"
?checked=${first(this.instance?.enabled, true)}
/>
<label class="pf-c-check__label"> ${t`Enabled`} </label>
</div>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`User matching mode`}
?required=${true}
name="userMatchingMode">
name="userMatchingMode"
>
<select class="pf-c-form-control">
<option value=${UserMatchingModeEnum.Identifier} ?selected=${this.instance?.userMatchingMode === UserMatchingModeEnum.Identifier}>
<option
value=${UserMatchingModeEnum.Identifier}
?selected=${this.instance?.userMatchingMode ===
UserMatchingModeEnum.Identifier}
>
${t`Link users on unique identifier`}
</option>
<option value=${UserMatchingModeEnum.UsernameLink} ?selected=${this.instance?.userMatchingMode === UserMatchingModeEnum.UsernameLink}>
<option
value=${UserMatchingModeEnum.UsernameLink}
?selected=${this.instance?.userMatchingMode ===
UserMatchingModeEnum.UsernameLink}
>
${t`Link to a user with identical email address. Can have security implications when a source doesn't validate email addresses`}
</option>
<option value=${UserMatchingModeEnum.UsernameDeny} ?selected=${this.instance?.userMatchingMode === UserMatchingModeEnum.UsernameDeny}>
<option
value=${UserMatchingModeEnum.UsernameDeny}
?selected=${this.instance?.userMatchingMode ===
UserMatchingModeEnum.UsernameDeny}
>
${t`Use the user's email address, but deny enrollment when the email address already exists.`}
</option>
<option value=${UserMatchingModeEnum.EmailLink} ?selected=${this.instance?.userMatchingMode === UserMatchingModeEnum.EmailLink}>
<option
value=${UserMatchingModeEnum.EmailLink}
?selected=${this.instance?.userMatchingMode ===
UserMatchingModeEnum.EmailLink}
>
${t`Link to a user with identical username address. Can have security implications when a username is used with another source.`}
</option>
<option value=${UserMatchingModeEnum.EmailDeny} ?selected=${this.instance?.userMatchingMode === UserMatchingModeEnum.EmailDeny}>
<option
value=${UserMatchingModeEnum.EmailDeny}
?selected=${this.instance?.userMatchingMode ===
UserMatchingModeEnum.EmailDeny}
>
${t`Use the user's username, but deny enrollment when the username already exists.`}
</option>
</select>
</ak-form-element-horizontal>
<ak-form-group .expanded=${true}>
<span slot="header">
${t`Protocol settings`}
</span>
<span slot="header"> ${t`Protocol settings`} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${t`Consumer key`}
?required=${true}
name="consumerKey">
<input type="text" value="${ifDefined(this.instance?.consumerKey)}" class="pf-c-form-control" required>
name="consumerKey"
>
<input
type="text"
value="${ifDefined(this.instance?.consumerKey)}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Consumer secret`}
?required=${true}
?writeOnly=${this.instance !== undefined}
name="consumerSecret">
<input type="text" value="" class="pf-c-form-control" required>
name="consumerSecret"
>
<input type="text" value="" class="pf-c-form-control" required />
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Provider type`}
name="providerType">
<select class="pf-c-form-control" @change=${(ev: Event) => {
const el = (ev.target as HTMLSelectElement);
const selected = el.selectedOptions[0];
this.showUrlOptions = "data-urls-custom" in selected.attributes;
this.showRequestTokenURL = "data-request-token" in selected.attributes;
if (!this.instance) {
this.instance = {} as OAuthSource;
}
this.instance.providerType = selected.value;
}}>
${until(new SourcesApi(DEFAULT_CONFIG).sourcesOauthSourceTypesList().then(types => {
return types.map(type => {
let selected = this.instance?.providerType === type.slug;
const modelSlug = this.modelName?.replace("oauthsource", "").replace("-", "");
const typeSlug = type.slug.replace("-", "");
if (!this.instance?.pk) {
if (modelSlug === typeSlug) {
selected = true;
this.showUrlOptions = type.urlsCustomizable;
this.showRequestTokenURL = type.requestTokenUrl !== null;
}
}
return html`<option
?data-urls-custom=${type.urlsCustomizable}
?data-request-token=${type.requestTokenUrl}
value=${type.slug}
?selected=${selected}>
${type.name}
</option>`;
});
}), html`<option>${t`Loading...`}</option>`)}
<ak-form-element-horizontal label=${t`Provider type`} name="providerType">
<select
class="pf-c-form-control"
@change=${(ev: Event) => {
const el = ev.target as HTMLSelectElement;
const selected = el.selectedOptions[0];
this.showUrlOptions = "data-urls-custom" in selected.attributes;
this.showRequestTokenURL =
"data-request-token" in selected.attributes;
if (!this.instance) {
this.instance = {} as OAuthSource;
}
this.instance.providerType = selected.value;
}}
>
${until(
new SourcesApi(DEFAULT_CONFIG)
.sourcesOauthSourceTypesList()
.then((types) => {
return types.map((type) => {
let selected =
this.instance?.providerType === type.slug;
const modelSlug = this.modelName
?.replace("oauthsource", "")
.replace("-", "");
const typeSlug = type.slug.replace("-", "");
if (!this.instance?.pk) {
if (modelSlug === typeSlug) {
selected = true;
this.showUrlOptions = type.urlsCustomizable;
this.showRequestTokenURL =
type.requestTokenUrl !== null;
}
}
return html`<option
?data-urls-custom=${type.urlsCustomizable}
?data-request-token=${type.requestTokenUrl}
value=${type.slug}
?selected=${selected}
>
${type.name}
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
</ak-form-element-horizontal>
</div>
</ak-form-group>
${this.renderUrlOptions()}
<ak-form-group>
<span slot="header">
${t`Flow settings`}
</span>
<span slot="header"> ${t`Flow settings`} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${t`Authentication flow`}
?required=${true}
name="authenticationFlow">
name="authenticationFlow"
>
<select class="pf-c-form-control">
${until(new FlowsApi(DEFAULT_CONFIG).flowsInstancesList({
ordering: "pk",
designation: FlowsInstancesListDesignationEnum.Authentication,
}).then(flows => {
return flows.results.map(flow => {
let selected = this.instance?.authenticationFlow === flow.pk;
if (!this.instance?.pk && !this.instance?.authenticationFlow && flow.slug === "default-source-authentication") {
selected = true;
}
return html`<option value=${ifDefined(flow.pk)} ?selected=${selected}>${flow.name} (${flow.slug})</option>`;
});
}), html`<option>${t`Loading...`}</option>`)}
${until(
new FlowsApi(DEFAULT_CONFIG)
.flowsInstancesList({
ordering: "pk",
designation:
FlowsInstancesListDesignationEnum.Authentication,
})
.then((flows) => {
return flows.results.map((flow) => {
let selected =
this.instance?.authenticationFlow === flow.pk;
if (
!this.instance?.pk &&
!this.instance?.authenticationFlow &&
flow.slug === "default-source-authentication"
) {
selected = true;
}
return html`<option
value=${ifDefined(flow.pk)}
?selected=${selected}
>
${flow.name} (${flow.slug})
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
<p class="pf-c-form__helper-text">${t`Flow to use when authenticating existing users.`}</p>
<p class="pf-c-form__helper-text">
${t`Flow to use when authenticating existing users.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Enrollment flow`}
?required=${true}
name="enrollmentFlow">
name="enrollmentFlow"
>
<select class="pf-c-form-control">
${until(new FlowsApi(DEFAULT_CONFIG).flowsInstancesList({
ordering: "pk",
designation: FlowsInstancesListDesignationEnum.Enrollment,
}).then(flows => {
return flows.results.map(flow => {
let selected = this.instance?.enrollmentFlow === flow.pk;
if (!this.instance?.pk && !this.instance?.enrollmentFlow && flow.slug === "default-source-enrollment") {
selected = true;
}
return html`<option value=${ifDefined(flow.pk)} ?selected=${selected}>${flow.name} (${flow.slug})</option>`;
});
}), html`<option>${t`Loading...`}</option>`)}
${until(
new FlowsApi(DEFAULT_CONFIG)
.flowsInstancesList({
ordering: "pk",
designation: FlowsInstancesListDesignationEnum.Enrollment,
})
.then((flows) => {
return flows.results.map((flow) => {
let selected =
this.instance?.enrollmentFlow === flow.pk;
if (
!this.instance?.pk &&
!this.instance?.enrollmentFlow &&
flow.slug === "default-source-enrollment"
) {
selected = true;
}
return html`<option
value=${ifDefined(flow.pk)}
?selected=${selected}
>
${flow.name} (${flow.slug})
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
<p class="pf-c-form__helper-text">${t`Flow to use when enrolling new users.`}</p>
<p class="pf-c-form__helper-text">
${t`Flow to use when enrolling new users.`}
</p>
</ak-form-element-horizontal>
</div>
</ak-form-group>
</form>`;
}
}

View File

@ -26,21 +26,34 @@ import { EVENT_REFRESH } from "../../../constants";
@customElement("ak-source-oauth-view")
export class OAuthSourceViewPage extends LitElement {
@property({ type: String })
set sourceSlug(value: string) {
new SourcesApi(DEFAULT_CONFIG).sourcesOauthRetrieve({
slug: value
}).then((source) => {
this.source = source;
});
new SourcesApi(DEFAULT_CONFIG)
.sourcesOauthRetrieve({
slug: value,
})
.then((source) => {
this.source = source;
});
}
@property({ attribute: false })
source?: OAuthSource;
static get styles(): CSSResult[] {
return [PFBase, PFPage, PFButton, PFFlex, PFDisplay, PFGallery, PFContent, PFCard, PFDescriptionList, PFSizing, AKGlobal];
return [
PFBase,
PFPage,
PFButton,
PFFlex,
PFDisplay,
PFGallery,
PFContent,
PFCard,
PFDescriptionList,
PFSizing,
AKGlobal,
];
}
constructor() {
@ -56,104 +69,142 @@ export class OAuthSourceViewPage extends LitElement {
return html``;
}
return html`<ak-tabs>
<section slot="page-overview" data-tab-title="${t`Overview`}" class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-u-display-flex pf-u-justify-content-center">
<div class="pf-u-w-75">
<div class="pf-c-card">
<div class="pf-c-card__body">
<dl class="pf-c-description-list pf-m-2-col-on-lg">
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${t`Name`}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">${this.source.name}</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${t`Provider Type`}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">${this.source.providerType}</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${t`Callback URL`}</span>
</dt>
<dd class="pf-c-description-list__description">
<code class="pf-c-description-list__text">${this.source.callbackUrl}</code>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${t`Access Key`}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">${this.source.consumerKey}</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${t`Authorization URL`}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">${this.source.type?.authorizationUrl || this.source.authorizationUrl}</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${t`Token URL`}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">${this.source.type?.accessTokenUrl || this.source.accessTokenUrl}</div>
</dd>
</div>
</dl>
</div>
<div class="pf-c-card__footer">
<ak-forms-modal>
<span slot="submit">
${t`Update`}
</span>
<span slot="header">
${t`Update OAuth Source`}
</span>
<ak-source-oauth-form
slot="form"
.instancePk=${this.source.slug}>
</ak-source-oauth-form>
<button slot="trigger" class="pf-c-button pf-m-primary">
${t`Edit`}
</button>
</ak-forms-modal>
</div>
<section
slot="page-overview"
data-tab-title="${t`Overview`}"
class="pf-c-page__main-section pf-m-no-padding-mobile"
>
<div class="pf-u-display-flex pf-u-justify-content-center">
<div class="pf-u-w-75">
<div class="pf-c-card">
<div class="pf-c-card__body">
<dl class="pf-c-description-list pf-m-2-col-on-lg">
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text"
>${t`Name`}</span
>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
${this.source.name}
</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text"
>${t`Provider Type`}</span
>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
${this.source.providerType}
</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text"
>${t`Callback URL`}</span
>
</dt>
<dd class="pf-c-description-list__description">
<code class="pf-c-description-list__text"
>${this.source.callbackUrl}</code
>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text"
>${t`Access Key`}</span
>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
${this.source.consumerKey}
</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text"
>${t`Authorization URL`}</span
>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
${this.source.type?.authorizationUrl ||
this.source.authorizationUrl}
</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text"
>${t`Token URL`}</span
>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
${this.source.type?.accessTokenUrl ||
this.source.accessTokenUrl}
</div>
</dd>
</div>
</dl>
</div>
<div class="pf-c-card__footer">
<ak-forms-modal>
<span slot="submit"> ${t`Update`} </span>
<span slot="header"> ${t`Update OAuth Source`} </span>
<ak-source-oauth-form
slot="form"
.instancePk=${this.source.slug}
>
</ak-source-oauth-form>
<button slot="trigger" class="pf-c-button pf-m-primary">
${t`Edit`}
</button>
</ak-forms-modal>
</div>
</div>
</div>
</section>
<section slot="page-changelog" data-tab-title="${t`Changelog`}" class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-c-card">
<div class="pf-c-card__body">
<ak-object-changelog
targetModelPk=${this.source.pk || ""}
targetModelApp="authentik_sources_oauth"
targetModelName="oauthsource">
</ak-object-changelog>
</div>
</div>
</section>
<div slot="page-policy-binding" data-tab-title="${t`Policy Bindings`}" class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-c-card">
<div class="pf-c-card__title">${t`These bindings control which users can access this source.
You can only use policies here as access is checked before the user is authenticated.`}</div>
<div class="pf-c-card__body">
<ak-bound-policies-list .target=${this.source.pk} ?policyOnly=${true}>
</ak-bound-policies-list>
</div>
</div>
</section>
<section
slot="page-changelog"
data-tab-title="${t`Changelog`}"
class="pf-c-page__main-section pf-m-no-padding-mobile"
>
<div class="pf-c-card">
<div class="pf-c-card__body">
<ak-object-changelog
targetModelPk=${this.source.pk || ""}
targetModelApp="authentik_sources_oauth"
targetModelName="oauthsource"
>
</ak-object-changelog>
</div>
</div>
</ak-tabs>`;
</section>
<div
slot="page-policy-binding"
data-tab-title="${t`Policy Bindings`}"
class="pf-c-page__main-section pf-m-no-padding-mobile"
>
<div class="pf-c-card">
<div class="pf-c-card__title">
${t`These bindings control which users can access this source.
You can only use policies here as access is checked before the user is authenticated.`}
</div>
<div class="pf-c-card__body">
<ak-bound-policies-list .target=${this.source.pk} ?policyOnly=${true}>
</ak-bound-policies-list>
</div>
</div>
</div>
</ak-tabs>`;
}
}

View File

@ -1,4 +1,10 @@
import { PlexSource, SourcesApi, FlowsApi, UserMatchingModeEnum, FlowsInstancesListDesignationEnum } from "authentik-api";
import {
PlexSource,
SourcesApi,
FlowsApi,
UserMatchingModeEnum,
FlowsInstancesListDesignationEnum,
} from "authentik-api";
import { t } from "@lingui/macro";
import { customElement, property } from "lit-element";
import { html, TemplateResult } from "lit-html";
@ -8,32 +14,32 @@ import "../../../elements/forms/HorizontalFormElement";
import { ifDefined } from "lit-html/directives/if-defined";
import { until } from "lit-html/directives/until";
import { first, randomString } from "../../../utils";
import { PlexAPIClient, PlexResource, popupCenterScreen} from "../../../flows/sources/plex/API";
import { PlexAPIClient, PlexResource, popupCenterScreen } from "../../../flows/sources/plex/API";
import { ModelForm } from "../../../elements/forms/ModelForm";
@customElement("ak-source-plex-form")
export class PlexSourceForm extends ModelForm<PlexSource, string> {
loadInstance(pk: string): Promise<PlexSource> {
return new SourcesApi(DEFAULT_CONFIG).sourcesPlexRetrieve({
slug: pk,
}).then(source => {
this.plexToken = source.plexToken;
this.loadServers();
return source;
});
return new SourcesApi(DEFAULT_CONFIG)
.sourcesPlexRetrieve({
slug: pk,
})
.then((source) => {
this.plexToken = source.plexToken;
this.loadServers();
return source;
});
}
@property()
plexToken?: string;
@property({attribute: false})
@property({ attribute: false })
plexResources?: PlexResource[];
get defaultInstance(): PlexSource | undefined {
return {
clientId: randomString(40)
clientId: randomString(40),
} as PlexSource;
}
@ -50,11 +56,11 @@ export class PlexSourceForm extends ModelForm<PlexSource, string> {
if (this.instance?.slug) {
return new SourcesApi(DEFAULT_CONFIG).sourcesPlexUpdate({
slug: this.instance.slug,
plexSourceRequest: data
plexSourceRequest: data,
});
} else {
return new SourcesApi(DEFAULT_CONFIG).sourcesPlexCreate({
plexSourceRequest: data
plexSourceRequest: data,
});
}
};
@ -62,7 +68,7 @@ export class PlexSourceForm extends ModelForm<PlexSource, string> {
async doAuth(): Promise<void> {
const authInfo = await PlexAPIClient.getPin(this.instance?.clientId || "");
const authWindow = popupCenterScreen(authInfo.authUrl, "plex auth", 550, 700);
PlexAPIClient.pinPoll(this.instance?.clientId || "", authInfo.pin.id).then(token => {
PlexAPIClient.pinPoll(this.instance?.clientId || "", authInfo.pin.id).then((token) => {
authWindow?.close();
this.plexToken = token;
this.loadServers();
@ -78,22 +84,32 @@ export class PlexSourceForm extends ModelForm<PlexSource, string> {
renderSettings(): TemplateResult {
if (!this.plexToken) {
return html`
<button class="pf-c-button pf-m-primary" type="button" @click=${() => {
return html` <button
class="pf-c-button pf-m-primary"
type="button"
@click=${() => {
this.doAuth();
}}>
${t`Load servers`}
</button>`;
}}
>
${t`Load servers`}
</button>`;
}
return html`
<button class="pf-c-button pf-m-secondary" type="button" @click=${() => {
this.doAuth();
}}>
${t`Re-authenticate with plex`}
return html` <button
class="pf-c-button pf-m-secondary"
type="button"
@click=${() => {
this.doAuth();
}}
>
${t`Re-authenticate with plex`}
</button>
<ak-form-element-horizontal name="allowFriends">
<div class="pf-c-check">
<input type="checkbox" class="pf-c-check__input" ?checked=${first(this.instance?.allowFriends, true)}>
<input
type="checkbox"
class="pf-c-check__input"
?checked=${first(this.instance?.allowFriends, true)}
/>
<label class="pf-c-check__label">
${t`Allow friends to authenticate via Plex, even if you don't share any servers`}
</label>
@ -102,127 +118,201 @@ export class PlexSourceForm extends ModelForm<PlexSource, string> {
<ak-form-element-horizontal
label=${t`Allowed servers`}
?required=${true}
name="allowedServers">
name="allowedServers"
>
<select class="pf-c-form-control" multiple>
${this.plexResources?.map(r => {
const selected = Array.from(this.instance?.allowedServers || []).some(server => {
return server == r.clientIdentifier;
});
return html`<option value=${r.clientIdentifier} ?selected=${selected}>${r.name}</option>`;
${this.plexResources?.map((r) => {
const selected = Array.from(this.instance?.allowedServers || []).some(
(server) => {
return server == r.clientIdentifier;
},
);
return html`<option value=${r.clientIdentifier} ?selected=${selected}>
${r.name}
</option>`;
})}
</select>
<p class="pf-c-form__helper-text">${t`Select which server a user has to be a member of to be allowed to authenticate.`}</p>
<p class="pf-c-form__helper-text">${t`Hold control/command to select multiple items.`}</p>
<p class="pf-c-form__helper-text">
${t`Select which server a user has to be a member of to be allowed to authenticate.`}
</p>
<p class="pf-c-form__helper-text">
${t`Hold control/command to select multiple items.`}
</p>
</ak-form-element-horizontal>`;
}
renderForm(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
<ak-form-element-horizontal
label=${t`Name`}
?required=${true}
name="name">
<input type="text" value="${ifDefined(this.instance?.name)}" class="pf-c-form-control" required>
<ak-form-element-horizontal label=${t`Name`} ?required=${true} name="name">
<input
type="text"
value="${ifDefined(this.instance?.name)}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Slug`}
?required=${true}
name="slug">
<input type="text" value="${ifDefined(this.instance?.slug)}" class="pf-c-form-control" required>
<ak-form-element-horizontal label=${t`Slug`} ?required=${true} name="slug">
<input
type="text"
value="${ifDefined(this.instance?.slug)}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal name="enabled">
<div class="pf-c-check">
<input type="checkbox" class="pf-c-check__input" ?checked=${first(this.instance?.enabled, true)}>
<label class="pf-c-check__label">
${t`Enabled`}
</label>
<input
type="checkbox"
class="pf-c-check__input"
?checked=${first(this.instance?.enabled, true)}
/>
<label class="pf-c-check__label"> ${t`Enabled`} </label>
</div>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`User matching mode`}
?required=${true}
name="userMatchingMode">
name="userMatchingMode"
>
<select class="pf-c-form-control">
<option value=${UserMatchingModeEnum.Identifier} ?selected=${this.instance?.userMatchingMode === UserMatchingModeEnum.Identifier}>
<option
value=${UserMatchingModeEnum.Identifier}
?selected=${this.instance?.userMatchingMode ===
UserMatchingModeEnum.Identifier}
>
${t`Link users on unique identifier`}
</option>
<option value=${UserMatchingModeEnum.UsernameLink} ?selected=${this.instance?.userMatchingMode === UserMatchingModeEnum.UsernameLink}>
<option
value=${UserMatchingModeEnum.UsernameLink}
?selected=${this.instance?.userMatchingMode ===
UserMatchingModeEnum.UsernameLink}
>
${t`Link to a user with identical email address. Can have security implications when a source doesn't validate email addresses`}
</option>
<option value=${UserMatchingModeEnum.UsernameDeny} ?selected=${this.instance?.userMatchingMode === UserMatchingModeEnum.UsernameDeny}>
<option
value=${UserMatchingModeEnum.UsernameDeny}
?selected=${this.instance?.userMatchingMode ===
UserMatchingModeEnum.UsernameDeny}
>
${t`Use the user's email address, but deny enrollment when the email address already exists.`}
</option>
<option value=${UserMatchingModeEnum.EmailLink} ?selected=${this.instance?.userMatchingMode === UserMatchingModeEnum.EmailLink}>
<option
value=${UserMatchingModeEnum.EmailLink}
?selected=${this.instance?.userMatchingMode ===
UserMatchingModeEnum.EmailLink}
>
${t`Link to a user with identical username address. Can have security implications when a username is used with another source.`}
</option>
<option value=${UserMatchingModeEnum.EmailDeny} ?selected=${this.instance?.userMatchingMode === UserMatchingModeEnum.EmailDeny}>
<option
value=${UserMatchingModeEnum.EmailDeny}
?selected=${this.instance?.userMatchingMode ===
UserMatchingModeEnum.EmailDeny}
>
${t`Use the user's username, but deny enrollment when the username already exists.`}
</option>
</select>
</ak-form-element-horizontal>
<ak-form-group .expanded=${true}>
<span slot="header">
${t`Protocol settings`}
</span>
<span slot="header"> ${t`Protocol settings`} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${t`Client ID`}
?required=${true}
name="clientId">
<input type="text" value="${first(this.instance?.clientId)}" class="pf-c-form-control" required>
name="clientId"
>
<input
type="text"
value="${first(this.instance?.clientId)}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
${this.renderSettings()}
</div>
</ak-form-group>
<ak-form-group>
<span slot="header">
${t`Flow settings`}
</span>
<span slot="header"> ${t`Flow settings`} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${t`Authentication flow`}
?required=${true}
name="authenticationFlow">
name="authenticationFlow"
>
<select class="pf-c-form-control">
${until(new FlowsApi(DEFAULT_CONFIG).flowsInstancesList({
ordering: "pk",
designation: FlowsInstancesListDesignationEnum.Authentication,
}).then(flows => {
return flows.results.map(flow => {
let selected = this.instance?.authenticationFlow === flow.pk;
if (!this.instance?.pk && !this.instance?.authenticationFlow && flow.slug === "default-source-authentication") {
selected = true;
}
return html`<option value=${ifDefined(flow.pk)} ?selected=${selected}>${flow.name} (${flow.slug})</option>`;
});
}), html`<option>${t`Loading...`}</option>`)}
${until(
new FlowsApi(DEFAULT_CONFIG)
.flowsInstancesList({
ordering: "pk",
designation:
FlowsInstancesListDesignationEnum.Authentication,
})
.then((flows) => {
return flows.results.map((flow) => {
let selected =
this.instance?.authenticationFlow === flow.pk;
if (
!this.instance?.pk &&
!this.instance?.authenticationFlow &&
flow.slug === "default-source-authentication"
) {
selected = true;
}
return html`<option
value=${ifDefined(flow.pk)}
?selected=${selected}
>
${flow.name} (${flow.slug})
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
<p class="pf-c-form__helper-text">${t`Flow to use when authenticating existing users.`}</p>
<p class="pf-c-form__helper-text">
${t`Flow to use when authenticating existing users.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Enrollment flow`}
?required=${true}
name="enrollmentFlow">
name="enrollmentFlow"
>
<select class="pf-c-form-control">
${until(new FlowsApi(DEFAULT_CONFIG).flowsInstancesList({
ordering: "pk",
designation: FlowsInstancesListDesignationEnum.Enrollment,
}).then(flows => {
return flows.results.map(flow => {
let selected = this.instance?.enrollmentFlow === flow.pk;
if (!this.instance?.pk && !this.instance?.enrollmentFlow && flow.slug === "default-source-enrollment") {
selected = true;
}
return html`<option value=${ifDefined(flow.pk)} ?selected=${selected}>${flow.name} (${flow.slug})</option>`;
});
}), html`<option>${t`Loading...`}</option>`)}
${until(
new FlowsApi(DEFAULT_CONFIG)
.flowsInstancesList({
ordering: "pk",
designation: FlowsInstancesListDesignationEnum.Enrollment,
})
.then((flows) => {
return flows.results.map((flow) => {
let selected =
this.instance?.enrollmentFlow === flow.pk;
if (
!this.instance?.pk &&
!this.instance?.enrollmentFlow &&
flow.slug === "default-source-enrollment"
) {
selected = true;
}
return html`<option
value=${ifDefined(flow.pk)}
?selected=${selected}
>
${flow.name} (${flow.slug})
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
<p class="pf-c-form__helper-text">${t`Flow to use when enrolling new users.`}</p>
<p class="pf-c-form__helper-text">
${t`Flow to use when enrolling new users.`}
</p>
</ak-form-element-horizontal>
</div>
</ak-form-group>
</form>`;
}
}

View File

@ -26,21 +26,34 @@ import { EVENT_REFRESH } from "../../../constants";
@customElement("ak-source-plex-view")
export class PlexSourceViewPage extends LitElement {
@property({ type: String })
set sourceSlug(value: string) {
new SourcesApi(DEFAULT_CONFIG).sourcesPlexRetrieve({
slug: value
}).then((source) => {
this.source = source;
});
new SourcesApi(DEFAULT_CONFIG)
.sourcesPlexRetrieve({
slug: value,
})
.then((source) => {
this.source = source;
});
}
@property({ attribute: false })
source?: PlexSource;
static get styles(): CSSResult[] {
return [PFBase, PFPage, PFButton, PFFlex, PFDisplay, PFGallery, PFContent, PFCard, PFDescriptionList, PFSizing, AKGlobal];
return [
PFBase,
PFPage,
PFButton,
PFFlex,
PFDisplay,
PFGallery,
PFContent,
PFCard,
PFDescriptionList,
PFSizing,
AKGlobal,
];
}
constructor() {
@ -56,64 +69,80 @@ export class PlexSourceViewPage extends LitElement {
return html``;
}
return html`<ak-tabs>
<section slot="page-overview" data-tab-title="${t`Overview`}" class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-u-display-flex pf-u-justify-content-center">
<div class="pf-u-w-75">
<div class="pf-c-card">
<div class="pf-c-card__body">
<dl class="pf-c-description-list pf-m-2-col-on-lg">
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${t`Name`}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">${this.source.name}</div>
</dd>
</div>
</dl>
</div>
<div class="pf-c-card__footer">
<ak-forms-modal>
<span slot="submit">
${t`Update`}
</span>
<span slot="header">
${t`Update Plex Source`}
</span>
<ak-source-plex-form
slot="form"
.instancePk=${this.source.slug}>
</ak-source-plex-form>
<button slot="trigger" class="pf-c-button pf-m-primary">
${t`Edit`}
</button>
</ak-forms-modal>
</div>
<section
slot="page-overview"
data-tab-title="${t`Overview`}"
class="pf-c-page__main-section pf-m-no-padding-mobile"
>
<div class="pf-u-display-flex pf-u-justify-content-center">
<div class="pf-u-w-75">
<div class="pf-c-card">
<div class="pf-c-card__body">
<dl class="pf-c-description-list pf-m-2-col-on-lg">
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text"
>${t`Name`}</span
>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
${this.source.name}
</div>
</dd>
</div>
</dl>
</div>
<div class="pf-c-card__footer">
<ak-forms-modal>
<span slot="submit"> ${t`Update`} </span>
<span slot="header"> ${t`Update Plex Source`} </span>
<ak-source-plex-form
slot="form"
.instancePk=${this.source.slug}
>
</ak-source-plex-form>
<button slot="trigger" class="pf-c-button pf-m-primary">
${t`Edit`}
</button>
</ak-forms-modal>
</div>
</div>
</div>
</section>
<section slot="page-changelog" data-tab-title="${t`Changelog`}" class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-c-card">
<div class="pf-c-card__body">
<ak-object-changelog
targetModelPk=${this.source.pk || ""}
targetModelApp="authentik_sources_plex"
targetModelName="plexsource">
</ak-object-changelog>
</div>
</div>
</section>
<div slot="page-policy-binding" data-tab-title="${t`Policy Bindings`}" class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-c-card">
<div class="pf-c-card__title">${t`These bindings control which users can access this source.
You can only use policies here as access is checked before the user is authenticated.`}</div>
<div class="pf-c-card__body">
<ak-bound-policies-list .target=${this.source.pk} ?policyOnly=${true}>
</ak-bound-policies-list>
</div>
</div>
</section>
<section
slot="page-changelog"
data-tab-title="${t`Changelog`}"
class="pf-c-page__main-section pf-m-no-padding-mobile"
>
<div class="pf-c-card">
<div class="pf-c-card__body">
<ak-object-changelog
targetModelPk=${this.source.pk || ""}
targetModelApp="authentik_sources_plex"
targetModelName="plexsource"
>
</ak-object-changelog>
</div>
</div>
</ak-tabs>`;
</section>
<div
slot="page-policy-binding"
data-tab-title="${t`Policy Bindings`}"
class="pf-c-page__main-section pf-m-no-padding-mobile"
>
<div class="pf-c-card">
<div class="pf-c-card__title">
${t`These bindings control which users can access this source.
You can only use policies here as access is checked before the user is authenticated.`}
</div>
<div class="pf-c-card__body">
<ak-bound-policies-list .target=${this.source.pk} ?policyOnly=${true}>
</ak-bound-policies-list>
</div>
</div>
</div>
</ak-tabs>`;
}
}

View File

@ -1,4 +1,14 @@
import { SAMLSource, SourcesApi, BindingTypeEnum, NameIdPolicyEnum, CryptoApi, DigestAlgorithmEnum, SignatureAlgorithmEnum, FlowsApi, FlowsInstancesListDesignationEnum } from "authentik-api";
import {
SAMLSource,
SourcesApi,
BindingTypeEnum,
NameIdPolicyEnum,
CryptoApi,
DigestAlgorithmEnum,
SignatureAlgorithmEnum,
FlowsApi,
FlowsInstancesListDesignationEnum,
} from "authentik-api";
import { t } from "@lingui/macro";
import { customElement } from "lit-element";
import { html, TemplateResult } from "lit-html";
@ -12,7 +22,6 @@ import { ModelForm } from "../../../elements/forms/ModelForm";
@customElement("ak-source-saml-form")
export class SAMLSourceForm extends ModelForm<SAMLSource, string> {
loadInstance(pk: string): Promise<SAMLSource> {
return new SourcesApi(DEFAULT_CONFIG).sourcesSamlRetrieve({
slug: pk,
@ -31,127 +40,190 @@ export class SAMLSourceForm extends ModelForm<SAMLSource, string> {
if (this.instance) {
return new SourcesApi(DEFAULT_CONFIG).sourcesSamlUpdate({
slug: this.instance.slug,
sAMLSourceRequest: data
sAMLSourceRequest: data,
});
} else {
return new SourcesApi(DEFAULT_CONFIG).sourcesSamlCreate({
sAMLSourceRequest: data
sAMLSourceRequest: data,
});
}
};
renderForm(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
<ak-form-element-horizontal
label=${t`Name`}
?required=${true}
name="name">
<input type="text" value="${ifDefined(this.instance?.name)}" class="pf-c-form-control" required>
<ak-form-element-horizontal label=${t`Name`} ?required=${true} name="name">
<input
type="text"
value="${ifDefined(this.instance?.name)}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Slug`}
?required=${true}
name="slug">
<input type="text" value="${ifDefined(this.instance?.slug)}" class="pf-c-form-control" required>
<ak-form-element-horizontal label=${t`Slug`} ?required=${true} name="slug">
<input
type="text"
value="${ifDefined(this.instance?.slug)}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal name="enabled">
<div class="pf-c-check">
<input type="checkbox" class="pf-c-check__input" ?checked=${first(this.instance?.enabled, true)}>
<label class="pf-c-check__label">
${t`Enabled`}
</label>
<input
type="checkbox"
class="pf-c-check__input"
?checked=${first(this.instance?.enabled, true)}
/>
<label class="pf-c-check__label"> ${t`Enabled`} </label>
</div>
</ak-form-element-horizontal>
<ak-form-group .expanded=${true}>
<span slot="header">
${t`Protocol settings`}
</span>
<span slot="header"> ${t`Protocol settings`} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${t`SSO URL`}
?required=${true}
name="ssoUrl">
<input type="text" value="${ifDefined(this.instance?.ssoUrl)}" class="pf-c-form-control" required>
<p class="pf-c-form__helper-text">${t`URL that the initial Login request is sent to.`}</p>
<ak-form-element-horizontal label=${t`SSO URL`} ?required=${true} name="ssoUrl">
<input
type="text"
value="${ifDefined(this.instance?.ssoUrl)}"
class="pf-c-form-control"
required
/>
<p class="pf-c-form__helper-text">
${t`URL that the initial Login request is sent to.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`SLO URL`}
name="sloUrl">
<input type="text" value="${ifDefined(this.instance?.sloUrl || "")}" class="pf-c-form-control">
<p class="pf-c-form__helper-text">${t`Optional URL if the IDP supports Single-Logout.`}</p>
<ak-form-element-horizontal label=${t`SLO URL`} name="sloUrl">
<input
type="text"
value="${ifDefined(this.instance?.sloUrl || "")}"
class="pf-c-form-control"
/>
<p class="pf-c-form__helper-text">
${t`Optional URL if the IDP supports Single-Logout.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Issuer`}
name="issuer">
<input type="text" value="${ifDefined(this.instance?.issuer)}" class="pf-c-form-control">
<p class="pf-c-form__helper-text">${t`Also known as Entity ID. Defaults the Metadata URL.`}</p>
<ak-form-element-horizontal label=${t`Issuer`} name="issuer">
<input
type="text"
value="${ifDefined(this.instance?.issuer)}"
class="pf-c-form-control"
/>
<p class="pf-c-form__helper-text">
${t`Also known as Entity ID. Defaults the Metadata URL.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Binding Type`}
?required=${true}
name="bindingType">
name="bindingType"
>
<select class="pf-c-form-control">
<option value=${BindingTypeEnum.Redirect} ?selected=${this.instance?.bindingType === BindingTypeEnum.Redirect}>
<option
value=${BindingTypeEnum.Redirect}
?selected=${this.instance?.bindingType === BindingTypeEnum.Redirect}
>
${t`Redirect binding`}
</option>
<option value=${BindingTypeEnum.PostAuto} ?selected=${this.instance?.bindingType === BindingTypeEnum.PostAuto}>
<option
value=${BindingTypeEnum.PostAuto}
?selected=${this.instance?.bindingType === BindingTypeEnum.PostAuto}
>
${t`Post binding (auto-submit)`}
</option>
<option value=${BindingTypeEnum.Post} ?selected=${this.instance?.bindingType === BindingTypeEnum.Post}>
<option
value=${BindingTypeEnum.Post}
?selected=${this.instance?.bindingType === BindingTypeEnum.Post}
>
${t`Post binding`}
</option>
</select>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Signing keypair`}
name="signingKp">
<ak-form-element-horizontal label=${t`Signing keypair`} name="signingKp">
<select class="pf-c-form-control">
<option value="" ?selected=${this.instance?.signingKp === undefined}>---------</option>
${until(new CryptoApi(DEFAULT_CONFIG).cryptoCertificatekeypairsList({
ordering: "pk",
}).then(keys => {
return keys.results.map(key => {
return html`<option value=${ifDefined(key.pk)} ?selected=${this.instance?.signingKp === key.pk}>${key.name}</option>`;
});
}), html`<option>${t`Loading...`}</option>`)}
<option value="" ?selected=${this.instance?.signingKp === undefined}>
---------
</option>
${until(
new CryptoApi(DEFAULT_CONFIG)
.cryptoCertificatekeypairsList({
ordering: "pk",
})
.then((keys) => {
return keys.results.map((key) => {
return html`<option
value=${ifDefined(key.pk)}
?selected=${this.instance?.signingKp === key.pk}
>
${key.name}
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
<p class="pf-c-form__helper-text">${t`Keypair which is used to sign outgoing requests. Leave empty to disable signing.`}</p>
<p class="pf-c-form__helper-text">
${t`Keypair which is used to sign outgoing requests. Leave empty to disable signing.`}
</p>
</ak-form-element-horizontal>
</div>
</ak-form-group>
<ak-form-group>
<span slot="header">
${t`Advanced protocol settings`}
</span>
<span slot="header"> ${t`Advanced protocol settings`} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal name="allowIdpInitiated">
<div class="pf-c-check">
<input type="checkbox" class="pf-c-check__input" ?checked=${first(this.instance?.allowIdpInitiated, false)}>
<input
type="checkbox"
class="pf-c-check__input"
?checked=${first(this.instance?.allowIdpInitiated, false)}
/>
<label class="pf-c-check__label">
${t` Allow IDP-initiated logins`}
</label>
</div>
<p class="pf-c-form__helper-text">${t`Allows authentication flows initiated by the IdP. This can be a security risk, as no validation of the request ID is done.`}</p>
<p class="pf-c-form__helper-text">
${t`Allows authentication flows initiated by the IdP. This can be a security risk, as no validation of the request ID is done.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`NameID Policy`}
?required=${true}
name="nameIdPolicy">
name="nameIdPolicy"
>
<select class="pf-c-form-control">
<option value=${NameIdPolicyEnum._20nameidFormatpersistent} ?selected=${this.instance?.nameIdPolicy === NameIdPolicyEnum._20nameidFormatpersistent}>
<option
value=${NameIdPolicyEnum._20nameidFormatpersistent}
?selected=${this.instance?.nameIdPolicy ===
NameIdPolicyEnum._20nameidFormatpersistent}
>
${t`Persistent`}
</option>
<option value=${NameIdPolicyEnum._11nameidFormatemailAddress} ?selected=${this.instance?.nameIdPolicy === NameIdPolicyEnum._11nameidFormatemailAddress}>
<option
value=${NameIdPolicyEnum._11nameidFormatemailAddress}
?selected=${this.instance?.nameIdPolicy ===
NameIdPolicyEnum._11nameidFormatemailAddress}
>
${t`Email address`}
</option>
<option value=${NameIdPolicyEnum._20nameidFormatWindowsDomainQualifiedName} ?selected=${this.instance?.nameIdPolicy === NameIdPolicyEnum._20nameidFormatWindowsDomainQualifiedName}>
<option
value=${NameIdPolicyEnum._20nameidFormatWindowsDomainQualifiedName}
?selected=${this.instance?.nameIdPolicy ===
NameIdPolicyEnum._20nameidFormatWindowsDomainQualifiedName}
>
${t`Windows`}
</option>
<option value=${NameIdPolicyEnum._20nameidFormatX509SubjectName} ?selected=${this.instance?.nameIdPolicy === NameIdPolicyEnum._20nameidFormatX509SubjectName}>
<option
value=${NameIdPolicyEnum._20nameidFormatX509SubjectName}
?selected=${this.instance?.nameIdPolicy ===
NameIdPolicyEnum._20nameidFormatX509SubjectName}
>
${t`X509 Subject`}
</option>
<option value=${NameIdPolicyEnum._20nameidFormattransient} ?selected=${this.instance?.nameIdPolicy === NameIdPolicyEnum._20nameidFormattransient}>
<option
value=${NameIdPolicyEnum._20nameidFormattransient}
?selected=${this.instance?.nameIdPolicy ===
NameIdPolicyEnum._20nameidFormattransient}
>
${t`Transient`}
</option>
</select>
@ -159,25 +231,51 @@ export class SAMLSourceForm extends ModelForm<SAMLSource, string> {
<ak-form-element-horizontal
label=${t`Delete temporary users after`}
?required=${true}
name="temporaryUserDeleteAfter">
<input type="text" value="${this.instance?.temporaryUserDeleteAfter || "days=1"}" class="pf-c-form-control" required>
<p class="pf-c-form__helper-text">${t`Time offset when temporary users should be deleted. This only applies if your IDP uses the NameID Format 'transient', and the user doesn't log out manually. (Format: hours=1;minutes=2;seconds=3).`}</p>
name="temporaryUserDeleteAfter"
>
<input
type="text"
value="${this.instance?.temporaryUserDeleteAfter || "days=1"}"
class="pf-c-form-control"
required
/>
<p class="pf-c-form__helper-text">
${t`Time offset when temporary users should be deleted. This only applies if your IDP uses the NameID Format 'transient', and the user doesn't log out manually. (Format: hours=1;minutes=2;seconds=3).`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Digest algorithm`}
?required=${true}
name="digestAlgorithm">
name="digestAlgorithm"
>
<select class="pf-c-form-control">
<option value=${DigestAlgorithmEnum._200009Xmldsigsha1} ?selected=${this.instance?.digestAlgorithm === DigestAlgorithmEnum._200009Xmldsigsha1}>
<option
value=${DigestAlgorithmEnum._200009Xmldsigsha1}
?selected=${this.instance?.digestAlgorithm ===
DigestAlgorithmEnum._200009Xmldsigsha1}
>
${t`SHA1`}
</option>
<option value=${DigestAlgorithmEnum._200104Xmlencsha256} ?selected=${this.instance?.digestAlgorithm === DigestAlgorithmEnum._200104Xmlencsha256 || this.instance?.digestAlgorithm === undefined}>
<option
value=${DigestAlgorithmEnum._200104Xmlencsha256}
?selected=${this.instance?.digestAlgorithm ===
DigestAlgorithmEnum._200104Xmlencsha256 ||
this.instance?.digestAlgorithm === undefined}
>
${t`SHA256`}
</option>
<option value=${DigestAlgorithmEnum._200104XmldsigMoresha384} ?selected=${this.instance?.digestAlgorithm === DigestAlgorithmEnum._200104XmldsigMoresha384}>
<option
value=${DigestAlgorithmEnum._200104XmldsigMoresha384}
?selected=${this.instance?.digestAlgorithm ===
DigestAlgorithmEnum._200104XmldsigMoresha384}
>
${t`SHA384`}
</option>
<option value=${DigestAlgorithmEnum._200104Xmlencsha512} ?selected=${this.instance?.digestAlgorithm === DigestAlgorithmEnum._200104Xmlencsha512}>
<option
value=${DigestAlgorithmEnum._200104Xmlencsha512}
?selected=${this.instance?.digestAlgorithm ===
DigestAlgorithmEnum._200104Xmlencsha512}
>
${t`SHA512`}
</option>
</select>
@ -185,21 +283,43 @@ export class SAMLSourceForm extends ModelForm<SAMLSource, string> {
<ak-form-element-horizontal
label=${t`Signature algorithm`}
?required=${true}
name="signatureAlgorithm">
name="signatureAlgorithm"
>
<select class="pf-c-form-control">
<option value=${SignatureAlgorithmEnum._200009XmldsigrsaSha1} ?selected=${this.instance?.signatureAlgorithm === SignatureAlgorithmEnum._200009XmldsigrsaSha1}>
<option
value=${SignatureAlgorithmEnum._200009XmldsigrsaSha1}
?selected=${this.instance?.signatureAlgorithm ===
SignatureAlgorithmEnum._200009XmldsigrsaSha1}
>
${t`RSA-SHA1`}
</option>
<option value=${SignatureAlgorithmEnum._200104XmldsigMorersaSha256} ?selected=${this.instance?.signatureAlgorithm === SignatureAlgorithmEnum._200104XmldsigMorersaSha256 || this.instance?.signatureAlgorithm === undefined}>
<option
value=${SignatureAlgorithmEnum._200104XmldsigMorersaSha256}
?selected=${this.instance?.signatureAlgorithm ===
SignatureAlgorithmEnum._200104XmldsigMorersaSha256 ||
this.instance?.signatureAlgorithm === undefined}
>
${t`RSA-SHA256`}
</option>
<option value=${SignatureAlgorithmEnum._200104XmldsigMorersaSha384} ?selected=${this.instance?.signatureAlgorithm === SignatureAlgorithmEnum._200104XmldsigMorersaSha384}>
<option
value=${SignatureAlgorithmEnum._200104XmldsigMorersaSha384}
?selected=${this.instance?.signatureAlgorithm ===
SignatureAlgorithmEnum._200104XmldsigMorersaSha384}
>
${t`RSA-SHA384`}
</option>
<option value=${SignatureAlgorithmEnum._200104XmldsigMorersaSha512} ?selected=${this.instance?.signatureAlgorithm === SignatureAlgorithmEnum._200104XmldsigMorersaSha512}>
<option
value=${SignatureAlgorithmEnum._200104XmldsigMorersaSha512}
?selected=${this.instance?.signatureAlgorithm ===
SignatureAlgorithmEnum._200104XmldsigMorersaSha512}
>
${t`RSA-SHA512`}
</option>
<option value=${SignatureAlgorithmEnum._200009XmldsigdsaSha1} ?selected=${this.instance?.signatureAlgorithm === SignatureAlgorithmEnum._200009XmldsigdsaSha1}>
<option
value=${SignatureAlgorithmEnum._200009XmldsigdsaSha1}
?selected=${this.instance?.signatureAlgorithm ===
SignatureAlgorithmEnum._200009XmldsigdsaSha1}
>
${t`DSA-SHA1`}
</option>
</select>
@ -207,73 +327,124 @@ export class SAMLSourceForm extends ModelForm<SAMLSource, string> {
</div>
</ak-form-group>
<ak-form-group>
<span slot="header">
${t`Flow settings`}
</span>
<span slot="header"> ${t`Flow settings`} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${t`Pre-authentication flow`}
?required=${true}
name="preAuthenticationFlow">
name="preAuthenticationFlow"
>
<select class="pf-c-form-control">
${until(new FlowsApi(DEFAULT_CONFIG).flowsInstancesList({
ordering: "pk",
designation: FlowsInstancesListDesignationEnum.StageConfiguration,
}).then(flows => {
return flows.results.map(flow => {
let selected = this.instance?.preAuthenticationFlow === flow.pk;
if (!this.instance?.pk && !this.instance?.preAuthenticationFlow && flow.slug === "default-source-pre-authentication") {
selected = true;
}
return html`<option value=${ifDefined(flow.pk)} ?selected=${selected}>${flow.name} (${flow.slug})</option>`;
});
}), html`<option>${t`Loading...`}</option>`)}
${until(
new FlowsApi(DEFAULT_CONFIG)
.flowsInstancesList({
ordering: "pk",
designation:
FlowsInstancesListDesignationEnum.StageConfiguration,
})
.then((flows) => {
return flows.results.map((flow) => {
let selected =
this.instance?.preAuthenticationFlow === flow.pk;
if (
!this.instance?.pk &&
!this.instance?.preAuthenticationFlow &&
flow.slug === "default-source-pre-authentication"
) {
selected = true;
}
return html`<option
value=${ifDefined(flow.pk)}
?selected=${selected}
>
${flow.name} (${flow.slug})
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
<p class="pf-c-form__helper-text">${t`Flow used before authentication.`}</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Authentication flow`}
?required=${true}
name="authenticationFlow">
name="authenticationFlow"
>
<select class="pf-c-form-control">
${until(new FlowsApi(DEFAULT_CONFIG).flowsInstancesList({
ordering: "pk",
designation: FlowsInstancesListDesignationEnum.Authentication,
}).then(flows => {
return flows.results.map(flow => {
let selected = this.instance?.authenticationFlow === flow.pk;
if (!this.instance?.pk && !this.instance?.authenticationFlow && flow.slug === "default-source-authentication") {
selected = true;
}
return html`<option value=${ifDefined(flow.pk)} ?selected=${selected}>${flow.name} (${flow.slug})</option>`;
});
}), html`<option>${t`Loading...`}</option>`)}
${until(
new FlowsApi(DEFAULT_CONFIG)
.flowsInstancesList({
ordering: "pk",
designation:
FlowsInstancesListDesignationEnum.Authentication,
})
.then((flows) => {
return flows.results.map((flow) => {
let selected =
this.instance?.authenticationFlow === flow.pk;
if (
!this.instance?.pk &&
!this.instance?.authenticationFlow &&
flow.slug === "default-source-authentication"
) {
selected = true;
}
return html`<option
value=${ifDefined(flow.pk)}
?selected=${selected}
>
${flow.name} (${flow.slug})
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
<p class="pf-c-form__helper-text">${t`Flow to use when authenticating existing users.`}</p>
<p class="pf-c-form__helper-text">
${t`Flow to use when authenticating existing users.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Enrollment flow`}
?required=${true}
name="enrollmentFlow">
name="enrollmentFlow"
>
<select class="pf-c-form-control">
${until(new FlowsApi(DEFAULT_CONFIG).flowsInstancesList({
ordering: "pk",
designation: FlowsInstancesListDesignationEnum.Enrollment,
}).then(flows => {
return flows.results.map(flow => {
let selected = this.instance?.enrollmentFlow === flow.pk;
if (!this.instance?.pk && !this.instance?.enrollmentFlow && flow.slug === "default-source-enrollment") {
selected = true;
}
return html`<option value=${ifDefined(flow.pk)} ?selected=${selected}>${flow.name} (${flow.slug})</option>`;
});
}), html`<option>${t`Loading...`}</option>`)}
${until(
new FlowsApi(DEFAULT_CONFIG)
.flowsInstancesList({
ordering: "pk",
designation: FlowsInstancesListDesignationEnum.Enrollment,
})
.then((flows) => {
return flows.results.map((flow) => {
let selected =
this.instance?.enrollmentFlow === flow.pk;
if (
!this.instance?.pk &&
!this.instance?.enrollmentFlow &&
flow.slug === "default-source-enrollment"
) {
selected = true;
}
return html`<option
value=${ifDefined(flow.pk)}
?selected=${selected}
>
${flow.name} (${flow.slug})
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
<p class="pf-c-form__helper-text">${t`Flow to use when enrolling new users.`}</p>
<p class="pf-c-form__helper-text">
${t`Flow to use when enrolling new users.`}
</p>
</ak-form-element-horizontal>
</div>
</ak-form-group>
</form>`;
}
}

View File

@ -28,21 +28,34 @@ import { ifDefined } from "lit-html/directives/if-defined";
@customElement("ak-source-saml-view")
export class SAMLSourceViewPage extends LitElement {
@property({ type: String })
set sourceSlug(slug: string) {
new SourcesApi(DEFAULT_CONFIG).sourcesSamlRetrieve({
slug: slug
}).then((source) => {
this.source = source;
});
new SourcesApi(DEFAULT_CONFIG)
.sourcesSamlRetrieve({
slug: slug,
})
.then((source) => {
this.source = source;
});
}
@property({ attribute: false })
source?: SAMLSource;
static get styles(): CSSResult[] {
return [PFBase, PFPage, PFFlex, PFButton, PFDisplay, PFGallery, PFContent, PFCard, PFDescriptionList, PFSizing, AKGlobal];
return [
PFBase,
PFPage,
PFFlex,
PFButton,
PFDisplay,
PFGallery,
PFContent,
PFCard,
PFDescriptionList,
PFSizing,
AKGlobal,
];
}
constructor() {
@ -58,111 +71,154 @@ export class SAMLSourceViewPage extends LitElement {
return html``;
}
return html`<ak-tabs>
<section slot="page-overview" data-tab-title="${t`Overview`}" class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-u-display-flex pf-u-justify-content-center">
<div class="pf-u-w-75">
<div class="pf-c-card">
<div class="pf-c-card__body">
<dl class="pf-c-description-list pf-m-3-col-on-lg">
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${t`Name`}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">${this.source.name}</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${t`SSO URL`}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">${this.source.ssoUrl}</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${t`SLO URL`}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">${this.source.sloUrl}</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${t`Issuer`}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">${this.source.issuer}</div>
</dd>
</div>
</dl>
</div>
<div class="pf-c-card__footer">
<ak-forms-modal>
<span slot="submit">
${t`Update`}
</span>
<span slot="header">
${t`Update SAML Source`}
</span>
<ak-source-saml-form
slot="form"
.instancePk=${this.source.slug}>
</ak-source-saml-form>
<button slot="trigger" class="pf-c-button pf-m-primary">
${t`Edit`}
</button>
</ak-forms-modal>
</div>
<section
slot="page-overview"
data-tab-title="${t`Overview`}"
class="pf-c-page__main-section pf-m-no-padding-mobile"
>
<div class="pf-u-display-flex pf-u-justify-content-center">
<div class="pf-u-w-75">
<div class="pf-c-card">
<div class="pf-c-card__body">
<dl class="pf-c-description-list pf-m-3-col-on-lg">
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text"
>${t`Name`}</span
>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
${this.source.name}
</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text"
>${t`SSO URL`}</span
>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
${this.source.ssoUrl}
</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text"
>${t`SLO URL`}</span
>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
${this.source.sloUrl}
</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text"
>${t`Issuer`}</span
>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
${this.source.issuer}
</div>
</dd>
</div>
</dl>
</div>
</div>
</div>
</section>
<section slot="page-changelog" data-tab-title="${t`Changelog`}" class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-c-card">
<div class="pf-c-card__body">
<ak-object-changelog
targetModelPk=${this.source.pk || ""}
targetModelApp="authentik_sources_saml"
targetModelName="samlsource">
</ak-object-changelog>
</div>
</div>
</section>
<section slot="page-metadata" data-tab-title="${t`Metadata`}" class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-u-display-flex pf-u-justify-content-center">
<div class="pf-u-w-75">
<div class="pf-c-card">
${until(new SourcesApi(DEFAULT_CONFIG).sourcesSamlMetadataRetrieve({
slug: this.source.slug,
}).then(m => {
return html`
<div class="pf-c-card__body">
<ak-codemirror mode="xml" ?readOnly=${true} value="${ifDefined(m.metadata)}"></ak-codemirror>
</div>
<div class="pf-c-card__footer">
<a class="pf-c-button pf-m-primary" target="_blank" href=${ifDefined(m.downloadUrl)}>
${t`Download`}
</a>
</div>
`;
})
)}
<div class="pf-c-card__footer">
<ak-forms-modal>
<span slot="submit"> ${t`Update`} </span>
<span slot="header"> ${t`Update SAML Source`} </span>
<ak-source-saml-form
slot="form"
.instancePk=${this.source.slug}
>
</ak-source-saml-form>
<button slot="trigger" class="pf-c-button pf-m-primary">
${t`Edit`}
</button>
</ak-forms-modal>
</div>
</div>
</div>
</section>
<div slot="page-policy-bindings" data-tab-title="${t`Policy Bindings`}" class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-c-card">
<div class="pf-c-card__title">${t`These bindings control which users can access this source.
You can only use policies here as access is checked before the user is authenticated.`}</div>
<div class="pf-c-card__body">
<ak-bound-policies-list .target=${this.source.pk} ?policyOnly=${true}>
</ak-bound-policies-list>
</div>
</div>
</div>
</ak-tabs>`;
</section>
<section
slot="page-changelog"
data-tab-title="${t`Changelog`}"
class="pf-c-page__main-section pf-m-no-padding-mobile"
>
<div class="pf-c-card">
<div class="pf-c-card__body">
<ak-object-changelog
targetModelPk=${this.source.pk || ""}
targetModelApp="authentik_sources_saml"
targetModelName="samlsource"
>
</ak-object-changelog>
</div>
</div>
</section>
<section
slot="page-metadata"
data-tab-title="${t`Metadata`}"
class="pf-c-page__main-section pf-m-no-padding-mobile"
>
<div class="pf-u-display-flex pf-u-justify-content-center">
<div class="pf-u-w-75">
<div class="pf-c-card">
${until(
new SourcesApi(DEFAULT_CONFIG)
.sourcesSamlMetadataRetrieve({
slug: this.source.slug,
})
.then((m) => {
return html`
<div class="pf-c-card__body">
<ak-codemirror
mode="xml"
?readOnly=${true}
value="${ifDefined(m.metadata)}"
></ak-codemirror>
</div>
<div class="pf-c-card__footer">
<a
class="pf-c-button pf-m-primary"
target="_blank"
href=${ifDefined(m.downloadUrl)}
>
${t`Download`}
</a>
</div>
`;
}),
)}
</div>
</div>
</div>
</section>
<div
slot="page-policy-bindings"
data-tab-title="${t`Policy Bindings`}"
class="pf-c-page__main-section pf-m-no-padding-mobile"
>
<div class="pf-c-card">
<div class="pf-c-card__title">
${t`These bindings control which users can access this source.
You can only use policies here as access is checked before the user is authenticated.`}
</div>
<div class="pf-c-card__body">
<ak-bound-policies-list .target=${this.source.pk} ?policyOnly=${true}>
</ak-bound-policies-list>
</div>
</div>
</div>
</ak-tabs>`;
}
}

View File

@ -62,11 +62,7 @@ export class StageListPage extends TablePage<Stage> {
}
columns(): TableColumn[] {
return [
new TableColumn(t`Name`, "name"),
new TableColumn(t`Flows`),
new TableColumn(""),
];
return [new TableColumn(t`Name`, "name"), new TableColumn(t`Flows`), new TableColumn("")];
}
row(item: Stage): TemplateResult[] {
@ -80,78 +76,66 @@ export class StageListPage extends TablePage<Stage> {
<code>${flow.slug}</code>
</a>`;
})}`,
html`
<ak-forms-modal>
<span slot="submit">
${t`Update`}
</span>
<span slot="header">
${t`Update ${item.verboseName}`}
</span>
<ak-proxy-form
slot="form"
.args=${{
"instancePk": item.pk
html` <ak-forms-modal>
<span slot="submit"> ${t`Update`} </span>
<span slot="header"> ${t`Update ${item.verboseName}`} </span>
<ak-proxy-form
slot="form"
.args=${{
instancePk: item.pk,
}}
type=${ifDefined(item.component)}
>
</ak-proxy-form>
<button slot="trigger" class="pf-c-button pf-m-secondary">${t`Edit`}</button>
</ak-forms-modal>
<ak-forms-delete
.obj=${item}
objectLabel=${item.verboseName || ""}
.usedBy=${() => {
return new StagesApi(DEFAULT_CONFIG).stagesAllUsedByList({
stageUuid: item.pk,
});
}}
type=${ifDefined(item.component)}>
</ak-proxy-form>
<button slot="trigger" class="pf-c-button pf-m-secondary">
${t`Edit`}
</button>
</ak-forms-modal>
<ak-forms-delete
.obj=${item}
objectLabel=${item.verboseName || ""}
.usedBy=${() => {
return new StagesApi(DEFAULT_CONFIG).stagesAllUsedByList({
stageUuid: item.pk
});
}}
.delete=${() => {
return new StagesApi(DEFAULT_CONFIG).stagesAllDestroy({
stageUuid: item.pk
});
}}>
<button slot="trigger" class="pf-c-button pf-m-danger">
${t`Delete`}
</button>
</ak-forms-delete>`,
.delete=${() => {
return new StagesApi(DEFAULT_CONFIG).stagesAllDestroy({
stageUuid: item.pk,
});
}}
>
<button slot="trigger" class="pf-c-button pf-m-danger">${t`Delete`}</button>
</ak-forms-delete>`,
];
}
renderToolbar(): TemplateResult {
return html`
<ak-dropdown class="pf-c-dropdown">
<button class="pf-m-primary pf-c-dropdown__toggle" type="button">
<span class="pf-c-dropdown__toggle-text">${t`Create`}</span>
<i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i>
</button>
<ul class="pf-c-dropdown__menu" hidden>
${until(new StagesApi(DEFAULT_CONFIG).stagesAllTypesList().then((types) => {
return types.map((type) => {
return html`<li>
<ak-forms-modal>
<span slot="submit">
${t`Create`}
</span>
<span slot="header">
${t`Create ${type.name}`}
</span>
<ak-proxy-form
slot="form"
type=${type.component}>
</ak-proxy-form>
<button slot="trigger" class="pf-c-dropdown__menu-item">
${type.name}<br>
<small>${type.description}</small>
</button>
</ak-forms-modal>
</li>`;
});
}), html`<ak-spinner></ak-spinner>`)}
</ul>
</ak-dropdown>
${super.renderToolbar()}`;
return html` <ak-dropdown class="pf-c-dropdown">
<button class="pf-m-primary pf-c-dropdown__toggle" type="button">
<span class="pf-c-dropdown__toggle-text">${t`Create`}</span>
<i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i>
</button>
<ul class="pf-c-dropdown__menu" hidden>
${until(
new StagesApi(DEFAULT_CONFIG).stagesAllTypesList().then((types) => {
return types.map((type) => {
return html`<li>
<ak-forms-modal>
<span slot="submit"> ${t`Create`} </span>
<span slot="header"> ${t`Create ${type.name}`} </span>
<ak-proxy-form slot="form" type=${type.component}>
</ak-proxy-form>
<button slot="trigger" class="pf-c-dropdown__menu-item">
${type.name}<br />
<small>${type.description}</small>
</button>
</ak-forms-modal>
</li>`;
});
}),
html`<ak-spinner></ak-spinner>`,
)}
</ul>
</ak-dropdown>
${super.renderToolbar()}`;
}
}

View File

@ -1,4 +1,10 @@
import { FlowsApi, AuthenticatorDuoStage, StagesApi, FlowsInstancesListDesignationEnum, AuthenticatorDuoStageRequest } from "authentik-api";
import {
FlowsApi,
AuthenticatorDuoStage,
StagesApi,
FlowsInstancesListDesignationEnum,
AuthenticatorDuoStageRequest,
} from "authentik-api";
import { t } from "@lingui/macro";
import { customElement } from "lit-element";
import { html, TemplateResult } from "lit-html";
@ -12,7 +18,6 @@ import { ModelForm } from "../../../elements/forms/ModelForm";
@customElement("ak-stage-authenticator-duo-form")
export class AuthenticatorDuoStageForm extends ModelForm<AuthenticatorDuoStage, string> {
loadInstance(pk: string): Promise<AuthenticatorDuoStage> {
return new StagesApi(DEFAULT_CONFIG).stagesAuthenticatorDuoRetrieve({
stageUuid: pk,
@ -31,11 +36,11 @@ export class AuthenticatorDuoStageForm extends ModelForm<AuthenticatorDuoStage,
if (this.instance) {
return new StagesApi(DEFAULT_CONFIG).stagesAuthenticatorDuoPartialUpdate({
stageUuid: this.instance.pk || "",
patchedAuthenticatorDuoStageRequest: data
patchedAuthenticatorDuoStageRequest: data,
});
} else {
return new StagesApi(DEFAULT_CONFIG).stagesAuthenticatorDuoCreate({
authenticatorDuoStageRequest: data as unknown as AuthenticatorDuoStageRequest
authenticatorDuoStageRequest: data as unknown as AuthenticatorDuoStageRequest,
});
}
};
@ -45,53 +50,84 @@ export class AuthenticatorDuoStageForm extends ModelForm<AuthenticatorDuoStage,
<div class="form-help-text">
${t`Stage used to configure a duo-based authenticator. This stage should be used for configuration flows.`}
</div>
<ak-form-element-horizontal
label=${t`Name`}
?required=${true}
name="name">
<input type="text" value="${ifDefined(this.instance?.name || "")}" class="pf-c-form-control" required>
<ak-form-element-horizontal label=${t`Name`} ?required=${true} name="name">
<input
type="text"
value="${ifDefined(this.instance?.name || "")}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-group .expanded=${true}>
<span slot="header">
${t`Stage-specific settings`}
</span>
<span slot="header"> ${t`Stage-specific settings`} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${t`Integration key`}
?required=${true}
name="clientId">
<input type="text" value="${first(this.instance?.clientId, "")}" class="pf-c-form-control" required>
name="clientId"
>
<input
type="text"
value="${first(this.instance?.clientId, "")}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Secret key`}
?required=${true}
?writeOnly=${this.instance !== undefined}
name="clientSecret">
<input type="text" value="" class="pf-c-form-control" required>
name="clientSecret"
>
<input type="text" value="" class="pf-c-form-control" required />
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`API Hostname`}
?required=${true}
name="apiHostname">
<input type="text" value="${first(this.instance?.apiHostname, "")}" class="pf-c-form-control" required>
name="apiHostname"
>
<input
type="text"
value="${first(this.instance?.apiHostname, "")}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Configuration flow`}
name="configureFlow">
<ak-form-element-horizontal label=${t`Configuration flow`} name="configureFlow">
<select class="pf-c-form-control">
<option value="" ?selected=${this.instance?.configureFlow === undefined}>---------</option>
${until(new FlowsApi(DEFAULT_CONFIG).flowsInstancesList({
ordering: "pk",
designation: FlowsInstancesListDesignationEnum.StageConfiguration,
}).then(flows => {
return flows.results.map(flow => {
let selected = this.instance?.configureFlow === flow.pk;
if (!this.instance?.pk && !this.instance?.configureFlow && flow.slug === "default-otp-time-configure") {
selected = true;
}
return html`<option value=${ifDefined(flow.pk)} ?selected=${selected}>${flow.name} (${flow.slug})</option>`;
});
}), html`<option>${t`Loading...`}</option>`)}
<option
value=""
?selected=${this.instance?.configureFlow === undefined}
>
---------
</option>
${until(
new FlowsApi(DEFAULT_CONFIG)
.flowsInstancesList({
ordering: "pk",
designation:
FlowsInstancesListDesignationEnum.StageConfiguration,
})
.then((flows) => {
return flows.results.map((flow) => {
let selected = this.instance?.configureFlow === flow.pk;
if (
!this.instance?.pk &&
!this.instance?.configureFlow &&
flow.slug === "default-otp-time-configure"
) {
selected = true;
}
return html`<option
value=${ifDefined(flow.pk)}
?selected=${selected}
>
${flow.name} (${flow.slug})
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
<p class="pf-c-form__helper-text">
${t`Flow used by an authenticated user to configure this Stage. If empty, user will not be able to configure this stage.`}
@ -101,5 +137,4 @@ export class AuthenticatorDuoStageForm extends ModelForm<AuthenticatorDuoStage,
</ak-form-group>
</form>`;
}
}

View File

@ -1,4 +1,9 @@
import { FlowsApi, AuthenticatorStaticStage, StagesApi, FlowsInstancesListDesignationEnum } from "authentik-api";
import {
FlowsApi,
AuthenticatorStaticStage,
StagesApi,
FlowsInstancesListDesignationEnum,
} from "authentik-api";
import { t } from "@lingui/macro";
import { customElement } from "lit-element";
import { html, TemplateResult } from "lit-html";
@ -12,7 +17,6 @@ import { ModelForm } from "../../../elements/forms/ModelForm";
@customElement("ak-stage-authenticator-static-form")
export class AuthenticatorStaticStageForm extends ModelForm<AuthenticatorStaticStage, string> {
loadInstance(pk: string): Promise<AuthenticatorStaticStage> {
return new StagesApi(DEFAULT_CONFIG).stagesAuthenticatorStaticRetrieve({
stageUuid: pk,
@ -31,11 +35,11 @@ export class AuthenticatorStaticStageForm extends ModelForm<AuthenticatorStaticS
if (this.instance) {
return new StagesApi(DEFAULT_CONFIG).stagesAuthenticatorStaticUpdate({
stageUuid: this.instance.pk || "",
authenticatorStaticStageRequest: data
authenticatorStaticStageRequest: data,
});
} else {
return new StagesApi(DEFAULT_CONFIG).stagesAuthenticatorStaticCreate({
authenticatorStaticStageRequest: data
authenticatorStaticStageRequest: data,
});
}
};
@ -45,40 +49,64 @@ export class AuthenticatorStaticStageForm extends ModelForm<AuthenticatorStaticS
<div class="form-help-text">
${t`Stage used to configure a static authenticator (i.e. static tokens). This stage should be used for configuration flows.`}
</div>
<ak-form-element-horizontal
label=${t`Name`}
?required=${true}
name="name">
<input type="text" value="${ifDefined(this.instance?.name || "")}" class="pf-c-form-control" required>
<ak-form-element-horizontal label=${t`Name`} ?required=${true} name="name">
<input
type="text"
value="${ifDefined(this.instance?.name || "")}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-group .expanded=${true}>
<span slot="header">
${t`Stage-specific settings`}
</span>
<span slot="header"> ${t`Stage-specific settings`} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${t`Token count`}
?required=${true}
name="tokenCount">
<input type="text" value="${first(this.instance?.tokenCount, 6)}" class="pf-c-form-control" required>
name="tokenCount"
>
<input
type="text"
value="${first(this.instance?.tokenCount, 6)}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Configuration flow`}
name="configureFlow">
<ak-form-element-horizontal label=${t`Configuration flow`} name="configureFlow">
<select class="pf-c-form-control">
<option value="" ?selected=${this.instance?.configureFlow === undefined}>---------</option>
${until(new FlowsApi(DEFAULT_CONFIG).flowsInstancesList({
ordering: "pk",
designation: FlowsInstancesListDesignationEnum.StageConfiguration,
}).then(flows => {
return flows.results.map(flow => {
let selected = this.instance?.configureFlow === flow.pk;
if (!this.instance?.pk && !this.instance?.configureFlow && flow.slug === "default-otp-time-configure") {
selected = true;
}
return html`<option value=${ifDefined(flow.pk)} ?selected=${selected}>${flow.name} (${flow.slug})</option>`;
});
}), html`<option>${t`Loading...`}</option>`)}
<option
value=""
?selected=${this.instance?.configureFlow === undefined}
>
---------
</option>
${until(
new FlowsApi(DEFAULT_CONFIG)
.flowsInstancesList({
ordering: "pk",
designation:
FlowsInstancesListDesignationEnum.StageConfiguration,
})
.then((flows) => {
return flows.results.map((flow) => {
let selected = this.instance?.configureFlow === flow.pk;
if (
!this.instance?.pk &&
!this.instance?.configureFlow &&
flow.slug === "default-otp-time-configure"
) {
selected = true;
}
return html`<option
value=${ifDefined(flow.pk)}
?selected=${selected}
>
${flow.name} (${flow.slug})
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
<p class="pf-c-form__helper-text">
${t`Flow used by an authenticated user to configure this Stage. If empty, user will not be able to configure this stage.`}
@ -88,5 +116,4 @@ export class AuthenticatorStaticStageForm extends ModelForm<AuthenticatorStaticS
</ak-form-group>
</form>`;
}
}

View File

@ -1,4 +1,9 @@
import { FlowsApi, AuthenticatorTOTPStage, StagesApi, FlowsInstancesListDesignationEnum } from "authentik-api";
import {
FlowsApi,
AuthenticatorTOTPStage,
StagesApi,
FlowsInstancesListDesignationEnum,
} from "authentik-api";
import { t } from "@lingui/macro";
import { customElement } from "lit-element";
import { html, TemplateResult } from "lit-html";
@ -11,7 +16,6 @@ import { ModelForm } from "../../../elements/forms/ModelForm";
@customElement("ak-stage-authenticator-totp-form")
export class AuthenticatorTOTPStageForm extends ModelForm<AuthenticatorTOTPStage, string> {
loadInstance(pk: string): Promise<AuthenticatorTOTPStage> {
return new StagesApi(DEFAULT_CONFIG).stagesAuthenticatorTotpRetrieve({
stageUuid: pk,
@ -30,11 +34,11 @@ export class AuthenticatorTOTPStageForm extends ModelForm<AuthenticatorTOTPStage
if (this.instance) {
return new StagesApi(DEFAULT_CONFIG).stagesAuthenticatorTotpUpdate({
stageUuid: this.instance.pk || "",
authenticatorTOTPStageRequest: data
authenticatorTOTPStageRequest: data,
});
} else {
return new StagesApi(DEFAULT_CONFIG).stagesAuthenticatorTotpCreate({
authenticatorTOTPStageRequest: data
authenticatorTOTPStageRequest: data,
});
}
};
@ -44,21 +48,18 @@ export class AuthenticatorTOTPStageForm extends ModelForm<AuthenticatorTOTPStage
<div class="form-help-text">
${t`Stage used to configure a TOTP authenticator (i.e. Authy/Google Authenticator).`}
</div>
<ak-form-element-horizontal
label=${t`Name`}
?required=${true}
name="name">
<input type="text" value="${ifDefined(this.instance?.name || "")}" class="pf-c-form-control" required>
<ak-form-element-horizontal label=${t`Name`} ?required=${true} name="name">
<input
type="text"
value="${ifDefined(this.instance?.name || "")}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-group .expanded=${true}>
<span slot="header">
${t`Stage-specific settings`}
</span>
<span slot="header"> ${t`Stage-specific settings`} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${t`Digits`}
?required=${true}
name="digits">
<ak-form-element-horizontal label=${t`Digits`} ?required=${true} name="digits">
<select name="users" class="pf-c-form-control">
<option value="6" ?selected=${this.instance?.digits === 6}>
${t`6 digits, widely compatible`}
@ -68,29 +69,48 @@ export class AuthenticatorTOTPStageForm extends ModelForm<AuthenticatorTOTPStage
</option>
</select>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Configuration flow`}
name="configureFlow">
<ak-form-element-horizontal label=${t`Configuration flow`} name="configureFlow">
<select class="pf-c-form-control">
<option value="" ?selected=${this.instance?.configureFlow === undefined}>---------</option>
${until(new FlowsApi(DEFAULT_CONFIG).flowsInstancesList({
ordering: "pk",
designation: FlowsInstancesListDesignationEnum.StageConfiguration,
}).then(flows => {
return flows.results.map(flow => {
let selected = this.instance?.configureFlow === flow.pk;
if (!this.instance?.pk && !this.instance?.configureFlow && flow.slug === "default-otp-time-configure") {
selected = true;
}
return html`<option value=${ifDefined(flow.pk)} ?selected=${selected}>${flow.name} (${flow.slug})</option>`;
});
}), html`<option>${t`Loading...`}</option>`)}
<option
value=""
?selected=${this.instance?.configureFlow === undefined}
>
---------
</option>
${until(
new FlowsApi(DEFAULT_CONFIG)
.flowsInstancesList({
ordering: "pk",
designation:
FlowsInstancesListDesignationEnum.StageConfiguration,
})
.then((flows) => {
return flows.results.map((flow) => {
let selected = this.instance?.configureFlow === flow.pk;
if (
!this.instance?.pk &&
!this.instance?.configureFlow &&
flow.slug === "default-otp-time-configure"
) {
selected = true;
}
return html`<option
value=${ifDefined(flow.pk)}
?selected=${selected}
>
${flow.name} (${flow.slug})
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
<p class="pf-c-form__helper-text">${t`Flow used by an authenticated user to configure this Stage. If empty, user will not be able to configure this stage.`}</p>
<p class="pf-c-form__helper-text">
${t`Flow used by an authenticated user to configure this Stage. If empty, user will not be able to configure this stage.`}
</p>
</ak-form-element-horizontal>
</div>
</ak-form-group>
</form>`;
}
}

View File

@ -1,4 +1,9 @@
import { AuthenticatorValidateStage, NotConfiguredActionEnum, DeviceClassesEnum, StagesApi } from "authentik-api";
import {
AuthenticatorValidateStage,
NotConfiguredActionEnum,
DeviceClassesEnum,
StagesApi,
} from "authentik-api";
import { t } from "@lingui/macro";
import { customElement, property } from "lit-element";
import { html, TemplateResult } from "lit-html";
@ -11,14 +16,16 @@ import { ModelForm } from "../../../elements/forms/ModelForm";
@customElement("ak-stage-authenticator-validate-form")
export class AuthenticatorValidateStageForm extends ModelForm<AuthenticatorValidateStage, string> {
loadInstance(pk: string): Promise<AuthenticatorValidateStage> {
return new StagesApi(DEFAULT_CONFIG).stagesAuthenticatorValidateRetrieve({
stageUuid: pk,
}).then(stage => {
this.showConfigurationStage = stage.notConfiguredAction === NotConfiguredActionEnum.Configure;
return stage;
});
return new StagesApi(DEFAULT_CONFIG)
.stagesAuthenticatorValidateRetrieve({
stageUuid: pk,
})
.then((stage) => {
this.showConfigurationStage =
stage.notConfiguredAction === NotConfiguredActionEnum.Configure;
return stage;
});
}
@property({ type: Boolean })
@ -36,19 +43,21 @@ export class AuthenticatorValidateStageForm extends ModelForm<AuthenticatorValid
if (this.instance) {
return new StagesApi(DEFAULT_CONFIG).stagesAuthenticatorValidateUpdate({
stageUuid: this.instance.pk || "",
authenticatorValidateStageRequest: data
authenticatorValidateStageRequest: data,
});
} else {
return new StagesApi(DEFAULT_CONFIG).stagesAuthenticatorValidateCreate({
authenticatorValidateStageRequest: data
authenticatorValidateStageRequest: data,
});
}
};
isDeviceClassSelected(field: DeviceClassesEnum): boolean {
return (this.instance?.deviceClasses || []).filter(isField => {
return field === isField;
}).length > 0;
return (
(this.instance?.deviceClasses || []).filter((isField) => {
return field === isField;
}).length > 0
);
}
renderForm(): TemplateResult {
@ -56,83 +65,141 @@ export class AuthenticatorValidateStageForm extends ModelForm<AuthenticatorValid
<div class="form-help-text">
${t`Stage used to validate any authenticator. This stage should be used during authentication or authorization flows.`}
</div>
<ak-form-element-horizontal
label=${t`Name`}
?required=${true}
name="name">
<input type="text" value="${ifDefined(this.instance?.name || "")}" class="pf-c-form-control" required>
<ak-form-element-horizontal label=${t`Name`} ?required=${true} name="name">
<input
type="text"
value="${ifDefined(this.instance?.name || "")}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-group .expanded=${true}>
<span slot="header">
${t`Stage-specific settings`}
</span>
<span slot="header"> ${t`Stage-specific settings`} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${t`Device classes`}
?required=${true}
name="deviceClasses">
name="deviceClasses"
>
<select name="users" class="pf-c-form-control" multiple>
<option value=${DeviceClassesEnum.Static} ?selected=${this.isDeviceClassSelected(DeviceClassesEnum.Static)}>
<option
value=${DeviceClassesEnum.Static}
?selected=${this.isDeviceClassSelected(DeviceClassesEnum.Static)}
>
${t`Static Tokens`}
</option>
<option value=${DeviceClassesEnum.Totp} ?selected=${this.isDeviceClassSelected(DeviceClassesEnum.Totp)}>
<option
value=${DeviceClassesEnum.Totp}
?selected=${this.isDeviceClassSelected(DeviceClassesEnum.Totp)}
>
${t`TOTP Authenticators`}
</option>
<option value=${DeviceClassesEnum.Webauthn} ?selected=${this.isDeviceClassSelected(DeviceClassesEnum.Webauthn)}>
<option
value=${DeviceClassesEnum.Webauthn}
?selected=${this.isDeviceClassSelected(DeviceClassesEnum.Webauthn)}
>
${t`WebAuthn Authenticators`}
</option>
<option value=${DeviceClassesEnum.Duo} ?selected=${this.isDeviceClassSelected(DeviceClassesEnum.Duo)}>
<option
value=${DeviceClassesEnum.Duo}
?selected=${this.isDeviceClassSelected(DeviceClassesEnum.Duo)}
>
${t`Duo Authenticators`}
</option>
</select>
<p class="pf-c-form__helper-text">${t`Device classes which can be used to authenticate.`}</p>
<p class="pf-c-form__helper-text">${t`Hold control/command to select multiple items.`}</p>
<p class="pf-c-form__helper-text">
${t`Device classes which can be used to authenticate.`}
</p>
<p class="pf-c-form__helper-text">
${t`Hold control/command to select multiple items.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Not configured action`}
?required=${true}
name="notConfiguredAction">
<select class="pf-c-form-control" @change=${(ev: Event) => {
const target = ev.target as HTMLSelectElement;
if (target.selectedOptions[0].value === NotConfiguredActionEnum.Configure) {
this.showConfigurationStage = true;
} else {
this.showConfigurationStage = false;
}
}}>
<option value=${NotConfiguredActionEnum.Configure} ?selected=${this.instance?.notConfiguredAction === NotConfiguredActionEnum.Configure}>
name="notConfiguredAction"
>
<select
class="pf-c-form-control"
@change=${(ev: Event) => {
const target = ev.target as HTMLSelectElement;
if (
target.selectedOptions[0].value ===
NotConfiguredActionEnum.Configure
) {
this.showConfigurationStage = true;
} else {
this.showConfigurationStage = false;
}
}}
>
<option
value=${NotConfiguredActionEnum.Configure}
?selected=${this.instance?.notConfiguredAction ===
NotConfiguredActionEnum.Configure}
>
${t`Force the user to configure an authenticator`}
</option>
<option value=${NotConfiguredActionEnum.Deny} ?selected=${this.instance?.notConfiguredAction === NotConfiguredActionEnum.Deny}>
<option
value=${NotConfiguredActionEnum.Deny}
?selected=${this.instance?.notConfiguredAction ===
NotConfiguredActionEnum.Deny}
>
${t`Deny the user access`}
</option>
<option value=${NotConfiguredActionEnum.Skip} ?selected=${this.instance?.notConfiguredAction === NotConfiguredActionEnum.Skip}>
<option
value=${NotConfiguredActionEnum.Skip}
?selected=${this.instance?.notConfiguredAction ===
NotConfiguredActionEnum.Skip}
>
${t`Continue`}
</option>
</select>
</ak-form-element-horizontal>
${this.showConfigurationStage ? html`
<ak-form-element-horizontal
label=${t`Configuration stage`}
?required=${true}
name="configurationStage">
<select class="pf-c-form-control">
<option value="" ?selected=${this.instance?.configurationStage === undefined}>---------</option>
${until(new StagesApi(DEFAULT_CONFIG).stagesAllList({
ordering: "pk",
}).then(stages => {
return stages.results.map(stage => {
const selected = this.instance?.configurationStage === stage.pk;
return html`<option value=${ifDefined(stage.pk)} ?selected=${selected}>${stage.name} (${stage.verboseName})</option>`;
});
}), html`<option>${t`Loading...`}</option>`)}
</select>
<p class="pf-c-form__helper-text">${t`Stage used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again.`}</p>
</ak-form-element-horizontal>
`: html``}
${this.showConfigurationStage
? html`
<ak-form-element-horizontal
label=${t`Configuration stage`}
?required=${true}
name="configurationStage"
>
<select class="pf-c-form-control">
<option
value=""
?selected=${this.instance?.configurationStage ===
undefined}
>
---------
</option>
${until(
new StagesApi(DEFAULT_CONFIG)
.stagesAllList({
ordering: "pk",
})
.then((stages) => {
return stages.results.map((stage) => {
const selected =
this.instance?.configurationStage ===
stage.pk;
return html`<option
value=${ifDefined(stage.pk)}
?selected=${selected}
>
${stage.name} (${stage.verboseName})
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
<p class="pf-c-form__helper-text">
${t`Stage used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again.`}
</p>
</ak-form-element-horizontal>
`
: html``}
</div>
</ak-form-group>
</form>`;
}
}

View File

@ -9,7 +9,6 @@ import { ModelForm } from "../../../elements/forms/ModelForm";
@customElement("ak-stage-authenticator-webauthn-form")
export class AuthenticateWebAuthnStageForm extends ModelForm<AuthenticateWebAuthnStage, string> {
loadInstance(pk: string): Promise<AuthenticateWebAuthnStage> {
return new StagesApi(DEFAULT_CONFIG).stagesAuthenticatorWebauthnRetrieve({
stageUuid: pk,
@ -28,11 +27,11 @@ export class AuthenticateWebAuthnStageForm extends ModelForm<AuthenticateWebAuth
if (this.instance) {
return new StagesApi(DEFAULT_CONFIG).stagesAuthenticatorWebauthnUpdate({
stageUuid: this.instance.pk || "",
authenticateWebAuthnStageRequest: data
authenticateWebAuthnStageRequest: data,
});
} else {
return new StagesApi(DEFAULT_CONFIG).stagesAuthenticatorWebauthnCreate({
authenticateWebAuthnStageRequest: data
authenticateWebAuthnStageRequest: data,
});
}
};
@ -42,13 +41,14 @@ export class AuthenticateWebAuthnStageForm extends ModelForm<AuthenticateWebAuth
<div class="form-help-text">
${t`Stage used to configure a WebAutnn authenticator (i.e. Yubikey, FaceID/Windows Hello).`}
</div>
<ak-form-element-horizontal
label=${t`Name`}
?required=${true}
name="name">
<input type="text" value="${ifDefined(this.instance?.name || "")}" class="pf-c-form-control" required>
<ak-form-element-horizontal label=${t`Name`} ?required=${true} name="name">
<input
type="text"
value="${ifDefined(this.instance?.name || "")}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
</form>`;
}
}

View File

@ -10,7 +10,6 @@ import { ModelForm } from "../../../elements/forms/ModelForm";
@customElement("ak-stage-captcha-form")
export class CaptchaStageForm extends ModelForm<CaptchaStage, string> {
loadInstance(pk: string): Promise<CaptchaStage> {
return new StagesApi(DEFAULT_CONFIG).stagesCaptchaRetrieve({
stageUuid: pk,
@ -29,11 +28,11 @@ export class CaptchaStageForm extends ModelForm<CaptchaStage, string> {
if (this.instance) {
return new StagesApi(DEFAULT_CONFIG).stagesCaptchaPartialUpdate({
stageUuid: this.instance.pk || "",
patchedCaptchaStageRequest: data
patchedCaptchaStageRequest: data,
});
} else {
return new StagesApi(DEFAULT_CONFIG).stagesCaptchaCreate({
captchaStageRequest: data as unknown as CaptchaStageRequest
captchaStageRequest: data as unknown as CaptchaStageRequest,
});
}
};
@ -43,35 +42,45 @@ export class CaptchaStageForm extends ModelForm<CaptchaStage, string> {
<div class="form-help-text">
${t`This stage checks the user's current session against the Google reCaptcha service.`}
</div>
<ak-form-element-horizontal
label=${t`Name`}
?required=${true}
name="name">
<input type="text" value="${ifDefined(this.instance?.name || "")}" class="pf-c-form-control" required>
<ak-form-element-horizontal label=${t`Name`} ?required=${true} name="name">
<input
type="text"
value="${ifDefined(this.instance?.name || "")}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-group .expanded=${true}>
<span slot="header">
${t`Stage-specific settings`}
</span>
<span slot="header"> ${t`Stage-specific settings`} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${t`Public Key`}
?required=${true}
name="publicKey">
<input type="text" value="${ifDefined(this.instance?.publicKey || "")}" class="pf-c-form-control" required>
<p class="pf-c-form__helper-text">${t`Public key, acquired from https://www.google.com/recaptcha/intro/v3.html.`}</p>
name="publicKey"
>
<input
type="text"
value="${ifDefined(this.instance?.publicKey || "")}"
class="pf-c-form-control"
required
/>
<p class="pf-c-form__helper-text">
${t`Public key, acquired from https://www.google.com/recaptcha/intro/v3.html.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Private Key`}
?required=${true}
?writeOnly=${this.instance !== undefined}
name="privateKey">
<input type="text" value="" class="pf-c-form-control" required>
<p class="pf-c-form__helper-text">${t`Private key, acquired from https://www.google.com/recaptcha/intro/v3.html.`}</p>
name="privateKey"
>
<input type="text" value="" class="pf-c-form-control" required />
<p class="pf-c-form__helper-text">
${t`Private key, acquired from https://www.google.com/recaptcha/intro/v3.html.`}
</p>
</ak-form-element-horizontal>
</div>
</ak-form-group>
</form>`;
}
}

View File

@ -10,17 +10,18 @@ import { ModelForm } from "../../../elements/forms/ModelForm";
@customElement("ak-stage-consent-form")
export class ConsentStageForm extends ModelForm<ConsentStage, string> {
loadInstance(pk: string): Promise<ConsentStage> {
return new StagesApi(DEFAULT_CONFIG).stagesConsentRetrieve({
stageUuid: pk,
}).then(stage => {
this.showExpiresIn = stage.mode === ConsentStageModeEnum.Expiring;
return stage;
});
return new StagesApi(DEFAULT_CONFIG)
.stagesConsentRetrieve({
stageUuid: pk,
})
.then((stage) => {
this.showExpiresIn = stage.mode === ConsentStageModeEnum.Expiring;
return stage;
});
}
@property({type: Boolean})
@property({ type: Boolean })
showExpiresIn = false;
getSuccessMessage(): string {
@ -35,11 +36,11 @@ export class ConsentStageForm extends ModelForm<ConsentStage, string> {
if (this.instance) {
return new StagesApi(DEFAULT_CONFIG).stagesConsentUpdate({
stageUuid: this.instance.pk || "",
consentStageRequest: data
consentStageRequest: data,
});
} else {
return new StagesApi(DEFAULT_CONFIG).stagesConsentCreate({
consentStageRequest: data
consentStageRequest: data,
});
}
};
@ -49,36 +50,49 @@ export class ConsentStageForm extends ModelForm<ConsentStage, string> {
<div class="form-help-text">
${t`Prompt for the user's consent. The consent can either be permanent or expire in a defined amount of time.`}
</div>
<ak-form-element-horizontal
label=${t`Name`}
?required=${true}
name="name">
<input type="text" value="${ifDefined(this.instance?.name || "")}" class="pf-c-form-control" required>
<ak-form-element-horizontal label=${t`Name`} ?required=${true} name="name">
<input
type="text"
value="${ifDefined(this.instance?.name || "")}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-group .expanded=${true}>
<span slot="header">
${t`Stage-specific settings`}
</span>
<span slot="header"> ${t`Stage-specific settings`} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${t`Mode`}
?required=${true}
name="mode">
<select class="pf-c-form-control" @change=${(ev: Event) => {
const target = ev.target as HTMLSelectElement;
if (target.selectedOptions[0].value === ConsentStageModeEnum.Expiring) {
this.showExpiresIn = true;
} else {
this.showExpiresIn = false;
}
}}>
<option value=${ConsentStageModeEnum.AlwaysRequire} ?selected=${this.instance?.mode === ConsentStageModeEnum.AlwaysRequire}>
<ak-form-element-horizontal label=${t`Mode`} ?required=${true} name="mode">
<select
class="pf-c-form-control"
@change=${(ev: Event) => {
const target = ev.target as HTMLSelectElement;
if (
target.selectedOptions[0].value ===
ConsentStageModeEnum.Expiring
) {
this.showExpiresIn = true;
} else {
this.showExpiresIn = false;
}
}}
>
<option
value=${ConsentStageModeEnum.AlwaysRequire}
?selected=${this.instance?.mode ===
ConsentStageModeEnum.AlwaysRequire}
>
${t`Always require consent`}
</option>
<option value=${ConsentStageModeEnum.Permanent} ?selected=${this.instance?.mode === ConsentStageModeEnum.Permanent}>
<option
value=${ConsentStageModeEnum.Permanent}
?selected=${this.instance?.mode === ConsentStageModeEnum.Permanent}
>
${t`Consent given last indefinitely`}
</option>
<option value=${ConsentStageModeEnum.Expiring} ?selected=${this.instance?.mode === ConsentStageModeEnum.Expiring}>
<option
value=${ConsentStageModeEnum.Expiring}
?selected=${this.instance?.mode === ConsentStageModeEnum.Expiring}
>
${t`Consent expires.`}
</option>
</select>
@ -87,13 +101,20 @@ export class ConsentStageForm extends ModelForm<ConsentStage, string> {
?hidden=${!this.showExpiresIn}
label=${t`Consent expires in`}
?required=${true}
name="consentExpireIn">
<input type="text" value="${ifDefined(this.instance?.consentExpireIn || "weeks=4")}" class="pf-c-form-control" required>
<p class="pf-c-form__helper-text">${t`Offset after which consent expires. (Format: hours=1;minutes=2;seconds=3).`}</p>
name="consentExpireIn"
>
<input
type="text"
value="${ifDefined(this.instance?.consentExpireIn || "weeks=4")}"
class="pf-c-form-control"
required
/>
<p class="pf-c-form__helper-text">
${t`Offset after which consent expires. (Format: hours=1;minutes=2;seconds=3).`}
</p>
</ak-form-element-horizontal>
</div>
</ak-form-group>
</form>`;
}
}

View File

@ -9,7 +9,6 @@ import { ModelForm } from "../../../elements/forms/ModelForm";
@customElement("ak-stage-deny-form")
export class DenyStageForm extends ModelForm<DenyStage, string> {
loadInstance(pk: string): Promise<DenyStage> {
return new StagesApi(DEFAULT_CONFIG).stagesDenyRetrieve({
stageUuid: pk,
@ -28,11 +27,11 @@ export class DenyStageForm extends ModelForm<DenyStage, string> {
if (this.instance) {
return new StagesApi(DEFAULT_CONFIG).stagesDenyUpdate({
stageUuid: this.instance.pk || "",
denyStageRequest: data
denyStageRequest: data,
});
} else {
return new StagesApi(DEFAULT_CONFIG).stagesDenyCreate({
denyStageRequest: data
denyStageRequest: data,
});
}
};
@ -42,13 +41,14 @@ export class DenyStageForm extends ModelForm<DenyStage, string> {
<div class="form-help-text">
${t`Statically deny the flow. To use this stage effectively, disable *Evaluate on plan* on the respective binding.`}
</div>
<ak-form-element-horizontal
label=${t`Name`}
?required=${true}
name="name">
<input type="text" value="${ifDefined(this.instance?.name || "")}" class="pf-c-form-control" required>
<ak-form-element-horizontal label=${t`Name`} ?required=${true} name="name">
<input
type="text"
value="${ifDefined(this.instance?.name || "")}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
</form>`;
}
}

View File

@ -9,7 +9,6 @@ import { ModelForm } from "../../../elements/forms/ModelForm";
@customElement("ak-stage-dummy-form")
export class DummyStageForm extends ModelForm<DummyStage, string> {
loadInstance(pk: string): Promise<DummyStage> {
return new StagesApi(DEFAULT_CONFIG).stagesDummyRetrieve({
stageUuid: pk,
@ -28,11 +27,11 @@ export class DummyStageForm extends ModelForm<DummyStage, string> {
if (this.instance) {
return new StagesApi(DEFAULT_CONFIG).stagesDummyUpdate({
stageUuid: this.instance.pk || "",
dummyStageRequest: data
dummyStageRequest: data,
});
} else {
return new StagesApi(DEFAULT_CONFIG).stagesDummyCreate({
dummyStageRequest: data
dummyStageRequest: data,
});
}
};
@ -42,13 +41,14 @@ export class DummyStageForm extends ModelForm<DummyStage, string> {
<div class="form-help-text">
${t`Dummy stage used for testing. Shows a simple continue button and always passes.`}
</div>
<ak-form-element-horizontal
label=${t`Name`}
?required=${true}
name="name">
<input type="text" value="${ifDefined(this.instance?.name || "")}" class="pf-c-form-control" required>
<ak-form-element-horizontal label=${t`Name`} ?required=${true} name="name">
<input
type="text"
value="${ifDefined(this.instance?.name || "")}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
</form>`;
}
}

View File

@ -12,17 +12,18 @@ import { ModelForm } from "../../../elements/forms/ModelForm";
@customElement("ak-stage-email-form")
export class EmailStageForm extends ModelForm<EmailStage, string> {
loadInstance(pk: string): Promise<EmailStage> {
return new StagesApi(DEFAULT_CONFIG).stagesEmailRetrieve({
stageUuid: pk,
}).then(stage => {
this.showConnectionSettings = !stage.useGlobalSettings;
return stage;
});
return new StagesApi(DEFAULT_CONFIG)
.stagesEmailRetrieve({
stageUuid: pk,
})
.then((stage) => {
this.showConnectionSettings = !stage.useGlobalSettings;
return stage;
});
}
@property({type: Boolean})
@property({ type: Boolean })
showConnectionSettings = false;
getSuccessMessage(): string {
@ -37,11 +38,11 @@ export class EmailStageForm extends ModelForm<EmailStage, string> {
if (this.instance) {
return new StagesApi(DEFAULT_CONFIG).stagesEmailPartialUpdate({
stageUuid: this.instance.pk || "",
patchedEmailStageRequest: data
patchedEmailStageRequest: data,
});
} else {
return new StagesApi(DEFAULT_CONFIG).stagesEmailCreate({
emailStageRequest: data
emailStageRequest: data,
});
}
};
@ -51,63 +52,80 @@ export class EmailStageForm extends ModelForm<EmailStage, string> {
return html``;
}
return html`<ak-form-group>
<span slot="header">
${t`Connection settings`}
</span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${t`SMTP Host`}
?required=${true}
name="host">
<input type="text" value="${ifDefined(this.instance?.host || "")}" class="pf-c-form-control" required>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`SMTP Port`}
?required=${true}
name="port">
<input type="number" value="${first(this.instance?.port, 25)}" class="pf-c-form-control" required>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`SMTP Username`}
name="username">
<input type="text" value="${ifDefined(this.instance?.username || "")}" class="pf-c-form-control">
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`SMTP Password`}
?writeOnly=${this.instance !== undefined}
name="password">
<input type="text" value="" class="pf-c-form-control">
</ak-form-element-horizontal>
<ak-form-element-horizontal name="useTls">
<div class="pf-c-check">
<input type="checkbox" class="pf-c-check__input" ?checked=${first(this.instance?.useTls, true)}>
<label class="pf-c-check__label">
${t`Use TLS`}
</label>
</div>
</ak-form-element-horizontal>
<ak-form-element-horizontal name="useSsl">
<div class="pf-c-check">
<input type="checkbox" class="pf-c-check__input" ?checked=${first(this.instance?.useSsl, false)}>
<label class="pf-c-check__label">
${t`Use SSL`}
</label>
</div>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Timeout`}
?required=${true}
name="timeout">
<input type="number" value="${first(this.instance?.timeout, 30)}" class="pf-c-form-control" required>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`From address`}
?required=${true}
name="fromAddress">
<input type="text" value="${ifDefined(this.instance?.fromAddress || "system@authentik.local")}" class="pf-c-form-control" required>
</ak-form-element-horizontal>
</div>
</ak-form-group>`;
<span slot="header"> ${t`Connection settings`} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal label=${t`SMTP Host`} ?required=${true} name="host">
<input
type="text"
value="${ifDefined(this.instance?.host || "")}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${t`SMTP Port`} ?required=${true} name="port">
<input
type="number"
value="${first(this.instance?.port, 25)}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${t`SMTP Username`} name="username">
<input
type="text"
value="${ifDefined(this.instance?.username || "")}"
class="pf-c-form-control"
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`SMTP Password`}
?writeOnly=${this.instance !== undefined}
name="password"
>
<input type="text" value="" class="pf-c-form-control" />
</ak-form-element-horizontal>
<ak-form-element-horizontal name="useTls">
<div class="pf-c-check">
<input
type="checkbox"
class="pf-c-check__input"
?checked=${first(this.instance?.useTls, true)}
/>
<label class="pf-c-check__label"> ${t`Use TLS`} </label>
</div>
</ak-form-element-horizontal>
<ak-form-element-horizontal name="useSsl">
<div class="pf-c-check">
<input
type="checkbox"
class="pf-c-check__input"
?checked=${first(this.instance?.useSsl, false)}
/>
<label class="pf-c-check__label"> ${t`Use SSL`} </label>
</div>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${t`Timeout`} ?required=${true} name="timeout">
<input
type="number"
value="${first(this.instance?.timeout, 30)}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`From address`}
?required=${true}
name="fromAddress"
>
<input
type="text"
value="${ifDefined(this.instance?.fromAddress || "system@authentik.local")}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
</div>
</ak-form-group>`;
}
renderForm(): TemplateResult {
@ -115,55 +133,84 @@ export class EmailStageForm extends ModelForm<EmailStage, string> {
<div class="form-help-text">
${t`Verify the user's email address by sending them a one-time-link. Can also be used for recovery to verify the user's authenticity.`}
</div>
<ak-form-element-horizontal
label=${t`Name`}
?required=${true}
name="name">
<input type="text" value="${ifDefined(this.instance?.name || "")}" class="pf-c-form-control" required>
<ak-form-element-horizontal label=${t`Name`} ?required=${true} name="name">
<input
type="text"
value="${ifDefined(this.instance?.name || "")}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-group .expanded=${true}>
<span slot="header">
${t`Stage-specific settings`}
</span>
<span slot="header"> ${t`Stage-specific settings`} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal name="useGlobalSettings">
<div class="pf-c-check">
<input type="checkbox" class="pf-c-check__input" ?checked=${first(this.instance?.useGlobalSettings, true)} @change=${(ev: Event) => {
const target = ev.target as HTMLInputElement;
this.showConnectionSettings = !target.checked;
}}>
<label class="pf-c-check__label">
${t`Use global settings`}
</label>
<input
type="checkbox"
class="pf-c-check__input"
?checked=${first(this.instance?.useGlobalSettings, true)}
@change=${(ev: Event) => {
const target = ev.target as HTMLInputElement;
this.showConnectionSettings = !target.checked;
}}
/>
<label class="pf-c-check__label"> ${t`Use global settings`} </label>
</div>
<p class="pf-c-form__helper-text">${t`When enabled, global Email connection settings will be used and connection settings below will be ignored.`}</p>
<p class="pf-c-form__helper-text">
${t`When enabled, global Email connection settings will be used and connection settings below will be ignored.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Token expiry`}
?required=${true}
name="tokenExpiry">
<input type="number" value="${first(this.instance?.tokenExpiry, 30)}" class="pf-c-form-control" required>
<p class="pf-c-form__helper-text">${t`Time in minutes the token sent is valid.`}</p>
name="tokenExpiry"
>
<input
type="number"
value="${first(this.instance?.tokenExpiry, 30)}"
class="pf-c-form-control"
required
/>
<p class="pf-c-form__helper-text">
${t`Time in minutes the token sent is valid.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Subject`}
?required=${true}
name="subject">
<input type="text" value="${first(this.instance?.subject, "authentik")}" class="pf-c-form-control" required>
name="subject"
>
<input
type="text"
value="${first(this.instance?.subject, "authentik")}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Template`}
?required=${true}
name="template">
name="template"
>
<select name="users" class="pf-c-form-control">
${until(new StagesApi(DEFAULT_CONFIG).stagesEmailTemplatesList().then(templates => {
return templates.map(template => {
const selected = this.instance?.template === template.name;
return html`<option value=${ifDefined(template.name)} ?selected=${selected}>
${template.description}
</option>`;
});
}), html`<option>${t`Loading...`}</option>`)}
${until(
new StagesApi(DEFAULT_CONFIG)
.stagesEmailTemplatesList()
.then((templates) => {
return templates.map((template) => {
const selected =
this.instance?.template === template.name;
return html`<option
value=${ifDefined(template.name)}
?selected=${selected}
>
${template.description}
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
</ak-form-element-horizontal>
</div>
@ -171,5 +218,4 @@ export class EmailStageForm extends ModelForm<EmailStage, string> {
${this.renderConnectionSettings()}
</form>`;
}
}

View File

@ -1,4 +1,11 @@
import { FlowsApi, IdentificationStage, UserFieldsEnum, StagesApi, FlowsInstancesListDesignationEnum, SourcesApi } from "authentik-api";
import {
FlowsApi,
IdentificationStage,
UserFieldsEnum,
StagesApi,
FlowsInstancesListDesignationEnum,
SourcesApi,
} from "authentik-api";
import { t } from "@lingui/macro";
import { customElement } from "lit-element";
import { html, TemplateResult } from "lit-html";
@ -12,7 +19,6 @@ import { ModelForm } from "../../../elements/forms/ModelForm";
@customElement("ak-stage-identification-form")
export class IdentificationStageForm extends ModelForm<IdentificationStage, string> {
loadInstance(pk: string): Promise<IdentificationStage> {
return new StagesApi(DEFAULT_CONFIG).stagesIdentificationRetrieve({
stageUuid: pk,
@ -31,19 +37,21 @@ export class IdentificationStageForm extends ModelForm<IdentificationStage, stri
if (this.instance) {
return new StagesApi(DEFAULT_CONFIG).stagesIdentificationUpdate({
stageUuid: this.instance.pk || "",
identificationStageRequest: data
identificationStageRequest: data,
});
} else {
return new StagesApi(DEFAULT_CONFIG).stagesIdentificationCreate({
identificationStageRequest: data
identificationStageRequest: data,
});
}
};
isUserFieldSelected(field: UserFieldsEnum): boolean {
return (this.instance?.userFields || []).filter(isField => {
return field === isField;
}).length > 0;
return (
(this.instance?.userFields || []).filter((isField) => {
return field === isField;
}).length > 0
);
}
renderForm(): TemplateResult {
@ -51,122 +59,204 @@ export class IdentificationStageForm extends ModelForm<IdentificationStage, stri
<div class="form-help-text">
${t`Let the user identify themselves with their username or Email address.`}
</div>
<ak-form-element-horizontal
label=${t`Name`}
?required=${true}
name="name">
<input type="text" value="${ifDefined(this.instance?.name || "")}" class="pf-c-form-control" required>
<ak-form-element-horizontal label=${t`Name`} ?required=${true} name="name">
<input
type="text"
value="${ifDefined(this.instance?.name || "")}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-group .expanded=${true}>
<span slot="header">
${t`Stage-specific settings`}
</span>
<span slot="header"> ${t`Stage-specific settings`} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${t`User fields`}
name="userFields">
<ak-form-element-horizontal label=${t`User fields`} name="userFields">
<select name="users" class="pf-c-form-control" multiple>
<option value=${UserFieldsEnum.Username} ?selected=${this.isUserFieldSelected(UserFieldsEnum.Username)}>
<option
value=${UserFieldsEnum.Username}
?selected=${this.isUserFieldSelected(UserFieldsEnum.Username)}
>
${t`Username`}
</option>
<option value=${UserFieldsEnum.Email} ?selected=${this.isUserFieldSelected(UserFieldsEnum.Email)}>
<option
value=${UserFieldsEnum.Email}
?selected=${this.isUserFieldSelected(UserFieldsEnum.Email)}
>
${t`Email`}
</option>
<option value=${UserFieldsEnum.Upn} ?selected=${this.isUserFieldSelected(UserFieldsEnum.Upn)}>
<option
value=${UserFieldsEnum.Upn}
?selected=${this.isUserFieldSelected(UserFieldsEnum.Upn)}
>
${t`UPN`}
</option>
</select>
<p class="pf-c-form__helper-text">${t`Fields a user can identify themselves with. If no fields are selected, the user will only be able to use sources.`}</p>
<p class="pf-c-form__helper-text">${t`Hold control/command to select multiple items.`}</p>
<p class="pf-c-form__helper-text">
${t`Fields a user can identify themselves with. If no fields are selected, the user will only be able to use sources.`}
</p>
<p class="pf-c-form__helper-text">
${t`Hold control/command to select multiple items.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Password stage`}
name="passwordStage">
<ak-form-element-horizontal label=${t`Password stage`} name="passwordStage">
<select class="pf-c-form-control">
<option value="" ?selected=${this.instance?.passwordStage === undefined}>---------</option>
${until(new StagesApi(DEFAULT_CONFIG).stagesPasswordList({
ordering: "pk",
}).then(stages => {
return stages.results.map(stage => {
const selected = this.instance?.passwordStage === stage.pk;
return html`<option value=${ifDefined(stage.pk)} ?selected=${selected}>${stage.name}</option>`;
});
}), html`<option>${t`Loading...`}</option>`)}
<option
value=""
?selected=${this.instance?.passwordStage === undefined}
>
---------
</option>
${until(
new StagesApi(DEFAULT_CONFIG)
.stagesPasswordList({
ordering: "pk",
})
.then((stages) => {
return stages.results.map((stage) => {
const selected =
this.instance?.passwordStage === stage.pk;
return html`<option
value=${ifDefined(stage.pk)}
?selected=${selected}
>
${stage.name}
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
<p class="pf-c-form__helper-text">${t`When selected, a password field is shown on the same page instead of a separate page. This prevents username enumeration attacks.`}</p>
<p class="pf-c-form__helper-text">
${t`When selected, a password field is shown on the same page instead of a separate page. This prevents username enumeration attacks.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal name="caseInsensitiveMatching">
<div class="pf-c-check">
<input type="checkbox" class="pf-c-check__input" ?checked=${first(this.instance?.caseInsensitiveMatching, true)}>
<input
type="checkbox"
class="pf-c-check__input"
?checked=${first(this.instance?.caseInsensitiveMatching, true)}
/>
<label class="pf-c-check__label">
${t`Case insensitive matching`}
</label>
</div>
<p class="pf-c-form__helper-text">${t`When enabled, user fields are matched regardless of their casing.`}</p>
<p class="pf-c-form__helper-text">
${t`When enabled, user fields are matched regardless of their casing.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Sources`}
?required=${true}
name="sources">
name="sources"
>
<select name="users" class="pf-c-form-control" multiple>
${until(new SourcesApi(DEFAULT_CONFIG).sourcesAllList({}).then(sources => {
return sources.results.map(source => {
const selected = Array.from(this.instance?.sources || []).some(su => {
return su == source.pk;
});
return html`<option value=${ifDefined(source.pk)} ?selected=${selected}>${source.name}</option>`;
});
}), html`<option>${t`Loading...`}</option>`)}
${until(
new SourcesApi(DEFAULT_CONFIG)
.sourcesAllList({})
.then((sources) => {
return sources.results.map((source) => {
const selected = Array.from(
this.instance?.sources || [],
).some((su) => {
return su == source.pk;
});
return html`<option
value=${ifDefined(source.pk)}
?selected=${selected}
>
${source.name}
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
<p class="pf-c-form__helper-text">${t`Select sources should be shown for users to authenticate with. This only affects web-based sources, not LDAP.`}</p>
<p class="pf-c-form__helper-text">${t`Hold control/command to select multiple items.`}</p>
<p class="pf-c-form__helper-text">
${t`Select sources should be shown for users to authenticate with. This only affects web-based sources, not LDAP.`}
</p>
<p class="pf-c-form__helper-text">
${t`Hold control/command to select multiple items.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal name="showMatchedUser">
<div class="pf-c-check">
<input type="checkbox" class="pf-c-check__input" ?checked=${first(this.instance?.showMatchedUser, true)}>
<label class="pf-c-check__label">
${t`Show matched user`}
</label>
<input
type="checkbox"
class="pf-c-check__input"
?checked=${first(this.instance?.showMatchedUser, true)}
/>
<label class="pf-c-check__label"> ${t`Show matched user`} </label>
</div>
<p class="pf-c-form__helper-text">${t`When a valid username/email has been entered, and this option is enabled, the user's username and avatar will be shown. Otherwise, the text that the user entered will be shown.`}</p>
<p class="pf-c-form__helper-text">
${t`When a valid username/email has been entered, and this option is enabled, the user's username and avatar will be shown. Otherwise, the text that the user entered will be shown.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Enrollment flow`}
name="enrollmentFlow">
<ak-form-element-horizontal label=${t`Enrollment flow`} name="enrollmentFlow">
<select class="pf-c-form-control">
<option value="" ?selected=${this.instance?.enrollmentFlow === undefined}>---------</option>
${until(new FlowsApi(DEFAULT_CONFIG).flowsInstancesList({
ordering: "pk",
designation: FlowsInstancesListDesignationEnum.Enrollment,
}).then(flows => {
return flows.results.map(flow => {
const selected = this.instance?.enrollmentFlow === flow.pk;
return html`<option value=${ifDefined(flow.pk)} ?selected=${selected}>${flow.name} (${flow.slug})</option>`;
});
}), html`<option>${t`Loading...`}</option>`)}
<option
value=""
?selected=${this.instance?.enrollmentFlow === undefined}
>
---------
</option>
${until(
new FlowsApi(DEFAULT_CONFIG)
.flowsInstancesList({
ordering: "pk",
designation: FlowsInstancesListDesignationEnum.Enrollment,
})
.then((flows) => {
return flows.results.map((flow) => {
const selected =
this.instance?.enrollmentFlow === flow.pk;
return html`<option
value=${ifDefined(flow.pk)}
?selected=${selected}
>
${flow.name} (${flow.slug})
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
<p class="pf-c-form__helper-text">${t`Optional enrollment flow, which is linked at the bottom of the page.`}</p>
<p class="pf-c-form__helper-text">
${t`Optional enrollment flow, which is linked at the bottom of the page.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Recovery flow`}
name="recoveryFlow">
<ak-form-element-horizontal label=${t`Recovery flow`} name="recoveryFlow">
<select class="pf-c-form-control">
<option value="" ?selected=${this.instance?.recoveryFlow === undefined}>---------</option>
${until(new FlowsApi(DEFAULT_CONFIG).flowsInstancesList({
ordering: "pk",
designation: FlowsInstancesListDesignationEnum.Recovery,
}).then(flows => {
return flows.results.map(flow => {
const selected = this.instance?.recoveryFlow === flow.pk;
return html`<option value=${ifDefined(flow.pk)} ?selected=${selected}>${flow.name} (${flow.slug})</option>`;
});
}), html`<option>${t`Loading...`}</option>`)}
<option value="" ?selected=${this.instance?.recoveryFlow === undefined}>
---------
</option>
${until(
new FlowsApi(DEFAULT_CONFIG)
.flowsInstancesList({
ordering: "pk",
designation: FlowsInstancesListDesignationEnum.Recovery,
})
.then((flows) => {
return flows.results.map((flow) => {
const selected =
this.instance?.recoveryFlow === flow.pk;
return html`<option
value=${ifDefined(flow.pk)}
?selected=${selected}
>
${flow.name} (${flow.slug})
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
<p class="pf-c-form__helper-text">${t`Optional recovery flow, which is linked at the bottom of the page.`}</p>
<p class="pf-c-form__helper-text">
${t`Optional recovery flow, which is linked at the bottom of the page.`}
</p>
</ak-form-element-horizontal>
</div>
</ak-form-group>
</form>`;
}
}

View File

@ -11,7 +11,6 @@ import { ModelForm } from "../../../elements/forms/ModelForm";
@customElement("ak-invitation-form")
export class InvitationForm extends ModelForm<Invitation, string> {
loadInstance(pk: string): Promise<Invitation> {
return new StagesApi(DEFAULT_CONFIG).stagesInvitationInvitationsRetrieve({
inviteUuid: pk,
@ -30,36 +29,38 @@ export class InvitationForm extends ModelForm<Invitation, string> {
if (this.instance) {
return new StagesApi(DEFAULT_CONFIG).stagesInvitationInvitationsUpdate({
inviteUuid: this.instance.pk || "",
invitationRequest: data
invitationRequest: data,
});
} else {
return new StagesApi(DEFAULT_CONFIG).stagesInvitationInvitationsCreate({
invitationRequest: data
invitationRequest: data,
});
}
};
renderForm(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
<ak-form-element-horizontal
label=${t`Expires`}
?required=${true}
name="expires">
<input type="date" class="pf-c-form-control" required>
<ak-form-element-horizontal label=${t`Expires`} ?required=${true} name="expires">
<input type="date" class="pf-c-form-control" required />
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Attributes`}
name="fixedData">
<ak-codemirror mode="yaml" value="${YAML.stringify(first(this.instance?.fixedData, {}))}">
<ak-form-element-horizontal label=${t`Attributes`} name="fixedData">
<ak-codemirror
mode="yaml"
value="${YAML.stringify(first(this.instance?.fixedData, {}))}"
>
</ak-codemirror>
<p class="pf-c-form__helper-text">${t`Optional data which is loaded into the flow's 'prompt_data' context variable. YAML or JSON.`}</p>
<p class="pf-c-form__helper-text">
${t`Optional data which is loaded into the flow's 'prompt_data' context variable. YAML or JSON.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal name="singleUse">
<div class="pf-c-check">
<input type="checkbox" class="pf-c-check__input" ?checked=${first(this.instance?.singleUse, true)}>
<label class="pf-c-check__label">
${t`Single use`}
</label>
<input
type="checkbox"
class="pf-c-check__input"
?checked=${first(this.instance?.singleUse, true)}
/>
<label class="pf-c-check__label"> ${t`Single use`} </label>
</div>
<p class="pf-c-form__helper-text">
${t`When enabled, the invitation will be deleted after usage.`}
@ -67,5 +68,4 @@ export class InvitationForm extends ModelForm<Invitation, string> {
</ak-form-element-horizontal>
</form>`;
}
}

View File

@ -12,9 +12,8 @@ import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList
@customElement("ak-stage-invitation-list-link")
export class InvitationListLink extends LitElement {
@property()
invitation?: string
invitation?: string;
@property()
selectedFlow?: string;
@ -35,33 +34,52 @@ export class InvitationListLink extends LitElement {
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
<select class="pf-c-form-control" @change=${(ev: Event) => {
const current = (ev.target as HTMLInputElement).value;
this.selectedFlow = current;
}}>
${until(new FlowsApi(DEFAULT_CONFIG).flowsInstancesList({
ordering: "pk",
designation: FlowsInstancesListDesignationEnum.Enrollment,
}).then(flows => {
return flows.results.map(flow => {
return html`<option value=${flow.slug} ?selected=${flow.slug === this.selectedFlow}>${flow.slug}</option>`;
});
}), html`<option>${t`Loading...`}</option>`)}
<select
class="pf-c-form-control"
@change=${(ev: Event) => {
const current = (ev.target as HTMLInputElement).value;
this.selectedFlow = current;
}}
>
${until(
new FlowsApi(DEFAULT_CONFIG)
.flowsInstancesList({
ordering: "pk",
designation: FlowsInstancesListDesignationEnum.Enrollment,
})
.then((flows) => {
return flows.results.map((flow) => {
return html`<option
value=${flow.slug}
?selected=${flow.slug === this.selectedFlow}
>
${flow.slug}
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${t`Link to use the invitation.`}</span>
<span class="pf-c-description-list__text"
>${t`Link to use the invitation.`}</span
>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
<input class="pf-c-form-control" readonly type="text" value=${this.renderLink()} />
<input
class="pf-c-form-control"
readonly
type="text"
value=${this.renderLink()}
/>
</div>
</dd>
</div>
</dl>`;
}
}

View File

@ -57,55 +57,47 @@ export class InvitationListPage extends TablePage<Invitation> {
html`${item.pk}`,
html`${item.createdBy?.username}`,
html`${item.expires?.toLocaleString() || "-"}`,
html`
<ak-forms-delete
html` <ak-forms-delete
.obj=${item}
objectLabel=${t`Invitation`}
.usedBy=${() => {
return new StagesApi(DEFAULT_CONFIG).stagesInvitationInvitationsUsedByList({
inviteUuid: item.pk
inviteUuid: item.pk,
});
}}
.delete=${() => {
return new StagesApi(DEFAULT_CONFIG).stagesInvitationInvitationsDestroy({
inviteUuid: item.pk
inviteUuid: item.pk,
});
}}>
<button slot="trigger" class="pf-c-button pf-m-danger">
${t`Delete`}
</button>
}}
>
<button slot="trigger" class="pf-c-button pf-m-danger">${t`Delete`}</button>
</ak-forms-delete>`,
];
}
renderExpanded(item: Invitation): TemplateResult {
return html`
<td role="cell" colspan="3">
<div class="pf-c-table__expandable-row-content">
<ak-stage-invitation-list-link invitation=${item.pk}></ak-stage-invitation-list-link>
</div>
</td>
<td></td>
<td></td>
<td></td>`;
return html` <td role="cell" colspan="3">
<div class="pf-c-table__expandable-row-content">
<ak-stage-invitation-list-link
invitation=${item.pk}
></ak-stage-invitation-list-link>
</div>
</td>
<td></td>
<td></td>
<td></td>`;
}
renderToolbar(): TemplateResult {
return html`
<ak-forms-modal>
<span slot="submit">
${t`Create`}
</span>
<span slot="header">
${t`Create Invitation`}
</span>
<ak-invitation-form slot="form">
</ak-invitation-form>
<button slot="trigger" class="pf-c-button pf-m-primary">
${t`Create`}
</button>
</ak-forms-modal>
${super.renderToolbar()}
<ak-forms-modal>
<span slot="submit"> ${t`Create`} </span>
<span slot="header"> ${t`Create Invitation`} </span>
<ak-invitation-form slot="form"> </ak-invitation-form>
<button slot="trigger" class="pf-c-button pf-m-primary">${t`Create`}</button>
</ak-forms-modal>
${super.renderToolbar()}
`;
}
}

Some files were not shown because too many files have changed in this diff Show More