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,
|
"title": flow.title,
|
||||||
"layout": "stacked",
|
"layout": "stacked",
|
||||||
},
|
},
|
||||||
|
"flow_designation": "authentication",
|
||||||
"type": ChallengeTypes.NATIVE.value,
|
"type": ChallengeTypes.NATIVE.value,
|
||||||
"password_fields": False,
|
"password_fields": False,
|
||||||
"primary_action": "Log in",
|
"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,
|
stage=stage_view.executor.current_stage,
|
||||||
device_class=DeviceClasses.TOTP.value,
|
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
|
return device
|
||||||
|
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ from django.db.models import Q
|
|||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
from drf_spectacular.utils import PolymorphicProxySerializer, extend_schema_field
|
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 rest_framework.serializers import ValidationError
|
||||||
from sentry_sdk.hub import Hub
|
from sentry_sdk.hub import Hub
|
||||||
|
|
||||||
@ -66,6 +66,7 @@ class IdentificationChallenge(Challenge):
|
|||||||
user_fields = ListField(child=CharField(), allow_empty=True, allow_null=True)
|
user_fields = ListField(child=CharField(), allow_empty=True, allow_null=True)
|
||||||
password_fields = BooleanField()
|
password_fields = BooleanField()
|
||||||
application_pre = CharField(required=False)
|
application_pre = CharField(required=False)
|
||||||
|
flow_designation = ChoiceField(FlowDesignation.choices)
|
||||||
|
|
||||||
enroll_url = CharField(required=False)
|
enroll_url = CharField(required=False)
|
||||||
recovery_url = CharField(required=False)
|
recovery_url = CharField(required=False)
|
||||||
@ -194,11 +195,12 @@ class IdentificationStageView(ChallengeStageView):
|
|||||||
challenge = IdentificationChallenge(
|
challenge = IdentificationChallenge(
|
||||||
data={
|
data={
|
||||||
"type": ChallengeTypes.NATIVE.value,
|
"type": ChallengeTypes.NATIVE.value,
|
||||||
"primary_action": self.get_primary_action(),
|
|
||||||
"component": "ak-stage-identification",
|
"component": "ak-stage-identification",
|
||||||
|
"primary_action": self.get_primary_action(),
|
||||||
"user_fields": current_stage.user_fields,
|
"user_fields": current_stage.user_fields,
|
||||||
"password_fields": bool(current_stage.password_stage),
|
"password_fields": bool(current_stage.password_stage),
|
||||||
"show_source_labels": current_stage.show_source_labels,
|
"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
|
# 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:
|
pk:
|
||||||
type: string
|
type: string
|
||||||
format: uuid
|
format: uuid
|
||||||
readOnly: true
|
title: Connection token uuid
|
||||||
title: Pbm uuid
|
|
||||||
provider:
|
provider:
|
||||||
type: integer
|
type: integer
|
||||||
provider_obj:
|
provider_obj:
|
||||||
@ -31793,7 +31792,6 @@ components:
|
|||||||
endpoint:
|
endpoint:
|
||||||
type: string
|
type: string
|
||||||
format: uuid
|
format: uuid
|
||||||
readOnly: true
|
|
||||||
endpoint_obj:
|
endpoint_obj:
|
||||||
allOf:
|
allOf:
|
||||||
- $ref: '#/components/schemas/Endpoint'
|
- $ref: '#/components/schemas/Endpoint'
|
||||||
@ -31805,7 +31803,6 @@ components:
|
|||||||
required:
|
required:
|
||||||
- endpoint
|
- endpoint
|
||||||
- endpoint_obj
|
- endpoint_obj
|
||||||
- pk
|
|
||||||
- provider
|
- provider
|
||||||
- provider_obj
|
- provider_obj
|
||||||
- user
|
- user
|
||||||
@ -31813,9 +31810,17 @@ components:
|
|||||||
type: object
|
type: object
|
||||||
description: ConnectionToken Serializer
|
description: ConnectionToken Serializer
|
||||||
properties:
|
properties:
|
||||||
|
pk:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
title: Connection token uuid
|
||||||
provider:
|
provider:
|
||||||
type: integer
|
type: integer
|
||||||
|
endpoint:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
required:
|
required:
|
||||||
|
- endpoint
|
||||||
- provider
|
- provider
|
||||||
ConsentChallenge:
|
ConsentChallenge:
|
||||||
type: object
|
type: object
|
||||||
@ -34332,6 +34337,8 @@ components:
|
|||||||
type: boolean
|
type: boolean
|
||||||
application_pre:
|
application_pre:
|
||||||
type: string
|
type: string
|
||||||
|
flow_designation:
|
||||||
|
$ref: '#/components/schemas/FlowDesignationEnum'
|
||||||
enroll_url:
|
enroll_url:
|
||||||
type: string
|
type: string
|
||||||
recovery_url:
|
recovery_url:
|
||||||
@ -34347,6 +34354,7 @@ components:
|
|||||||
show_source_labels:
|
show_source_labels:
|
||||||
type: boolean
|
type: boolean
|
||||||
required:
|
required:
|
||||||
|
- flow_designation
|
||||||
- password_fields
|
- password_fields
|
||||||
- primary_action
|
- primary_action
|
||||||
- show_source_labels
|
- show_source_labels
|
||||||
@ -38586,8 +38594,15 @@ components:
|
|||||||
type: object
|
type: object
|
||||||
description: ConnectionToken Serializer
|
description: ConnectionToken Serializer
|
||||||
properties:
|
properties:
|
||||||
|
pk:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
title: Connection token uuid
|
||||||
provider:
|
provider:
|
||||||
type: integer
|
type: integer
|
||||||
|
endpoint:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
PatchedConsentStageRequest:
|
PatchedConsentStageRequest:
|
||||||
type: object
|
type: object
|
||||||
description: ConsentStage Serializer
|
description: ConsentStage Serializer
|
||||||
@ -45584,8 +45599,8 @@ components:
|
|||||||
description: Get latest version from cache
|
description: Get latest version from cache
|
||||||
readOnly: true
|
readOnly: true
|
||||||
version_latest_valid:
|
version_latest_valid:
|
||||||
type: boolean
|
type: string
|
||||||
description: Latest version query is a valid non-default value
|
description: Check if latest version is valid
|
||||||
readOnly: true
|
readOnly: true
|
||||||
build_hash:
|
build_hash:
|
||||||
type: string
|
type: string
|
||||||
|
@ -61,12 +61,12 @@ export class ConnectionTokenListPage extends Table<ConnectionToken> {
|
|||||||
}}
|
}}
|
||||||
.usedBy=${(item: ConnectionToken) => {
|
.usedBy=${(item: ConnectionToken) => {
|
||||||
return new RacApi(DEFAULT_CONFIG).racConnectionTokensUsedByList({
|
return new RacApi(DEFAULT_CONFIG).racConnectionTokensUsedByList({
|
||||||
connectionTokenUuid: item.pk,
|
connectionTokenUuid: item.pk || "",
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
.delete=${(item: ConnectionToken) => {
|
.delete=${(item: ConnectionToken) => {
|
||||||
return new RacApi(DEFAULT_CONFIG).racConnectionTokensDestroy({
|
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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</ak-form-element>
|
</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
|
<ak-form-element
|
||||||
label="${msg("Code")}"
|
label="${msg("Code")}"
|
||||||
?required="${true}"
|
?required="${true}"
|
||||||
|
@ -5,7 +5,7 @@ import "@goauthentik/elements/forms/FormElement";
|
|||||||
import { BaseStage } from "@goauthentik/flow/stages/base";
|
import { BaseStage } from "@goauthentik/flow/stages/base";
|
||||||
|
|
||||||
import { msg, str } from "@lit/localize";
|
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 { customElement } from "lit/decorators.js";
|
||||||
|
|
||||||
import PFAlert from "@patternfly/patternfly/components/Alert/alert.css";
|
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 PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
FlowDesignationEnum,
|
||||||
IdentificationChallenge,
|
IdentificationChallenge,
|
||||||
IdentificationChallengeResponseRequest,
|
IdentificationChallengeResponseRequest,
|
||||||
LoginSource,
|
LoginSource,
|
||||||
@ -220,7 +221,16 @@ export class IdentificationStage extends BaseStage<
|
|||||||
[UserFieldsEnum.Upn]: msg("UPN"),
|
[UserFieldsEnum.Upn]: msg("UPN"),
|
||||||
};
|
};
|
||||||
const label = OR_LIST_FORMATTERS.format(fields.map((f) => uiFields[f]));
|
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}
|
label=${label}
|
||||||
?required="${true}"
|
?required="${true}"
|
||||||
class="pf-c-form__group"
|
class="pf-c-form__group"
|
||||||
|
Reference in New Issue
Block a user