stages/authentiactor_validate: improve error handling for duo

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
Jens Langhammer 2022-07-28 20:57:29 +02:00
parent 1f90359310
commit 0248755cda
14 changed files with 180 additions and 87 deletions

View file

@ -1,10 +1,12 @@
"""Validation stage challenge checking""" """Validation stage challenge checking"""
from json import dumps, loads from json import dumps, loads
from typing import Optional from typing import Optional
from urllib.parse import urlencode
from django.http import HttpRequest from django.http import HttpRequest
from django.http.response import Http404 from django.http.response import Http404
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.utils.translation import gettext as __
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django_otp import match_token from django_otp import match_token
from django_otp.models import Device from django_otp.models import Device
@ -17,9 +19,11 @@ from webauthn.helpers.exceptions import InvalidAuthenticationResponse
from webauthn.helpers.structs import AuthenticationCredential from webauthn.helpers.structs import AuthenticationCredential
from authentik.core.api.utils import PassiveSerializer from authentik.core.api.utils import PassiveSerializer
from authentik.core.models import User from authentik.core.models import Application, User
from authentik.core.signals import login_failed from authentik.core.signals import login_failed
from authentik.events.models import Event, EventAction
from authentik.flows.stage import StageView from authentik.flows.stage import StageView
from authentik.flows.views.executor import SESSION_KEY_APPLICATION_PRE
from authentik.lib.utils.http import get_client_ip from authentik.lib.utils.http import get_client_ip
from authentik.stages.authenticator_duo.models import AuthenticatorDuoStage, DuoDevice from authentik.stages.authenticator_duo.models import AuthenticatorDuoStage, DuoDevice
from authentik.stages.authenticator_sms.models import SMSDevice from authentik.stages.authenticator_sms.models import SMSDevice
@ -27,6 +31,7 @@ from authentik.stages.authenticator_validate.models import DeviceClasses
from authentik.stages.authenticator_webauthn.models import WebAuthnDevice from authentik.stages.authenticator_webauthn.models import WebAuthnDevice
from authentik.stages.authenticator_webauthn.stage import SESSION_KEY_WEBAUTHN_CHALLENGE from authentik.stages.authenticator_webauthn.stage import SESSION_KEY_WEBAUTHN_CHALLENGE
from authentik.stages.authenticator_webauthn.utils import get_origin, get_rp_id from authentik.stages.authenticator_webauthn.utils import get_origin, get_rp_id
from authentik.stages.consent.stage import PLAN_CONTEXT_CONSENT_TITLE
LOGGER = get_logger() LOGGER = get_logger()
@ -155,23 +160,49 @@ def validate_challenge_duo(device_pk: int, stage_view: StageView, user: User) ->
LOGGER.warning("device mismatch") LOGGER.warning("device mismatch")
raise Http404 raise Http404
stage: AuthenticatorDuoStage = device.stage stage: AuthenticatorDuoStage = device.stage
response = stage.client.auth(
"auto", # Get additional context for push
user_id=device.duo_user_id, pushinfo = {
ipaddr=get_client_ip(stage_view.request), __("Domain"): stage_view.request.get_host(),
type="authentik Login request", }
display_username=user.username, if PLAN_CONTEXT_CONSENT_TITLE in stage_view.executor.plan.context:
device="auto", pushinfo[__("Title")] = stage_view.executor.plan.context[PLAN_CONTEXT_CONSENT_TITLE]
) if SESSION_KEY_APPLICATION_PRE in stage_view.request.session:
# {'result': 'allow', 'status': 'allow', 'status_msg': 'Success. Logging you in...'} pushinfo[__("Application")] = stage_view.request.session.get(
if response["result"] == "deny": SESSION_KEY_APPLICATION_PRE, Application()
login_failed.send( ).name
sender=__name__,
credentials={"username": user.username}, try:
request=stage_view.request, response = stage.client.auth(
stage=stage_view.executor.current_stage, "auto",
device_class=DeviceClasses.DUO.value, user_id=device.duo_user_id,
ipaddr=get_client_ip(stage_view.request),
type=__(
"%(brand_name)s Login request"
% {
"brand_name": stage_view.request.tenant.branding_title,
}
),
display_username=user.username,
device="auto",
pushinfo=urlencode(pushinfo),
) )
# {'result': 'allow', 'status': 'allow', 'status_msg': 'Success. Logging you in...'}
if response["result"] == "deny":
login_failed.send(
sender=__name__,
credentials={"username": user.username},
request=stage_view.request,
stage=stage_view.executor.current_stage,
device_class=DeviceClasses.DUO.value,
)
raise ValidationError("Duo denied access")
device.save()
return device
except RuntimeError as exc:
Event.new(
EventAction.CONFIGURATION_ERROR,
message=f"Failed to DUO authenticate user: {str(exc)}",
user=user,
).from_http(stage_view.request, user)
raise ValidationError("Duo denied access") raise ValidationError("Duo denied access")
device.save()
return device

View file

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-07-02 15:10+0000\n" "POT-Creation-Date: 2022-07-28 19:11+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -39,11 +39,11 @@ msgstr ""
msgid "Create a SAML Provider by importing its Metadata." msgid "Create a SAML Provider by importing its Metadata."
msgstr "" msgstr ""
#: authentik/core/api/users.py:90 #: authentik/core/api/users.py:93
msgid "No leading or trailing slashes allowed." msgid "No leading or trailing slashes allowed."
msgstr "" msgstr ""
#: authentik/core/api/users.py:93 #: authentik/core/api/users.py:96
msgid "No empty segments in user path allowed." msgid "No empty segments in user path allowed."
msgstr "" msgstr ""
@ -59,105 +59,105 @@ msgstr ""
msgid "User's display name." msgid "User's display name."
msgstr "" msgstr ""
#: authentik/core/models.py:237 authentik/providers/oauth2/models.py:318 #: authentik/core/models.py:239 authentik/providers/oauth2/models.py:321
msgid "User" msgid "User"
msgstr "" msgstr ""
#: authentik/core/models.py:238 #: authentik/core/models.py:240
msgid "Users" msgid "Users"
msgstr "" msgstr ""
#: authentik/core/models.py:249 #: authentik/core/models.py:251
msgid "Flow used when authorizing this provider." msgid "Flow used when authorizing this provider."
msgstr "" msgstr ""
#: authentik/core/models.py:282 #: authentik/core/models.py:284
msgid "Application's display Name." msgid "Application's display Name."
msgstr "" msgstr ""
#: authentik/core/models.py:283 #: authentik/core/models.py:285
msgid "Internal application name, used in URLs." msgid "Internal application name, used in URLs."
msgstr "" msgstr ""
#: authentik/core/models.py:295 #: authentik/core/models.py:297
msgid "Open launch URL in a new browser tab or window." msgid "Open launch URL in a new browser tab or window."
msgstr "" msgstr ""
#: authentik/core/models.py:354 #: authentik/core/models.py:356
msgid "Application" msgid "Application"
msgstr "" msgstr ""
#: authentik/core/models.py:355 #: authentik/core/models.py:357
msgid "Applications" msgid "Applications"
msgstr "" msgstr ""
#: authentik/core/models.py:361 #: authentik/core/models.py:363
msgid "Use the source-specific identifier" msgid "Use the source-specific identifier"
msgstr "" msgstr ""
#: authentik/core/models.py:369 #: authentik/core/models.py:371
msgid "" msgid ""
"Use the user's email address, but deny enrollment when the email address " "Use the user's email address, but deny enrollment when the email address "
"already exists." "already exists."
msgstr "" msgstr ""
#: authentik/core/models.py:378 #: authentik/core/models.py:380
msgid "" msgid ""
"Use the user's username, but deny enrollment when the username already " "Use the user's username, but deny enrollment when the username already "
"exists." "exists."
msgstr "" msgstr ""
#: authentik/core/models.py:385 #: authentik/core/models.py:387
msgid "Source's display Name." msgid "Source's display Name."
msgstr "" msgstr ""
#: authentik/core/models.py:386 #: authentik/core/models.py:388
msgid "Internal source name, used in URLs." msgid "Internal source name, used in URLs."
msgstr "" msgstr ""
#: authentik/core/models.py:399 #: authentik/core/models.py:401
msgid "Flow to use when authenticating existing users." msgid "Flow to use when authenticating existing users."
msgstr "" msgstr ""
#: authentik/core/models.py:408 #: authentik/core/models.py:410
msgid "Flow to use when enrolling new users." msgid "Flow to use when enrolling new users."
msgstr "" msgstr ""
#: authentik/core/models.py:557 #: authentik/core/models.py:560
msgid "Token" msgid "Token"
msgstr "" msgstr ""
#: authentik/core/models.py:558 #: authentik/core/models.py:561
msgid "Tokens" msgid "Tokens"
msgstr "" msgstr ""
#: authentik/core/models.py:601 #: authentik/core/models.py:604
msgid "Property Mapping" msgid "Property Mapping"
msgstr "" msgstr ""
#: authentik/core/models.py:602 #: authentik/core/models.py:605
msgid "Property Mappings" msgid "Property Mappings"
msgstr "" msgstr ""
#: authentik/core/models.py:638 #: authentik/core/models.py:641
msgid "Authenticated Session" msgid "Authenticated Session"
msgstr "" msgstr ""
#: authentik/core/models.py:639 #: authentik/core/models.py:642
msgid "Authenticated Sessions" msgid "Authenticated Sessions"
msgstr "" msgstr ""
#: authentik/core/sources/flow_manager.py:177 #: authentik/core/sources/flow_manager.py:176
msgid "source" msgid "source"
msgstr "" msgstr ""
#: authentik/core/sources/flow_manager.py:245 #: authentik/core/sources/flow_manager.py:243
#: authentik/core/sources/flow_manager.py:283 #: authentik/core/sources/flow_manager.py:281
#, python-format #, python-format
msgid "Successfully authenticated with %(source)s!" msgid "Successfully authenticated with %(source)s!"
msgstr "" msgstr ""
#: authentik/core/sources/flow_manager.py:264 #: authentik/core/sources/flow_manager.py:262
#, python-format #, python-format
msgid "Successfully linked %(source)s!" msgid "Successfully linked %(source)s!"
msgstr "" msgstr ""
@ -168,8 +168,8 @@ msgstr ""
#: authentik/core/templates/if/admin.html:18 #: authentik/core/templates/if/admin.html:18
#: authentik/core/templates/if/admin.html:24 #: authentik/core/templates/if/admin.html:24
#: authentik/core/templates/if/flow.html:35 #: authentik/core/templates/if/flow.html:37
#: authentik/core/templates/if/flow.html:41 #: authentik/core/templates/if/flow.html:43
#: authentik/core/templates/if/user.html:18 #: authentik/core/templates/if/user.html:18
#: authentik/core/templates/if/user.html:24 #: authentik/core/templates/if/user.html:24
msgid "Loading..." msgid "Loading..."
@ -355,6 +355,10 @@ msgstr ""
msgid "Flow not applicable to current user/request: %(messages)s" msgid "Flow not applicable to current user/request: %(messages)s"
msgstr "" msgstr ""
#: authentik/flows/exceptions.py:17
msgid "Flow does not apply to current user (denied by policy)."
msgstr ""
#: authentik/flows/models.py:117 #: authentik/flows/models.py:117
msgid "Visible in the URL." msgid "Visible in the URL."
msgstr "" msgstr ""
@ -742,98 +746,104 @@ msgstr ""
msgid "Client Type" msgid "Client Type"
msgstr "" msgstr ""
#: authentik/providers/oauth2/models.py:151 #: authentik/providers/oauth2/models.py:147
msgid ""
"Confidential clients are capable of maintaining the confidentiality of their "
"credentials. Public clients are incapable"
msgstr ""
#: authentik/providers/oauth2/models.py:154
msgid "Client ID" msgid "Client ID"
msgstr "" msgstr ""
#: authentik/providers/oauth2/models.py:157 #: authentik/providers/oauth2/models.py:160
msgid "Client Secret" msgid "Client Secret"
msgstr "" msgstr ""
#: authentik/providers/oauth2/models.py:163 #: authentik/providers/oauth2/models.py:166
msgid "Redirect URIs" msgid "Redirect URIs"
msgstr "" msgstr ""
#: authentik/providers/oauth2/models.py:164 #: authentik/providers/oauth2/models.py:167
msgid "Enter each URI on a new line." msgid "Enter each URI on a new line."
msgstr "" msgstr ""
#: authentik/providers/oauth2/models.py:169 #: authentik/providers/oauth2/models.py:172
msgid "Include claims in id_token" msgid "Include claims in id_token"
msgstr "" msgstr ""
#: authentik/providers/oauth2/models.py:217 #: authentik/providers/oauth2/models.py:220
msgid "Signing Key" msgid "Signing Key"
msgstr "" msgstr ""
#: authentik/providers/oauth2/models.py:221 #: authentik/providers/oauth2/models.py:224
msgid "" msgid ""
"Key used to sign the tokens. Only required when JWT Algorithm is set to " "Key used to sign the tokens. Only required when JWT Algorithm is set to "
"RS256." "RS256."
msgstr "" msgstr ""
#: authentik/providers/oauth2/models.py:228 #: authentik/providers/oauth2/models.py:231
msgid "" msgid ""
"Any JWT signed by the JWK of the selected source can be used to authenticate." "Any JWT signed by the JWK of the selected source can be used to authenticate."
msgstr "" msgstr ""
#: authentik/providers/oauth2/models.py:310 #: authentik/providers/oauth2/models.py:313
msgid "OAuth2/OpenID Provider" msgid "OAuth2/OpenID Provider"
msgstr "" msgstr ""
#: authentik/providers/oauth2/models.py:311 #: authentik/providers/oauth2/models.py:314
msgid "OAuth2/OpenID Providers" msgid "OAuth2/OpenID Providers"
msgstr "" msgstr ""
#: authentik/providers/oauth2/models.py:319 #: authentik/providers/oauth2/models.py:322
msgid "Scopes" msgid "Scopes"
msgstr "" msgstr ""
#: authentik/providers/oauth2/models.py:338 #: authentik/providers/oauth2/models.py:341
msgid "Code" msgid "Code"
msgstr "" msgstr ""
#: authentik/providers/oauth2/models.py:339 #: authentik/providers/oauth2/models.py:342
msgid "Nonce" msgid "Nonce"
msgstr "" msgstr ""
#: authentik/providers/oauth2/models.py:340 #: authentik/providers/oauth2/models.py:343
msgid "Is Authentication?" msgid "Is Authentication?"
msgstr "" msgstr ""
#: authentik/providers/oauth2/models.py:341 #: authentik/providers/oauth2/models.py:344
msgid "Code Challenge" msgid "Code Challenge"
msgstr "" msgstr ""
#: authentik/providers/oauth2/models.py:343 #: authentik/providers/oauth2/models.py:346
msgid "Code Challenge Method" msgid "Code Challenge Method"
msgstr "" msgstr ""
#: authentik/providers/oauth2/models.py:357 #: authentik/providers/oauth2/models.py:360
msgid "Authorization Code" msgid "Authorization Code"
msgstr "" msgstr ""
#: authentik/providers/oauth2/models.py:358 #: authentik/providers/oauth2/models.py:361
msgid "Authorization Codes" msgid "Authorization Codes"
msgstr "" msgstr ""
#: authentik/providers/oauth2/models.py:401 #: authentik/providers/oauth2/models.py:404
msgid "Access Token" msgid "Access Token"
msgstr "" msgstr ""
#: authentik/providers/oauth2/models.py:402 #: authentik/providers/oauth2/models.py:405
msgid "Refresh Token" msgid "Refresh Token"
msgstr "" msgstr ""
#: authentik/providers/oauth2/models.py:403 #: authentik/providers/oauth2/models.py:406
msgid "ID Token" msgid "ID Token"
msgstr "" msgstr ""
#: authentik/providers/oauth2/models.py:406 #: authentik/providers/oauth2/models.py:409
msgid "OAuth2 Token" msgid "OAuth2 Token"
msgstr "" msgstr ""
#: authentik/providers/oauth2/models.py:407 #: authentik/providers/oauth2/models.py:410
msgid "OAuth2 Tokens" msgid "OAuth2 Tokens"
msgstr "" msgstr ""
@ -1385,7 +1395,7 @@ msgstr ""
msgid "TOTP Authenticator Setup Stages" msgid "TOTP Authenticator Setup Stages"
msgstr "" msgstr ""
#: authentik/stages/authenticator_validate/challenge.py:110 #: authentik/stages/authenticator_validate/challenge.py:115
msgid "Invalid Token" msgid "Invalid Token"
msgstr "" msgstr ""
@ -1687,51 +1697,57 @@ msgstr ""
msgid "Invalid password" msgid "Invalid password"
msgstr "" msgstr ""
#: authentik/stages/prompt/models.py:38 #: authentik/stages/prompt/models.py:40
msgid "Text: Simple Text input" msgid "Text: Simple Text input"
msgstr "" msgstr ""
#: authentik/stages/prompt/models.py:41 #: authentik/stages/prompt/models.py:43
msgid "Text (read-only): Simple Text input, but cannot be edited." msgid "Text (read-only): Simple Text input, but cannot be edited."
msgstr "" msgstr ""
#: authentik/stages/prompt/models.py:48 #: authentik/stages/prompt/models.py:50
msgid "Email: Text field with Email type." msgid "Email: Text field with Email type."
msgstr "" msgstr ""
#: authentik/stages/prompt/models.py:64 #: authentik/stages/prompt/models.py:69
msgid ""
"File: File upload for arbitrary files. File content will be available in "
"flow context as data-URI"
msgstr ""
#: authentik/stages/prompt/models.py:74
msgid "Separator: Static Separator Line" msgid "Separator: Static Separator Line"
msgstr "" msgstr ""
#: authentik/stages/prompt/models.py:65 #: authentik/stages/prompt/models.py:75
msgid "Hidden: Hidden field, can be used to insert data into form." msgid "Hidden: Hidden field, can be used to insert data into form."
msgstr "" msgstr ""
#: authentik/stages/prompt/models.py:66 #: authentik/stages/prompt/models.py:76
msgid "Static: Static value, displayed as-is." msgid "Static: Static value, displayed as-is."
msgstr "" msgstr ""
#: authentik/stages/prompt/models.py:68 #: authentik/stages/prompt/models.py:78
msgid "authentik: Selection of locales authentik supports" msgid "authentik: Selection of locales authentik supports"
msgstr "" msgstr ""
#: authentik/stages/prompt/models.py:77 #: authentik/stages/prompt/models.py:101
msgid "Name of the form field, also used to store the value" msgid "Name of the form field, also used to store the value"
msgstr "" msgstr ""
#: authentik/stages/prompt/models.py:170 #: authentik/stages/prompt/models.py:198
msgid "Prompt" msgid "Prompt"
msgstr "" msgstr ""
#: authentik/stages/prompt/models.py:171 #: authentik/stages/prompt/models.py:199
msgid "Prompts" msgid "Prompts"
msgstr "" msgstr ""
#: authentik/stages/prompt/models.py:199 #: authentik/stages/prompt/models.py:227
msgid "Prompt Stage" msgid "Prompt Stage"
msgstr "" msgstr ""
#: authentik/stages/prompt/models.py:200 #: authentik/stages/prompt/models.py:228
msgid "Prompt Stages" msgid "Prompt Stages"
msgstr "" msgstr ""
@ -1807,10 +1823,10 @@ msgid ""
"and `ba.b`" "and `ba.b`"
msgstr "" msgstr ""
#: authentik/tenants/models.py:75 #: authentik/tenants/models.py:80
msgid "Tenant" msgid "Tenant"
msgstr "" msgstr ""
#: authentik/tenants/models.py:76 #: authentik/tenants/models.py:81
msgid "Tenants" msgid "Tenants"
msgstr "" msgstr ""

View file

@ -55,6 +55,7 @@ export class AuthenticatorValidateStage
set selectedDeviceChallenge(value: DeviceChallenge | undefined) { set selectedDeviceChallenge(value: DeviceChallenge | undefined) {
this._selectedDeviceChallenge = value; this._selectedDeviceChallenge = value;
if (!value) return; if (!value) return;
if (value === this._selectedDeviceChallenge) return;
// We don't use this.submit here, as we don't want to advance the flow. // We don't use this.submit here, as we don't want to advance the flow.
// We just want to notify the backend which challenge has been selected. // We just want to notify the backend which challenge has been selected.
new FlowsApi(DEFAULT_CONFIG).flowsExecutorSolve({ new FlowsApi(DEFAULT_CONFIG).flowsExecutorSolve({

View file

@ -50,6 +50,7 @@ export class AuthenticatorValidateStageWebDuo extends BaseStage<
if (!this.challenge) { if (!this.challenge) {
return html`<ak-empty-state ?loading="${true}" header=${t`Loading`}> </ak-empty-state>`; return html`<ak-empty-state ?loading="${true}" header=${t`Loading`}> </ak-empty-state>`;
} }
const errors = this.challenge.responseErrors?.duo || [];
return html`<div class="pf-c-login__main-body"> return html`<div class="pf-c-login__main-body">
<form <form
class="pf-c-form" class="pf-c-form"
@ -69,6 +70,10 @@ export class AuthenticatorValidateStageWebDuo extends BaseStage<
</div> </div>
</ak-form-static> </ak-form-static>
${errors.map((err) => {
return html`<p>${err.string}</p>`;
})}
<div class="pf-c-form__group pf-m-action"> <div class="pf-c-form__group pf-m-action">
<button type="submit" class="pf-c-button pf-m-primary pf-m-block"> <button type="submit" class="pf-c-button pf-m-primary pf-m-block">
${t`Continue`} ${t`Continue`}

View file

@ -2259,6 +2259,10 @@ msgstr "Felder"
msgid "Fields a user can identify themselves with. If no fields are selected, the user will only be able to use sources." msgid "Fields a user can identify themselves with. If no fields are selected, the user will only be able to use sources."
msgstr "Felder, mit denen sich ein Benutzer identifizieren kann. Wenn keine Felder ausgewählt sind, kann der Benutzer nur Quellen verwenden." msgstr "Felder, mit denen sich ein Benutzer identifizieren kann. Wenn keine Felder ausgewählt sind, kann der Benutzer nur Quellen verwenden."
#: src/pages/stages/prompt/PromptForm.ts
msgid "File"
msgstr ""
#: src/elements/wizard/Wizard.ts #: src/elements/wizard/Wizard.ts
msgid "Finish" msgid "Finish"
msgstr "" msgstr ""

View file

@ -2295,6 +2295,10 @@ msgstr "Fields"
msgid "Fields a user can identify themselves with. If no fields are selected, the user will only be able to use sources." msgid "Fields a user can identify themselves with. If no fields are selected, the user will only be able to use sources."
msgstr "Fields a user can identify themselves with. If no fields are selected, the user will only be able to use sources." msgstr "Fields a user can identify themselves with. If no fields are selected, the user will only be able to use sources."
#: src/pages/stages/prompt/PromptForm.ts
msgid "File"
msgstr "File"
#: src/elements/wizard/Wizard.ts #: src/elements/wizard/Wizard.ts
msgid "Finish" msgid "Finish"
msgstr "Finish" msgstr "Finish"

View file

@ -2250,6 +2250,10 @@ msgstr "Campos"
msgid "Fields a user can identify themselves with. If no fields are selected, the user will only be able to use sources." msgid "Fields a user can identify themselves with. If no fields are selected, the user will only be able to use sources."
msgstr "Campos con los que un usuario puede identificarse. Si no se seleccionan campos, el usuario solo podrá usar fuentes." msgstr "Campos con los que un usuario puede identificarse. Si no se seleccionan campos, el usuario solo podrá usar fuentes."
#: src/pages/stages/prompt/PromptForm.ts
msgid "File"
msgstr ""
#: src/elements/wizard/Wizard.ts #: src/elements/wizard/Wizard.ts
msgid "Finish" msgid "Finish"
msgstr "" msgstr ""

View file

@ -2275,6 +2275,10 @@ msgstr "Champs"
msgid "Fields a user can identify themselves with. If no fields are selected, the user will only be able to use sources." msgid "Fields a user can identify themselves with. If no fields are selected, the user will only be able to use sources."
msgstr "Champs avec lesquels un utilisateur peut s'identifier. Si aucun champ n'est sélectionné, l'utilisateur ne pourra utiliser que des sources." msgstr "Champs avec lesquels un utilisateur peut s'identifier. Si aucun champ n'est sélectionné, l'utilisateur ne pourra utiliser que des sources."
#: src/pages/stages/prompt/PromptForm.ts
msgid "File"
msgstr ""
#: src/elements/wizard/Wizard.ts #: src/elements/wizard/Wizard.ts
msgid "Finish" msgid "Finish"
msgstr "" msgstr ""

View file

@ -2247,6 +2247,10 @@ msgstr "Pola"
msgid "Fields a user can identify themselves with. If no fields are selected, the user will only be able to use sources." msgid "Fields a user can identify themselves with. If no fields are selected, the user will only be able to use sources."
msgstr "Pola, z którymi użytkownik może się identyfikować. Jeśli żadne pola nie zostaną wybrane, użytkownik będzie mógł korzystać tylko ze źródeł." msgstr "Pola, z którymi użytkownik może się identyfikować. Jeśli żadne pola nie zostaną wybrane, użytkownik będzie mógł korzystać tylko ze źródeł."
#: src/pages/stages/prompt/PromptForm.ts
msgid "File"
msgstr ""
#: src/elements/wizard/Wizard.ts #: src/elements/wizard/Wizard.ts
msgid "Finish" msgid "Finish"
msgstr "" msgstr ""

View file

@ -2281,6 +2281,10 @@ msgstr ""
msgid "Fields a user can identify themselves with. If no fields are selected, the user will only be able to use sources." msgid "Fields a user can identify themselves with. If no fields are selected, the user will only be able to use sources."
msgstr "" msgstr ""
#: src/pages/stages/prompt/PromptForm.ts
msgid "File"
msgstr ""
#: src/elements/wizard/Wizard.ts #: src/elements/wizard/Wizard.ts
msgid "Finish" msgid "Finish"
msgstr "" msgstr ""

View file

@ -2250,6 +2250,10 @@ msgstr "Alanlar"
msgid "Fields a user can identify themselves with. If no fields are selected, the user will only be able to use sources." msgid "Fields a user can identify themselves with. If no fields are selected, the user will only be able to use sources."
msgstr "Kullanıcının kendilerini tanımlayabileceği alanlar. Herhangi bir alan seçilmezse, kullanıcı yalnızca kaynakları kullanabilir." msgstr "Kullanıcının kendilerini tanımlayabileceği alanlar. Herhangi bir alan seçilmezse, kullanıcı yalnızca kaynakları kullanabilir."
#: src/pages/stages/prompt/PromptForm.ts
msgid "File"
msgstr ""
#: src/elements/wizard/Wizard.ts #: src/elements/wizard/Wizard.ts
msgid "Finish" msgid "Finish"
msgstr "" msgstr ""

View file

@ -2238,6 +2238,10 @@ msgstr "字段"
msgid "Fields a user can identify themselves with. If no fields are selected, the user will only be able to use sources." msgid "Fields a user can identify themselves with. If no fields are selected, the user will only be able to use sources."
msgstr "用户可以用来标识自己的字段。如果未选择任何字段,则用户将只能使用源。" msgstr "用户可以用来标识自己的字段。如果未选择任何字段,则用户将只能使用源。"
#: src/pages/stages/prompt/PromptForm.ts
msgid "File"
msgstr ""
#: src/elements/wizard/Wizard.ts #: src/elements/wizard/Wizard.ts
msgid "Finish" msgid "Finish"
msgstr "完成" msgstr "完成"

View file

@ -2241,6 +2241,10 @@ msgstr "字段"
msgid "Fields a user can identify themselves with. If no fields are selected, the user will only be able to use sources." msgid "Fields a user can identify themselves with. If no fields are selected, the user will only be able to use sources."
msgstr "用户可以用来标识自己的字段。如果未选择任何字段,则用户将只能使用源。" msgstr "用户可以用来标识自己的字段。如果未选择任何字段,则用户将只能使用源。"
#: src/pages/stages/prompt/PromptForm.ts
msgid "File"
msgstr ""
#: src/elements/wizard/Wizard.ts #: src/elements/wizard/Wizard.ts
msgid "Finish" msgid "Finish"
msgstr "完成" msgstr "完成"

View file

@ -2241,6 +2241,10 @@ msgstr "字段"
msgid "Fields a user can identify themselves with. If no fields are selected, the user will only be able to use sources." msgid "Fields a user can identify themselves with. If no fields are selected, the user will only be able to use sources."
msgstr "用户可以用来标识自己的字段。如果未选择任何字段,则用户将只能使用源。" msgstr "用户可以用来标识自己的字段。如果未选择任何字段,则用户将只能使用源。"
#: src/pages/stages/prompt/PromptForm.ts
msgid "File"
msgstr ""
#: src/elements/wizard/Wizard.ts #: src/elements/wizard/Wizard.ts
msgid "Finish" msgid "Finish"
msgstr "完成" msgstr "完成"