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:
Roney Dsilva
2024-03-25 17:24:40 +05:30
committed by GitHub
parent 1e25d3e3e9
commit d7e399dbf9
8 changed files with 106 additions and 13 deletions

View File

@ -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",

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 || "",
});
}}
>

View File

@ -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",
},
},
},
};

View File

@ -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}"

View File

@ -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"