stages/authentiactor_validate: improve error handling for duo
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
parent
1f90359310
commit
0248755cda
|
@ -1,10 +1,12 @@
|
|||
"""Validation stage challenge checking"""
|
||||
from json import dumps, loads
|
||||
from typing import Optional
|
||||
from urllib.parse import urlencode
|
||||
|
||||
from django.http import HttpRequest
|
||||
from django.http.response import Http404
|
||||
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_otp import match_token
|
||||
from django_otp.models import Device
|
||||
|
@ -17,9 +19,11 @@ from webauthn.helpers.exceptions import InvalidAuthenticationResponse
|
|||
from webauthn.helpers.structs import AuthenticationCredential
|
||||
|
||||
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.events.models import Event, EventAction
|
||||
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.stages.authenticator_duo.models import AuthenticatorDuoStage, DuoDevice
|
||||
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.stage import SESSION_KEY_WEBAUTHN_CHALLENGE
|
||||
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()
|
||||
|
||||
|
@ -155,23 +160,49 @@ def validate_challenge_duo(device_pk: int, stage_view: StageView, user: User) ->
|
|||
LOGGER.warning("device mismatch")
|
||||
raise Http404
|
||||
stage: AuthenticatorDuoStage = device.stage
|
||||
response = stage.client.auth(
|
||||
"auto",
|
||||
user_id=device.duo_user_id,
|
||||
ipaddr=get_client_ip(stage_view.request),
|
||||
type="authentik Login request",
|
||||
display_username=user.username,
|
||||
device="auto",
|
||||
)
|
||||
# {'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,
|
||||
|
||||
# Get additional context for push
|
||||
pushinfo = {
|
||||
__("Domain"): stage_view.request.get_host(),
|
||||
}
|
||||
if PLAN_CONTEXT_CONSENT_TITLE in stage_view.executor.plan.context:
|
||||
pushinfo[__("Title")] = stage_view.executor.plan.context[PLAN_CONTEXT_CONSENT_TITLE]
|
||||
if SESSION_KEY_APPLICATION_PRE in stage_view.request.session:
|
||||
pushinfo[__("Application")] = stage_view.request.session.get(
|
||||
SESSION_KEY_APPLICATION_PRE, Application()
|
||||
).name
|
||||
|
||||
try:
|
||||
response = stage.client.auth(
|
||||
"auto",
|
||||
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")
|
||||
device.save()
|
||||
return device
|
||||
|
|
|
@ -8,7 +8,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\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"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
|
@ -39,11 +39,11 @@ msgstr ""
|
|||
msgid "Create a SAML Provider by importing its Metadata."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/api/users.py:90
|
||||
#: authentik/core/api/users.py:93
|
||||
msgid "No leading or trailing slashes allowed."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/api/users.py:93
|
||||
#: authentik/core/api/users.py:96
|
||||
msgid "No empty segments in user path allowed."
|
||||
msgstr ""
|
||||
|
||||
|
@ -59,105 +59,105 @@ msgstr ""
|
|||
msgid "User's display name."
|
||||
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"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:238
|
||||
#: authentik/core/models.py:240
|
||||
msgid "Users"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:249
|
||||
#: authentik/core/models.py:251
|
||||
msgid "Flow used when authorizing this provider."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:282
|
||||
#: authentik/core/models.py:284
|
||||
msgid "Application's display Name."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:283
|
||||
#: authentik/core/models.py:285
|
||||
msgid "Internal application name, used in URLs."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:295
|
||||
#: authentik/core/models.py:297
|
||||
msgid "Open launch URL in a new browser tab or window."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:354
|
||||
#: authentik/core/models.py:356
|
||||
msgid "Application"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:355
|
||||
#: authentik/core/models.py:357
|
||||
msgid "Applications"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:361
|
||||
#: authentik/core/models.py:363
|
||||
msgid "Use the source-specific identifier"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:369
|
||||
#: authentik/core/models.py:371
|
||||
msgid ""
|
||||
"Use the user's email address, but deny enrollment when the email address "
|
||||
"already exists."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:378
|
||||
#: authentik/core/models.py:380
|
||||
msgid ""
|
||||
"Use the user's username, but deny enrollment when the username already "
|
||||
"exists."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:385
|
||||
#: authentik/core/models.py:387
|
||||
msgid "Source's display Name."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:386
|
||||
#: authentik/core/models.py:388
|
||||
msgid "Internal source name, used in URLs."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:399
|
||||
#: authentik/core/models.py:401
|
||||
msgid "Flow to use when authenticating existing users."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:408
|
||||
#: authentik/core/models.py:410
|
||||
msgid "Flow to use when enrolling new users."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:557
|
||||
#: authentik/core/models.py:560
|
||||
msgid "Token"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:558
|
||||
#: authentik/core/models.py:561
|
||||
msgid "Tokens"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:601
|
||||
#: authentik/core/models.py:604
|
||||
msgid "Property Mapping"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:602
|
||||
#: authentik/core/models.py:605
|
||||
msgid "Property Mappings"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:638
|
||||
#: authentik/core/models.py:641
|
||||
msgid "Authenticated Session"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:639
|
||||
#: authentik/core/models.py:642
|
||||
msgid "Authenticated Sessions"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/sources/flow_manager.py:177
|
||||
#: authentik/core/sources/flow_manager.py:176
|
||||
msgid "source"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/sources/flow_manager.py:245
|
||||
#: authentik/core/sources/flow_manager.py:283
|
||||
#: authentik/core/sources/flow_manager.py:243
|
||||
#: authentik/core/sources/flow_manager.py:281
|
||||
#, python-format
|
||||
msgid "Successfully authenticated with %(source)s!"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/sources/flow_manager.py:264
|
||||
#: authentik/core/sources/flow_manager.py:262
|
||||
#, python-format
|
||||
msgid "Successfully linked %(source)s!"
|
||||
msgstr ""
|
||||
|
@ -168,8 +168,8 @@ msgstr ""
|
|||
|
||||
#: authentik/core/templates/if/admin.html:18
|
||||
#: authentik/core/templates/if/admin.html:24
|
||||
#: authentik/core/templates/if/flow.html:35
|
||||
#: authentik/core/templates/if/flow.html:41
|
||||
#: authentik/core/templates/if/flow.html:37
|
||||
#: authentik/core/templates/if/flow.html:43
|
||||
#: authentik/core/templates/if/user.html:18
|
||||
#: authentik/core/templates/if/user.html:24
|
||||
msgid "Loading..."
|
||||
|
@ -355,6 +355,10 @@ msgstr ""
|
|||
msgid "Flow not applicable to current user/request: %(messages)s"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/flows/exceptions.py:17
|
||||
msgid "Flow does not apply to current user (denied by policy)."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/flows/models.py:117
|
||||
msgid "Visible in the URL."
|
||||
msgstr ""
|
||||
|
@ -742,98 +746,104 @@ msgstr ""
|
|||
msgid "Client Type"
|
||||
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"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/providers/oauth2/models.py:157
|
||||
#: authentik/providers/oauth2/models.py:160
|
||||
msgid "Client Secret"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/providers/oauth2/models.py:163
|
||||
#: authentik/providers/oauth2/models.py:166
|
||||
msgid "Redirect URIs"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/providers/oauth2/models.py:164
|
||||
#: authentik/providers/oauth2/models.py:167
|
||||
msgid "Enter each URI on a new line."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/providers/oauth2/models.py:169
|
||||
#: authentik/providers/oauth2/models.py:172
|
||||
msgid "Include claims in id_token"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/providers/oauth2/models.py:217
|
||||
#: authentik/providers/oauth2/models.py:220
|
||||
msgid "Signing Key"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/providers/oauth2/models.py:221
|
||||
#: authentik/providers/oauth2/models.py:224
|
||||
msgid ""
|
||||
"Key used to sign the tokens. Only required when JWT Algorithm is set to "
|
||||
"RS256."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/providers/oauth2/models.py:228
|
||||
#: authentik/providers/oauth2/models.py:231
|
||||
msgid ""
|
||||
"Any JWT signed by the JWK of the selected source can be used to authenticate."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/providers/oauth2/models.py:310
|
||||
#: authentik/providers/oauth2/models.py:313
|
||||
msgid "OAuth2/OpenID Provider"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/providers/oauth2/models.py:311
|
||||
#: authentik/providers/oauth2/models.py:314
|
||||
msgid "OAuth2/OpenID Providers"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/providers/oauth2/models.py:319
|
||||
#: authentik/providers/oauth2/models.py:322
|
||||
msgid "Scopes"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/providers/oauth2/models.py:338
|
||||
#: authentik/providers/oauth2/models.py:341
|
||||
msgid "Code"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/providers/oauth2/models.py:339
|
||||
#: authentik/providers/oauth2/models.py:342
|
||||
msgid "Nonce"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/providers/oauth2/models.py:340
|
||||
#: authentik/providers/oauth2/models.py:343
|
||||
msgid "Is Authentication?"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/providers/oauth2/models.py:341
|
||||
#: authentik/providers/oauth2/models.py:344
|
||||
msgid "Code Challenge"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/providers/oauth2/models.py:343
|
||||
#: authentik/providers/oauth2/models.py:346
|
||||
msgid "Code Challenge Method"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/providers/oauth2/models.py:357
|
||||
#: authentik/providers/oauth2/models.py:360
|
||||
msgid "Authorization Code"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/providers/oauth2/models.py:358
|
||||
#: authentik/providers/oauth2/models.py:361
|
||||
msgid "Authorization Codes"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/providers/oauth2/models.py:401
|
||||
#: authentik/providers/oauth2/models.py:404
|
||||
msgid "Access Token"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/providers/oauth2/models.py:402
|
||||
#: authentik/providers/oauth2/models.py:405
|
||||
msgid "Refresh Token"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/providers/oauth2/models.py:403
|
||||
#: authentik/providers/oauth2/models.py:406
|
||||
msgid "ID Token"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/providers/oauth2/models.py:406
|
||||
#: authentik/providers/oauth2/models.py:409
|
||||
msgid "OAuth2 Token"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/providers/oauth2/models.py:407
|
||||
#: authentik/providers/oauth2/models.py:410
|
||||
msgid "OAuth2 Tokens"
|
||||
msgstr ""
|
||||
|
||||
|
@ -1385,7 +1395,7 @@ msgstr ""
|
|||
msgid "TOTP Authenticator Setup Stages"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/stages/authenticator_validate/challenge.py:110
|
||||
#: authentik/stages/authenticator_validate/challenge.py:115
|
||||
msgid "Invalid Token"
|
||||
msgstr ""
|
||||
|
||||
|
@ -1687,51 +1697,57 @@ msgstr ""
|
|||
msgid "Invalid password"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/stages/prompt/models.py:38
|
||||
#: authentik/stages/prompt/models.py:40
|
||||
msgid "Text: Simple Text input"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/stages/prompt/models.py:41
|
||||
#: authentik/stages/prompt/models.py:43
|
||||
msgid "Text (read-only): Simple Text input, but cannot be edited."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/stages/prompt/models.py:48
|
||||
#: authentik/stages/prompt/models.py:50
|
||||
msgid "Email: Text field with Email type."
|
||||
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"
|
||||
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."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/stages/prompt/models.py:66
|
||||
#: authentik/stages/prompt/models.py:76
|
||||
msgid "Static: Static value, displayed as-is."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/stages/prompt/models.py:68
|
||||
#: authentik/stages/prompt/models.py:78
|
||||
msgid "authentik: Selection of locales authentik supports"
|
||||
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"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/stages/prompt/models.py:170
|
||||
#: authentik/stages/prompt/models.py:198
|
||||
msgid "Prompt"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/stages/prompt/models.py:171
|
||||
#: authentik/stages/prompt/models.py:199
|
||||
msgid "Prompts"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/stages/prompt/models.py:199
|
||||
#: authentik/stages/prompt/models.py:227
|
||||
msgid "Prompt Stage"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/stages/prompt/models.py:200
|
||||
#: authentik/stages/prompt/models.py:228
|
||||
msgid "Prompt Stages"
|
||||
msgstr ""
|
||||
|
||||
|
@ -1807,10 +1823,10 @@ msgid ""
|
|||
"and `ba.b`"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/tenants/models.py:75
|
||||
#: authentik/tenants/models.py:80
|
||||
msgid "Tenant"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/tenants/models.py:76
|
||||
#: authentik/tenants/models.py:81
|
||||
msgid "Tenants"
|
||||
msgstr ""
|
||||
|
|
|
@ -55,6 +55,7 @@ export class AuthenticatorValidateStage
|
|||
set selectedDeviceChallenge(value: DeviceChallenge | undefined) {
|
||||
this._selectedDeviceChallenge = value;
|
||||
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 just want to notify the backend which challenge has been selected.
|
||||
new FlowsApi(DEFAULT_CONFIG).flowsExecutorSolve({
|
||||
|
|
|
@ -50,6 +50,7 @@ export class AuthenticatorValidateStageWebDuo extends BaseStage<
|
|||
if (!this.challenge) {
|
||||
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">
|
||||
<form
|
||||
class="pf-c-form"
|
||||
|
@ -69,6 +70,10 @@ export class AuthenticatorValidateStageWebDuo extends BaseStage<
|
|||
</div>
|
||||
</ak-form-static>
|
||||
|
||||
${errors.map((err) => {
|
||||
return html`<p>${err.string}</p>`;
|
||||
})}
|
||||
|
||||
<div class="pf-c-form__group pf-m-action">
|
||||
<button type="submit" class="pf-c-button pf-m-primary pf-m-block">
|
||||
${t`Continue`}
|
||||
|
|
|
@ -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."
|
||||
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
|
||||
msgid "Finish"
|
||||
msgstr ""
|
||||
|
|
|
@ -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."
|
||||
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
|
||||
msgid "Finish"
|
||||
msgstr "Finish"
|
||||
|
|
|
@ -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."
|
||||
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
|
||||
msgid "Finish"
|
||||
msgstr ""
|
||||
|
|
|
@ -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."
|
||||
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
|
||||
msgid "Finish"
|
||||
msgstr ""
|
||||
|
|
|
@ -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."
|
||||
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
|
||||
msgid "Finish"
|
||||
msgstr ""
|
||||
|
|
|
@ -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."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/stages/prompt/PromptForm.ts
|
||||
msgid "File"
|
||||
msgstr ""
|
||||
|
||||
#: src/elements/wizard/Wizard.ts
|
||||
msgid "Finish"
|
||||
msgstr ""
|
||||
|
|
|
@ -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."
|
||||
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
|
||||
msgid "Finish"
|
||||
msgstr ""
|
||||
|
|
|
@ -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."
|
||||
msgstr "用户可以用来标识自己的字段。如果未选择任何字段,则用户将只能使用源。"
|
||||
|
||||
#: src/pages/stages/prompt/PromptForm.ts
|
||||
msgid "File"
|
||||
msgstr ""
|
||||
|
||||
#: src/elements/wizard/Wizard.ts
|
||||
msgid "Finish"
|
||||
msgstr "完成"
|
||||
|
|
|
@ -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."
|
||||
msgstr "用户可以用来标识自己的字段。如果未选择任何字段,则用户将只能使用源。"
|
||||
|
||||
#: src/pages/stages/prompt/PromptForm.ts
|
||||
msgid "File"
|
||||
msgstr ""
|
||||
|
||||
#: src/elements/wizard/Wizard.ts
|
||||
msgid "Finish"
|
||||
msgstr "完成"
|
||||
|
|
|
@ -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."
|
||||
msgstr "用户可以用来标识自己的字段。如果未选择任何字段,则用户将只能使用源。"
|
||||
|
||||
#: src/pages/stages/prompt/PromptForm.ts
|
||||
msgid "File"
|
||||
msgstr ""
|
||||
|
||||
#: src/elements/wizard/Wizard.ts
|
||||
msgid "Finish"
|
||||
msgstr "完成"
|
||||
|
|
Reference in a new issue