Merge branch 'main' into web/sidebar-with-live-content-3

* main: (42 commits)
  stages/authenticator_totp: fix API validation error due to choices (#7608)
  website: fix pricing page inconsistency (#7607)
  web: bump API Client version (#7602)
  translate: Updates for file web/xliff/en.xlf in zh_CN (#7603)
  core: bump goauthentik.io/api/v3 from 3.2023103.2 to 3.2023103.3 (#7606)
  translate: Updates for file web/xliff/en.xlf in zh-Hans (#7604)
  Revert "web: bump @lit-labs/context from 0.4.1 to 0.5.1 in /web (#7486)"
  root: fix API schema for kotlin (#7601)
  web: bump @lit-labs/context from 0.4.1 to 0.5.1 in /web (#7486)
  translate: Updates for file web/xliff/en.xlf in zh-Hans (#7583)
  events: fix missing model_* events when not directly authenticated (#7588)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in zh_TW (#7594)
  providers/scim: fix missing schemas attribute for User and Group (#7477)
  core: bump pydantic from 2.5.0 to 2.5.1 (#7592)
  web/admin: contextually add user to group when creating user from group page (#7586)
  website/blog: title and slug change (#7585)
  events: sanitize functions (#7587)
  stages/email: use uuid for email confirmation token instead of username (#7581)
  website/blog: Blog about zero trust and wireguard (#7567)
  ci: translation-advice: avoid commenting after make i18n-extract
  ...
This commit is contained in:
Ken Sternberg
2023-11-17 09:34:15 -08:00
53 changed files with 1469 additions and 917 deletions

View File

@ -8,10 +8,10 @@ import { msg } from "@lit/localize";
import { TemplateResult, html } from "lit";
import { customElement, state } from "lit/decorators.js";
import { AdminApi, OutpostsApi, System } from "@goauthentik/api";
import { AdminApi, OutpostsApi, SystemInfo } from "@goauthentik/api";
@customElement("ak-admin-status-system")
export class SystemStatusCard extends AdminStatusCard<System> {
export class SystemStatusCard extends AdminStatusCard<SystemInfo> {
now?: Date;
icon = "pf-icon pf-icon-server";
@ -19,7 +19,7 @@ export class SystemStatusCard extends AdminStatusCard<System> {
@state()
statusSummary?: string;
async getPrimaryValue(): Promise<System> {
async getPrimaryValue(): Promise<SystemInfo> {
this.now = new Date();
let status = await new AdminApi(DEFAULT_CONFIG).adminSystemRetrieve();
if (status.embeddedOutpostHost === "" || !status.embeddedOutpostHost.includes("http")) {
@ -50,7 +50,7 @@ export class SystemStatusCard extends AdminStatusCard<System> {
});
}
getStatus(value: System): Promise<AdminStatus> {
getStatus(value: SystemInfo): Promise<AdminStatus> {
if (value.embeddedOutpostHost === "") {
this.statusSummary = msg("Warning");
return Promise.resolve<AdminStatus>({

View File

@ -1,5 +1,5 @@
import "@goauthentik/admin/groups/GroupForm";
import "@goauthentik/admin/users/RelatedUserList";
import "@goauthentik/app/admin/groups/RelatedUserList";
import "@goauthentik/app/elements/rbac/ObjectPermissionsPage";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { EVENT_REFRESH } from "@goauthentik/common/constants";

View File

@ -24,7 +24,7 @@ import { UserOption } from "@goauthentik/elements/user/utils";
import "@patternfly/elements/pf-tooltip/pf-tooltip.js";
import { msg, str } from "@lit/localize";
import { CSSResult, TemplateResult, html } from "lit";
import { CSSResult, TemplateResult, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
@ -402,7 +402,16 @@ export class RelatedUserList extends Table<User> {
<ak-forms-modal>
<span slot="submit"> ${msg("Create")} </span>
<span slot="header"> ${msg("Create User")} </span>
<ak-user-form slot="form"> </ak-user-form>
${this.targetGroup
? html`
<div class="pf-c-banner pf-m-info" slot="above-form">
${msg(
str`This user will be added to the group "${this.targetGroup.name}".`,
)}
</div>
`
: nothing}
<ak-user-form .group=${this.targetGroup} slot="form"> </ak-user-form>
<a slot="trigger" class="pf-c-dropdown__menu-item">
${msg("Create user")}
</a>
@ -415,7 +424,17 @@ export class RelatedUserList extends Table<User> {
>
<span slot="submit"> ${msg("Create")} </span>
<span slot="header"> ${msg("Create Service account")} </span>
<ak-user-service-account slot="form"> </ak-user-service-account>
${this.targetGroup
? html`
<div class="pf-c-banner pf-m-info" slot="above-form">
${msg(
str`This user will be added to the group "${this.targetGroup.name}".`,
)}
</div>
`
: nothing}
<ak-user-service-account-form .group=${this.targetGroup} slot="form">
</ak-user-service-account-form>
<a slot="trigger" class="pf-c-dropdown__menu-item">
${msg("Create Service account")}
</a>

View File

@ -22,17 +22,14 @@ export class PasswordPolicyForm extends ModelForm<PasswordPolicy, string> {
@state()
showZxcvbn = false;
loadInstance(pk: string): Promise<PasswordPolicy> {
return new PoliciesApi(DEFAULT_CONFIG)
.policiesPasswordRetrieve({
policyUuid: pk,
})
.then((policy) => {
this.showStatic = policy.checkStaticRules || false;
this.showHIBP = policy.checkHaveIBeenPwned || false;
this.showZxcvbn = policy.checkZxcvbn || false;
return policy;
});
async loadInstance(pk: string): Promise<PasswordPolicy> {
const policy = await new PoliciesApi(DEFAULT_CONFIG).policiesPasswordRetrieve({
policyUuid: pk,
});
this.showStatic = policy.checkStaticRules || false;
this.showHIBP = policy.checkHaveIBeenPwned || false;
this.showZxcvbn = policy.checkZxcvbn || false;
return policy;
}
getSuccessMessage(): string {
@ -200,26 +197,26 @@ export class PasswordPolicyForm extends ModelForm<PasswordPolicy, string> {
)}
</p>
<p class="pf-c-form__helper-text">
${msg("0: Too guessable: risky password. (guesses < 10^3)")}
${msg("0: Too guessable: risky password. (guesses &lt; 10^3)")}
</p>
<p class="pf-c-form__helper-text">
${msg(
"1: Very guessable: protection from throttled online attacks. (guesses < 10^6)",
"1: Very guessable: protection from throttled online attacks. (guesses &lt; 10^6)",
)}
</p>
<p class="pf-c-form__helper-text">
${msg(
"2: Somewhat guessable: protection from unthrottled online attacks. (guesses < 10^8)",
"2: Somewhat guessable: protection from unthrottled online attacks. (guesses &lt; 10^8)",
)}
</p>
<p class="pf-c-form__helper-text">
${msg(
"3: Safely unguessable: moderate protection from offline slow-hash scenario. (guesses < 10^10)",
"3: Safely unguessable: moderate protection from offline slow-hash scenario. (guesses &lt; 10^10)",
)}
</p>
<p class="pf-c-form__helper-text">
${msg(
"4: Very unguessable: strong protection from offline slow-hash scenario. (guesses >= 10^10)",
"4: Very unguessable: strong protection from offline slow-hash scenario. (guesses &gt;= 10^10)",
)}
</p>
</ak-form-element-horizontal>

View File

@ -184,28 +184,31 @@ export class OAuthSourceForm extends ModelForm<OAuthSource, string> {
</p>
</ak-form-element-horizontal> `
: html``}
${this.providerType.slug === ProviderTypeEnum.Openidconnect
? html`
<ak-form-element-horizontal
label=${msg("OIDC Well-known URL")}
name="oidcWellKnownUrl"
>
<input
type="text"
value="${first(
this.instance?.oidcWellKnownUrl,
this.providerType.oidcWellKnownUrl,
"",
)}"
class="pf-c-form-control"
/>
<p class="pf-c-form__helper-text">
${msg(
"OIDC well-known configuration URL. Can be used to automatically configure the URLs above.",
)}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
${this.providerType.slug === ProviderTypeEnum.Openidconnect ||
this.providerType.oidcWellKnownUrl !== ""
? html`<ak-form-element-horizontal
label=${msg("OIDC Well-known URL")}
name="oidcWellKnownUrl"
>
<input
type="text"
value="${first(
this.instance?.oidcWellKnownUrl,
this.providerType.oidcWellKnownUrl,
"",
)}"
class="pf-c-form-control"
/>
<p class="pf-c-form__helper-text">
${msg(
"OIDC well-known configuration URL. Can be used to automatically configure the URLs above.",
)}
</p>
</ak-form-element-horizontal>`
: html``}
${this.providerType.slug === ProviderTypeEnum.Openidconnect ||
this.providerType.oidcJwksUrl !== ""
? html`<ak-form-element-horizontal
label=${msg("OIDC JWKS URL")}
name="oidcJwksUrl"
>
@ -224,7 +227,6 @@ export class OAuthSourceForm extends ModelForm<OAuthSource, string> {
)}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${msg("OIDC JWKS")} name="oidcJwks">
<ak-codemirror
mode=${CodeMirrorMode.JavaScript}
@ -232,8 +234,7 @@ export class OAuthSourceForm extends ModelForm<OAuthSource, string> {
>
</ak-codemirror>
<p class="pf-c-form__helper-text">${msg("Raw JWKS data.")}</p>
</ak-form-element-horizontal>
`
</ak-form-element-horizontal>`
: html``}
</div>
</ak-form-group>`;

View File

@ -89,14 +89,14 @@ export class AuthenticatorTOTPStageForm extends ModelForm<AuthenticatorTOTPStage
>
<select name="users" class="pf-c-form-control">
<option
value="${DigitsEnum.NUMBER_6}"
?selected=${this.instance?.digits === DigitsEnum.NUMBER_6}
value="${DigitsEnum._6}"
?selected=${this.instance?.digits === DigitsEnum._6}
>
${msg("6 digits, widely compatible")}
</option>
<option
value="${DigitsEnum.NUMBER_8}"
?selected=${this.instance?.digits === DigitsEnum.NUMBER_8}
value="${DigitsEnum._8}"
?selected=${this.instance?.digits === DigitsEnum._8}
>
${msg(
"8 digits, not compatible with apps like Google Authenticator",

View File

@ -4,19 +4,30 @@ import { Form } from "@goauthentik/elements/forms/Form";
import "@goauthentik/elements/forms/HorizontalFormElement";
import { ModalForm } from "@goauthentik/elements/forms/ModalForm";
import { msg } from "@lit/localize";
import { msg, str } from "@lit/localize";
import { TemplateResult, html } from "lit";
import { customElement, property } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
import { CoreApi, UserServiceAccountRequest, UserServiceAccountResponse } from "@goauthentik/api";
import {
CoreApi,
Group,
UserServiceAccountRequest,
UserServiceAccountResponse,
} from "@goauthentik/api";
@customElement("ak-user-service-account")
@customElement("ak-user-service-account-form")
export class ServiceAccountForm extends Form<UserServiceAccountRequest> {
@property({ attribute: false })
result?: UserServiceAccountResponse;
@property({ attribute: false })
group?: Group;
getSuccessMessage(): string {
if (this.group) {
return msg(str`Successfully created user and added to group ${this.group.name}`);
}
return msg("Successfully created user.");
}
@ -26,6 +37,14 @@ export class ServiceAccountForm extends Form<UserServiceAccountRequest> {
});
this.result = result;
(this.parentElement as ModalForm).showSubmitButton = false;
if (this.group) {
await new CoreApi(DEFAULT_CONFIG).coreGroupsAddUserCreate({
groupUuid: this.group.pk,
userAccountRequest: {
pk: this.result.userPk,
},
});
}
return result;
}

View File

@ -8,15 +8,18 @@ import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
import "@goauthentik/elements/forms/Radio";
import YAML from "yaml";
import { msg } from "@lit/localize";
import { msg, str } from "@lit/localize";
import { CSSResult, TemplateResult, css, html } from "lit";
import { customElement } from "lit/decorators.js";
import { customElement, property } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
import { CoreApi, User, UserTypeEnum } from "@goauthentik/api";
import { CoreApi, Group, User, UserTypeEnum } from "@goauthentik/api";
@customElement("ak-user-form")
export class UserForm extends ModelForm<User, number> {
@property({ attribute: false })
group?: Group;
static get defaultUserAttributes(): { [key: string]: unknown } {
return {};
}
@ -42,6 +45,9 @@ export class UserForm extends ModelForm<User, number> {
if (this.instance) {
return msg("Successfully updated user.");
} else {
if (this.group) {
return msg(str`Successfully created user and added to group ${this.group.name}`);
}
return msg("Successfully created user.");
}
}
@ -50,21 +56,31 @@ export class UserForm extends ModelForm<User, number> {
if (data.attributes === null) {
data.attributes = UserForm.defaultUserAttributes;
}
let user;
if (this.instance?.pk) {
return new CoreApi(DEFAULT_CONFIG).coreUsersPartialUpdate({
user = await new CoreApi(DEFAULT_CONFIG).coreUsersPartialUpdate({
id: this.instance.pk,
patchedUserRequest: data,
});
} else {
data.groups = [];
return new CoreApi(DEFAULT_CONFIG).coreUsersCreate({
user = await new CoreApi(DEFAULT_CONFIG).coreUsersCreate({
userRequest: data,
});
}
if (this.group) {
await new CoreApi(DEFAULT_CONFIG).coreGroupsAddUserCreate({
groupUuid: this.group.pk,
userAccountRequest: {
pk: user.pk,
},
});
}
return user;
}
renderForm(): TemplateResult {
return html` <ak-form-element-horizontal
return html`<ak-form-element-horizontal
label=${msg("Username")}
?required=${true}
name="username"

View File

@ -399,7 +399,7 @@ export class UserListPage extends TablePage<User> {
<ak-forms-modal .closeAfterSuccessfulSubmit=${false} .cancelText=${msg("Close")}>
<span slot="submit"> ${msg("Create")} </span>
<span slot="header"> ${msg("Create Service account")} </span>
<ak-user-service-account slot="form"> </ak-user-service-account>
<ak-user-service-account-form slot="form"> </ak-user-service-account-form>
<button slot="trigger" class="pf-c-button pf-m-secondary">
${msg("Create Service account")}
</button>

View File

@ -194,7 +194,6 @@ export abstract class Table<T> extends AKElement {
this.data = await this.apiEndpoint(this.page);
this.error = undefined;
this.page = this.data.pagination.current;
const newSelected: T[] = [];
const newExpanded: T[] = [];
this.data.results.forEach((res) => {
const jsonRes = JSON.stringify(res);
@ -214,18 +213,12 @@ export abstract class Table<T> extends AKElement {
);
};
}
const selectedIndex = this.selectedElements.findIndex(comp);
if (selectedIndex > -1) {
newSelected.push(res);
}
const expandedIndex = this.expandedElements.findIndex(comp);
if (expandedIndex > -1) {
newExpanded.push(res);
}
});
this.isLoading = false;
this.selectedElements = newSelected;
this.expandedElements = newExpanded;
} catch (ex) {
this.isLoading = false;

View File

@ -75,6 +75,8 @@ export class IdentificationStage extends BaseStage<
// meaning that without the auto-redirect the user would only have the option
// to manually click on the source button
if ((this.challenge.userFields || []).length !== 0) return;
// we also don't want to auto-redirect if there's a passwordless URL configured
if (this.challenge.passwordlessUrl) return;
const source = this.challenge.sources[0];
this.host.challenge = source.challenge;
}