Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Static config v1 #13600

Draft
wants to merge 3 commits into
base: error-normalizing
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion web/packages/sfe/src/index.ts
Original file line number Diff line number Diff line change
@@ -515,7 +515,7 @@ class AuthenticatorValidateStage extends Stage<AuthenticatorValidationChallenge>
throw new Error(`Error when validating assertion on server: ${err}`);
}
})
.catch((error) => {
.catch((error: unknown) => {
console.warn(error);
this.deviceChallenge = undefined;
this.render();
6 changes: 3 additions & 3 deletions web/src/admin/AdminInterface/AboutModal.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { VERSION } from "@goauthentik/common/constants";
import { globalAK } from "@goauthentik/common/global";
import { BrandConfig, ServerConfig } from "@goauthentik/common/global";
import "@goauthentik/elements/EmptyState";
import { WithBrandConfig } from "@goauthentik/elements/Interface/brandProvider";
import { WithLicenseSummary } from "@goauthentik/elements/Interface/licenseSummaryProvider";
@@ -33,7 +33,7 @@ export class AboutModal extends WithLicenseSummary(WithBrandConfig(ModalButton))
const status = await new AdminApi(DEFAULT_CONFIG).adminSystemRetrieve();
const version = await new AdminApi(DEFAULT_CONFIG).adminVersionRetrieve();
let build: string | TemplateResult = msg("Release");
if (globalAK().config.capabilities.includes(CapabilitiesEnum.CanDebug)) {
if (ServerConfig.capabilities.includes(CapabilitiesEnum.CanDebug)) {
build = msg("Development");
} else if (version.buildHash !== "") {
build = html`<a
@@ -58,7 +58,7 @@ export class AboutModal extends WithLicenseSummary(WithBrandConfig(ModalButton))
}

renderModal() {
let product = globalAK().brand.brandingTitle || DefaultBrand.brandingTitle;
let product = BrandConfig.brandingTitle || DefaultBrand.brandingTitle;
if (this.licenseSummary.status != LicenseSummaryStatusEnum.Unlicensed) {
product += ` ${msg("Enterprise")}`;
}
9 changes: 6 additions & 3 deletions web/src/admin/DebugPage.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { parseAPIResponseError, pluckErrorDetail } from "@goauthentik/common/errors/network";
import { MessageLevel } from "@goauthentik/common/messages";
import { AKElement } from "@goauthentik/elements/Base";
import "@goauthentik/elements/PageHeader";
@@ -51,13 +52,15 @@ export class DebugPage extends AKElement {
.then(() => {
showMessage({
level: MessageLevel.success,
message: "Success",
title: "Success",
});
})
.catch((exc) => {
.catch(async (error) => {
const parsedError = await parseAPIResponseError(error);
showMessage({
level: MessageLevel.error,
message: exc,
title: pluckErrorDetail(parsedError),
});
});
}}
23 changes: 13 additions & 10 deletions web/src/admin/admin-overview/cards/AdminStatusCard.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import { EVENT_REFRESH } from "@goauthentik/common/constants";
import { PFSize } from "@goauthentik/common/enums.js";
import {
APIError,
parseAPIResponseError,
pluckErrorDetail,
} from "@goauthentik/common/errors/network";
import { AggregateCard } from "@goauthentik/elements/cards/AggregateCard";

import { msg } from "@lit/localize";
import { PropertyValues, TemplateResult, html, nothing } from "lit";
import { state } from "lit/decorators.js";

import { ResponseError } from "@goauthentik/api";

export interface AdminStatus {
icon: string;
message?: TemplateResult;
@@ -29,7 +32,7 @@ export abstract class AdminStatusCard<T> extends AggregateCard {

// Current error state if any request fails
@state()
protected error?: string;
protected error?: APIError;

// Abstract methods to be implemented by subclasses
abstract getPrimaryValue(): Promise<T>;
@@ -59,9 +62,9 @@ export abstract class AdminStatusCard<T> extends AggregateCard {
this.value = value; // Triggers shouldUpdate
this.error = undefined;
})
.catch((err: ResponseError) => {
.catch(async (error: unknown) => {
this.status = undefined;
this.error = err?.response?.statusText ?? msg("Unknown error");
this.error = await parseAPIResponseError(error);
});
}

@@ -79,9 +82,9 @@ export abstract class AdminStatusCard<T> extends AggregateCard {
this.status = status;
this.error = undefined;
})
.catch((err: ResponseError) => {
.catch(async (error: unknown) => {
this.status = undefined;
this.error = err?.response?.statusText ?? msg("Unknown error");
this.error = await parseAPIResponseError(error);
});

// Prevent immediate re-render if only value changed
@@ -120,8 +123,8 @@ export abstract class AdminStatusCard<T> extends AggregateCard {
*/
private renderError(error: string): TemplateResult {
return html`
<p><i class="fa fa-times"></i>&nbsp;${error}</p>
<p class="subtext">${msg("Failed to fetch")}</p>
<p><i class="fa fa-times"></i>&nbsp;${msg("Failed to fetch")}</p>
<p class="subtext">${error}</p>
`;
}

@@ -146,7 +149,7 @@ export abstract class AdminStatusCard<T> extends AggregateCard {
this.status
? this.renderStatus(this.status) // Status available
: this.error
? this.renderError(this.error) // Error state
? this.renderError(pluckErrorDetail(this.error)) // Error state
: this.renderLoading() // Loading state
}
</p>
13 changes: 9 additions & 4 deletions web/src/admin/admin-overview/cards/RecentEventsCard.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { EventGeo, EventUser } from "@goauthentik/admin/events/utils";
import { EventUser, formatGeoEvent } from "@goauthentik/admin/events/utils";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { EventWithContext } from "@goauthentik/common/events";
import { actionToLabel } from "@goauthentik/common/labels";
@@ -10,6 +10,7 @@ import "@goauthentik/elements/buttons/ModalButton";
import "@goauthentik/elements/buttons/SpinnerButton";
import { PaginatedResponse } from "@goauthentik/elements/table/Table";
import { Table, TableColumn } from "@goauthentik/elements/table/Table";
import { SlottedTemplateResult } from "@goauthentik/elements/types";

import { msg } from "@lit/localize";
import { CSSResult, TemplateResult, css, html } from "lit";
@@ -68,20 +69,24 @@ export class RecentEventsCard extends Table<Event> {
</div>`;
}

row(item: EventWithContext): TemplateResult[] {
row(item: EventWithContext): SlottedTemplateResult[] {
return [
html`<div><a href="${`#/events/log/${item.pk}`}">${actionToLabel(item.action)}</a></div>
<small>${item.app}</small>`,
EventUser(item),
html`<div>${getRelativeTime(item.created)}</div>
<small>${item.created.toLocaleString()}</small>`,
html` <div>${item.clientIp || msg("-")}</div>
<small>${EventGeo(item)}</small>`,
<small>${formatGeoEvent(item)}</small>`,
html`<span>${item.brand?.name || msg("-")}</span>`,
];
}

renderEmpty(): TemplateResult {
renderEmpty(inner?: SlottedTemplateResult): TemplateResult {
if (this.error) {
return super.renderEmpty(inner);
}

return super.renderEmpty(
html`<ak-empty-state header=${msg("No Events found.")}>
<div slot="body">${msg("No matching events could be found.")}</div>
4 changes: 2 additions & 2 deletions web/src/admin/applications/wizard/ApplicationWizardStep.ts
Original file line number Diff line number Diff line change
@@ -14,9 +14,9 @@ import { property, query } from "lit/decorators.js";
import { ValidationError } from "@goauthentik/api";

import {
ApplicationTransactionValidationError,
type ApplicationWizardState,
type ApplicationWizardStateUpdate,
ExtendedValidationError,
} from "./types";

export class ApplicationWizardStep extends WizardStep {
@@ -48,7 +48,7 @@ export class ApplicationWizardStep extends WizardStep {
}

protected removeErrors(
keyToDelete: keyof ExtendedValidationError,
keyToDelete: keyof ApplicationTransactionValidationError,
): ValidationError | undefined {
if (!this.wizard.errors) {
return undefined;
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import "@goauthentik/admin/applications/wizard/ak-wizard-title.js";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { EVENT_REFRESH } from "@goauthentik/common/constants";
import { parseAPIError } from "@goauthentik/common/errors";
import { parseAPIResponseError } from "@goauthentik/common/errors/network";
import { WizardNavigationEvent } from "@goauthentik/components/ak-wizard/events.js";
import { type WizardButton } from "@goauthentik/components/ak-wizard/types";
import { CustomEmitterElement } from "@goauthentik/elements/utils/eventEmitter";
@@ -33,7 +33,7 @@ import {
} from "@goauthentik/api";

import { ApplicationWizardStep } from "../ApplicationWizardStep.js";
import { ExtendedValidationError, OneOfProvider } from "../types.js";
import { ApplicationTransactionValidationError, OneOfProvider } from "../types.js";
import { providerRenderers } from "./SubmitStepOverviewRenderers.js";

const _submitStates = ["reviewing", "running", "submitted"] as const;
@@ -131,39 +131,36 @@ export class ApplicationWizardSubmitStep extends CustomEmitterElement(Applicatio

this.state = "running";

return (
new CoreApi(DEFAULT_CONFIG)
.coreTransactionalApplicationsUpdate({
transactionApplicationRequest: request,
})
.then((_response: TransactionApplicationResponse) => {
this.dispatchCustomEvent(EVENT_REFRESH);
this.state = "submitted";
})

// eslint-disable-next-line @typescript-eslint/no-explicit-any
.catch(async (resolution: any) => {
const errors = (await parseAPIError(
await resolution,
)) as ExtendedValidationError;

// THIS is a really gross special case; if the user is duplicating the name of
// an existing provider, the error appears on the `app` (!) error object. We
// have to move that to the `provider.name` error field so it shows up in the
// right place.
if (Array.isArray(errors?.app?.provider)) {
const providerError = errors.app.provider;
errors.provider = errors.provider ?? {};
errors.provider.name = providerError;
delete errors.app.provider;
if (Object.keys(errors.app).length === 0) {
delete errors.app;
}
return new CoreApi(DEFAULT_CONFIG)
.coreTransactionalApplicationsUpdate({
transactionApplicationRequest: request,
})
.then((_response: TransactionApplicationResponse) => {
this.dispatchCustomEvent(EVENT_REFRESH);
this.state = "submitted";
})

.catch(async (resolution) => {
const errors =
await parseAPIResponseError<ApplicationTransactionValidationError>(resolution);

// THIS is a really gross special case; if the user is duplicating the name of an existing provider, the error appears on the `app` (!) error object.
// We have to move that to the `provider.name` error field so it shows up in the right place.
if (Array.isArray(errors?.app?.provider)) {
const providerError = errors.app.provider;
errors.provider = errors.provider ?? {};
errors.provider.name = providerError;

delete errors.app.provider;

if (Object.keys(errors.app).length === 0) {
delete errors.app;
}
this.handleUpdate({ errors });
this.state = "reviewing";
})
);
}

this.handleUpdate({ errors });
this.state = "reviewing";
});
}

override handleButton(button: WizardButton) {
@@ -232,7 +229,7 @@ export class ApplicationWizardSubmitStep extends CustomEmitterElement(Applicatio
const navTo = (step: string) => () => this.dispatchEvent(new WizardNavigationEvent(step));
const errors = this.wizard.errors;
return html` <hr class="pf-c-divider" />
${match(errors as ExtendedValidationError)
${match(errors as ApplicationTransactionValidationError)
.with(
{ app: P.nonNullable },
() =>
Original file line number Diff line number Diff line change
@@ -9,7 +9,7 @@ import { customElement, state } from "lit/decorators.js";
import { OAuth2ProviderRequest, SourcesApi } from "@goauthentik/api";
import { type OAuth2Provider, type PaginatedOAuthSourceList } from "@goauthentik/api";

import { ExtendedValidationError } from "../../types.js";
import { ApplicationTransactionValidationError } from "../../types.js";
import { ApplicationWizardProviderForm } from "./ApplicationWizardProviderForm.js";

@customElement("ak-application-wizard-provider-for-oauth")
@@ -34,7 +34,7 @@ export class ApplicationWizardOauth2ProviderForm extends ApplicationWizardProvid
});
}

renderForm(provider: OAuth2Provider, errors: ExtendedValidationError) {
renderForm(provider: OAuth2Provider, errors: ApplicationTransactionValidationError) {
const showClientSecretCallback = (show: boolean) => {
this.showClientSecret = show;
};
29 changes: 23 additions & 6 deletions web/src/admin/applications/wizard/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { APIError } from "@goauthentik/common/errors/network";

import {
type ApplicationRequest,
type LDAPProviderRequest,
@@ -25,16 +27,31 @@ export type OneOfProvider =

export type ValidationRecord = { [key: string]: string[] };

// TODO: Elf, extend this type and apply it to every object in the wizard. Then run
// the type-checker again.

export type ExtendedValidationError = ValidationError & {
/**
* An error that occurs during the creation or modification of an application.
*
* @todo (Elf) Extend this type to include all possible errors that can occur during the creation or modification of an application.
*/
export interface ApplicationTransactionValidationError extends ValidationError {
app?: ValidationRecord;
provider?: ValidationRecord;
bindings?: ValidationRecord;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
detail?: any;
};
}

/**
* Type-guard to determine if an API response is shaped like an {@linkcode ApplicationTransactionValidationError}.
*/
export function isApplicationTransactionValidationError(
error: APIError,
): error is ApplicationTransactionValidationError {
if ("app" in error) return true;
if ("provider" in error) return true;
if ("bindings" in error) return true;

return false;
}

// We use the PolicyBinding instead of the PolicyBindingRequest here, because that gives us a slot
// in which to preserve the retrieved policy, group, or user object from the SearchSelect used to
@@ -49,7 +66,7 @@ export interface ApplicationWizardState {
proxyMode: ProxyMode;
bindings: PolicyBinding[];
currentBinding: number;
errors: ExtendedValidationError;
errors: ApplicationTransactionValidationError;
}

export interface ApplicationWizardStateUpdate {
4 changes: 2 additions & 2 deletions web/src/admin/events/EventListPage.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import "@goauthentik/admin/events/EventVolumeChart";
import { EventGeo, EventUser } from "@goauthentik/admin/events/utils";
import { EventUser, formatGeoEvent } from "@goauthentik/admin/events/utils";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { EventWithContext } from "@goauthentik/common/events";
import { actionToLabel } from "@goauthentik/common/labels";
@@ -80,7 +80,7 @@ export class EventListPage extends TablePage<Event> {
html`<div>${getRelativeTime(item.created)}</div>
<small>${item.created.toLocaleString()}</small>`,
html`<div>${item.clientIp || msg("-")}</div>
<small>${EventGeo(item)}</small>`,
<small>${formatGeoEvent(item)}</small>`,
html`<span>${item.brand?.name || msg("-")}</span>`,
html`<a href="#/events/log/${item.pk}">
<pf-tooltip position="top" content=${msg("Show details")}>
4 changes: 2 additions & 2 deletions web/src/admin/events/EventViewPage.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { EventGeo, EventUser } from "@goauthentik/admin/events/utils";
import { EventUser, formatGeoEvent } from "@goauthentik/admin/events/utils";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { EventWithContext } from "@goauthentik/common/events";
import { actionToLabel } from "@goauthentik/common/labels";
@@ -118,7 +118,7 @@ export class EventViewPage extends AKElement {
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
<div>${this.event.clientIp || msg("-")}</div>
<small>${EventGeo(this.event)}</small>
<small>${formatGeoEvent(this.event)}</small>
</div>
</dd>
</div>
Loading

Unchanged files with check annotations Beta

ENV GEOIPUPDATE_EDITION_IDS="GeoLite2-City GeoLite2-ASN"
ENV GEOIPUPDATE_VERBOSE="1"
ENV GEOIPUPDATE_ACCOUNT_ID_FILE="/run/secrets/GEOIPUPDATE_ACCOUNT_ID"

Check warning on line 88 in Dockerfile

GitHub Actions / build / build-server-amd64 / Build amd64

Sensitive data should not be used in the ARG or ENV commands

SecretsUsedInArgOrEnv: Do not use ARG or ENV instructions for sensitive data (ENV "GEOIPUPDATE_LICENSE_KEY_FILE") More info: https://docs.docker.com/go/dockerfile/rule/secrets-used-in-arg-or-env/

Check warning on line 88 in Dockerfile

GitHub Actions / build / build-server-arm64 / Build arm64

Sensitive data should not be used in the ARG or ENV commands

SecretsUsedInArgOrEnv: Do not use ARG or ENV instructions for sensitive data (ENV "GEOIPUPDATE_LICENSE_KEY_FILE") More info: https://docs.docker.com/go/dockerfile/rule/secrets-used-in-arg-or-env/
ENV GEOIPUPDATE_LICENSE_KEY_FILE="/run/secrets/GEOIPUPDATE_LICENSE_KEY"
USER root