web/flow: general ux improvements (#8558)
* message fixes * format Signed-off-by: Jens Langhammer <jens@goauthentik.io> * remove inline css, reword Signed-off-by: Jens Langhammer <jens@goauthentik.io> * don't rely on flow naming to show message Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix tests Signed-off-by: Jens Langhammer <jens@goauthentik.io> --------- Signed-off-by: Jens Langhammer <jens@goauthentik.io> Co-authored-by: roney <roney.dsilva@cdmx.in> Co-authored-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
@ -53,6 +53,7 @@ class TestFlowInspector(APITestCase):
|
||||
"title": flow.title,
|
||||
"layout": "stacked",
|
||||
},
|
||||
"flow_designation": "authentication",
|
||||
"type": ChallengeTypes.NATIVE.value,
|
||||
"password_fields": False,
|
||||
"primary_action": "Log in",
|
||||
|
@ -120,7 +120,9 @@ def validate_challenge_code(code: str, stage_view: StageView, user: User) -> Dev
|
||||
stage=stage_view.executor.current_stage,
|
||||
device_class=DeviceClasses.TOTP.value,
|
||||
)
|
||||
raise ValidationError(_("Invalid Token"))
|
||||
raise ValidationError(
|
||||
_("Invalid Token. Please ensure the time on your device is accurate and try again.")
|
||||
)
|
||||
return device
|
||||
|
||||
|
||||
|
@ -10,7 +10,7 @@ from django.db.models import Q
|
||||
from django.http import HttpResponse
|
||||
from django.utils.translation import gettext as _
|
||||
from drf_spectacular.utils import PolymorphicProxySerializer, extend_schema_field
|
||||
from rest_framework.fields import BooleanField, CharField, DictField, ListField
|
||||
from rest_framework.fields import BooleanField, CharField, ChoiceField, DictField, ListField
|
||||
from rest_framework.serializers import ValidationError
|
||||
from sentry_sdk.hub import Hub
|
||||
|
||||
@ -66,6 +66,7 @@ class IdentificationChallenge(Challenge):
|
||||
user_fields = ListField(child=CharField(), allow_empty=True, allow_null=True)
|
||||
password_fields = BooleanField()
|
||||
application_pre = CharField(required=False)
|
||||
flow_designation = ChoiceField(FlowDesignation.choices)
|
||||
|
||||
enroll_url = CharField(required=False)
|
||||
recovery_url = CharField(required=False)
|
||||
@ -194,11 +195,12 @@ class IdentificationStageView(ChallengeStageView):
|
||||
challenge = IdentificationChallenge(
|
||||
data={
|
||||
"type": ChallengeTypes.NATIVE.value,
|
||||
"primary_action": self.get_primary_action(),
|
||||
"component": "ak-stage-identification",
|
||||
"primary_action": self.get_primary_action(),
|
||||
"user_fields": current_stage.user_fields,
|
||||
"password_fields": bool(current_stage.password_stage),
|
||||
"show_source_labels": current_stage.show_source_labels,
|
||||
"flow_designation": self.executor.flow.designation,
|
||||
}
|
||||
)
|
||||
# If the user has been redirected to us whilst trying to access an
|
||||
|
27
schema.yml
27
schema.yml
@ -31782,8 +31782,7 @@ components:
|
||||
pk:
|
||||
type: string
|
||||
format: uuid
|
||||
readOnly: true
|
||||
title: Pbm uuid
|
||||
title: Connection token uuid
|
||||
provider:
|
||||
type: integer
|
||||
provider_obj:
|
||||
@ -31793,7 +31792,6 @@ components:
|
||||
endpoint:
|
||||
type: string
|
||||
format: uuid
|
||||
readOnly: true
|
||||
endpoint_obj:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/Endpoint'
|
||||
@ -31805,7 +31803,6 @@ components:
|
||||
required:
|
||||
- endpoint
|
||||
- endpoint_obj
|
||||
- pk
|
||||
- provider
|
||||
- provider_obj
|
||||
- user
|
||||
@ -31813,9 +31810,17 @@ components:
|
||||
type: object
|
||||
description: ConnectionToken Serializer
|
||||
properties:
|
||||
pk:
|
||||
type: string
|
||||
format: uuid
|
||||
title: Connection token uuid
|
||||
provider:
|
||||
type: integer
|
||||
endpoint:
|
||||
type: string
|
||||
format: uuid
|
||||
required:
|
||||
- endpoint
|
||||
- provider
|
||||
ConsentChallenge:
|
||||
type: object
|
||||
@ -34332,6 +34337,8 @@ components:
|
||||
type: boolean
|
||||
application_pre:
|
||||
type: string
|
||||
flow_designation:
|
||||
$ref: '#/components/schemas/FlowDesignationEnum'
|
||||
enroll_url:
|
||||
type: string
|
||||
recovery_url:
|
||||
@ -34347,6 +34354,7 @@ components:
|
||||
show_source_labels:
|
||||
type: boolean
|
||||
required:
|
||||
- flow_designation
|
||||
- password_fields
|
||||
- primary_action
|
||||
- show_source_labels
|
||||
@ -38586,8 +38594,15 @@ components:
|
||||
type: object
|
||||
description: ConnectionToken Serializer
|
||||
properties:
|
||||
pk:
|
||||
type: string
|
||||
format: uuid
|
||||
title: Connection token uuid
|
||||
provider:
|
||||
type: integer
|
||||
endpoint:
|
||||
type: string
|
||||
format: uuid
|
||||
PatchedConsentStageRequest:
|
||||
type: object
|
||||
description: ConsentStage Serializer
|
||||
@ -45584,8 +45599,8 @@ components:
|
||||
description: Get latest version from cache
|
||||
readOnly: true
|
||||
version_latest_valid:
|
||||
type: boolean
|
||||
description: Latest version query is a valid non-default value
|
||||
type: string
|
||||
description: Check if latest version is valid
|
||||
readOnly: true
|
||||
build_hash:
|
||||
type: string
|
||||
|
@ -61,12 +61,12 @@ export class ConnectionTokenListPage extends Table<ConnectionToken> {
|
||||
}}
|
||||
.usedBy=${(item: ConnectionToken) => {
|
||||
return new RacApi(DEFAULT_CONFIG).racConnectionTokensUsedByList({
|
||||
connectionTokenUuid: item.pk,
|
||||
connectionTokenUuid: item.pk || "",
|
||||
});
|
||||
}}
|
||||
.delete=${(item: ConnectionToken) => {
|
||||
return new RacApi(DEFAULT_CONFIG).racConnectionTokensDestroy({
|
||||
connectionTokenUuid: item.pk,
|
||||
connectionTokenUuid: item.pk || "",
|
||||
});
|
||||
}}
|
||||
>
|
||||
|
@ -0,0 +1,58 @@
|
||||
import type { StoryObj } from "@storybook/web-components";
|
||||
|
||||
import { html } from "lit";
|
||||
|
||||
import "@patternfly/patternfly/components/Login/login.css";
|
||||
|
||||
import { AuthenticatorTOTPChallenge, ChallengeChoices, UiThemeEnum } from "@goauthentik/api";
|
||||
|
||||
import "../../../stories/flow-interface";
|
||||
import "./AuthenticatorTOTPStage";
|
||||
|
||||
export default {
|
||||
title: "Flow / Stages / AuthenticatorTOTPStage",
|
||||
};
|
||||
|
||||
export const LoadingNoChallenge = () => {
|
||||
return html`<ak-storybook-interface theme=${UiThemeEnum.Dark}>
|
||||
<div class="pf-c-login">
|
||||
<div class="pf-c-login__container">
|
||||
<div class="pf-c-login__main">
|
||||
<ak-stage-authenticator-totp></ak-stage-authenticator-totp>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ak-storybook-interface>`;
|
||||
};
|
||||
|
||||
export const Challenge: StoryObj = {
|
||||
render: ({ theme, challenge }) => {
|
||||
return html`<ak-storybook-interface theme=${theme}>
|
||||
<div class="pf-c-login">
|
||||
<div class="pf-c-login__container">
|
||||
<div class="pf-c-login__main">
|
||||
<ak-stage-authenticator-totp
|
||||
.challenge=${challenge}
|
||||
></ak-stage-authenticator-totp>
|
||||
</div>
|
||||
</div></div
|
||||
></ak-storybook-interface>`;
|
||||
},
|
||||
args: {
|
||||
theme: "automatic",
|
||||
challenge: {
|
||||
type: ChallengeChoices.Native,
|
||||
pendingUser: "foo",
|
||||
pendingUserAvatar: "https://picsum.photos/64",
|
||||
configUrl: "",
|
||||
} as AuthenticatorTOTPChallenge,
|
||||
},
|
||||
argTypes: {
|
||||
theme: {
|
||||
options: [UiThemeEnum.Automatic, UiThemeEnum.Light, UiThemeEnum.Dark],
|
||||
control: {
|
||||
type: "select",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
@ -106,6 +106,11 @@ export class AuthenticatorTOTPStage extends BaseStage<
|
||||
</button>
|
||||
</div>
|
||||
</ak-form-element>
|
||||
<p>
|
||||
${msg(
|
||||
"Please scan the QR code above using the Microsoft Authenticator, Google Authenticator, or other authenticator apps on your device, and enter the code the device displays below to finish setting up the MFA device.",
|
||||
)}
|
||||
</p>
|
||||
<ak-form-element
|
||||
label="${msg("Code")}"
|
||||
?required="${true}"
|
||||
|
@ -5,7 +5,7 @@ import "@goauthentik/elements/forms/FormElement";
|
||||
import { BaseStage } from "@goauthentik/flow/stages/base";
|
||||
|
||||
import { msg, str } from "@lit/localize";
|
||||
import { CSSResult, TemplateResult, css, html } from "lit";
|
||||
import { CSSResult, TemplateResult, css, html, nothing } from "lit";
|
||||
import { customElement } from "lit/decorators.js";
|
||||
|
||||
import PFAlert from "@patternfly/patternfly/components/Alert/alert.css";
|
||||
@ -17,6 +17,7 @@ import PFTitle from "@patternfly/patternfly/components/Title/title.css";
|
||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||
|
||||
import {
|
||||
FlowDesignationEnum,
|
||||
IdentificationChallenge,
|
||||
IdentificationChallengeResponseRequest,
|
||||
LoginSource,
|
||||
@ -220,7 +221,16 @@ export class IdentificationStage extends BaseStage<
|
||||
[UserFieldsEnum.Upn]: msg("UPN"),
|
||||
};
|
||||
const label = OR_LIST_FORMATTERS.format(fields.map((f) => uiFields[f]));
|
||||
return html`<ak-form-element
|
||||
return html`${this.challenge.flowDesignation === FlowDesignationEnum.Recovery
|
||||
? html`
|
||||
<p>
|
||||
${msg(
|
||||
"Enter the email associated with your account, and we'll send you a link to reset your password.",
|
||||
)}
|
||||
</p>
|
||||
`
|
||||
: nothing}
|
||||
<ak-form-element
|
||||
label=${label}
|
||||
?required="${true}"
|
||||
class="pf-c-form__group"
|
||||
|
Reference in New Issue
Block a user