Account: Refactor access token API and request forms #808 #4114

Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
Michael Mayer
2024-04-05 14:46:11 +02:00
parent 8208f80be5
commit fdc2062d33
92 changed files with 1659 additions and 1085 deletions

View File

@@ -1,8 +1,8 @@
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: ci@photoprism.app\n"
"POT-Creation-Date: 2024-03-21 13:35+0000\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-03-22 14:48+0000\n"
"PO-Revision-Date: 2024-04-03 10:23+0000\n"
"Last-Translator: Admin <hello@photoprism.app>\n"
"Language-Team: German <https://translate.photoprism.app/projects/photoprism/"

View File

@@ -379,10 +379,10 @@ export default class Session {
return LoginPage === window.location.href.substring(window.location.href.lastIndexOf("/") + 1);
}
login(username, password, passcode, token) {
login(username, password, code, token) {
this.reset();
return Api.post("session", { username, password, passcode, token }).then((resp) => {
return Api.post("session", { username, password, code, token }).then((resp) => {
const reload = this.config.getLanguage() !== resp.data?.config?.settings?.ui?.language;
this.setResp(resp);
this.onLogin();

View File

@@ -70,7 +70,7 @@
<v-card-text class="py-0 px-2">
<v-layout wrap align-top>
<v-flex xs12 class="pa-2 body-1">
<translate>Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated passcode for verification:</translate>
<translate>Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated verification code:</translate>
</v-flex>
<v-flex xs12 class="pa-2">
<img :src="key.QRCode" class="width-100" alt="QR Code" />
@@ -81,9 +81,9 @@
</v-flex>
<v-flex xs12 class="pa-2">
<v-text-field
v-model="passcode"
v-model="code"
:disabled="busy"
name="passcode"
name="one-time-code"
type="text"
:label="$gettext('Verification Code')"
mask="### ###"
@@ -97,7 +97,7 @@
autocapitalize="none"
autocomplete="one-time-code"
browser-autocomplete="one-time-code"
class="input-passcode"
class="input-code"
color="secondary-dark"
prepend-inner-icon="verified_user"
@keyup.enter.native="onConfirm"
@@ -110,7 +110,7 @@
<v-btn depressed color="secondary-light" class="action-cancel ml-0" @click.stop="close">
<translate>Cancel</translate>
</v-btn>
<v-btn depressed color="primary-button" class="action-confirm white--text compact mr-0" :disabled="passcode.length !== 6" @click.stop="onConfirm">
<v-btn depressed color="primary-button" class="action-confirm white--text compact mr-0" :disabled="code.length !== 6" @click.stop="onConfirm">
<translate>Confirm</translate>
</v-btn>
</v-flex>
@@ -122,7 +122,7 @@
<v-card-text class="py-0 px-2">
<v-layout wrap align-top>
<v-flex xs12 class="pa-2 body-2">
<translate>Use the following recovery code to access your account when you are unable to generate a valid passcode with your authenticator app or device:</translate>
<translate>Use the following recovery code to access your account when you are unable to generate a verification code with your authenticator app:</translate>
</v-flex>
<v-flex xs12 class="pa-2">
<v-text-field
@@ -257,7 +257,7 @@ export default {
busy: false,
isDemo: this.$config.get("demo"),
isPublic: this.$config.get("public"),
passcode: "",
code: "",
password: "",
recoveryCodeCopied: false,
showPassword: false,
@@ -312,7 +312,7 @@ export default {
}
},
reset() {
this.passcode = "";
this.code = "";
this.password = "";
this.showPassword = false;
this.recoveryCodeCopied = false;
@@ -339,19 +339,19 @@ export default {
});
},
onConfirm() {
if (this.busy || this.passcode === "") {
if (this.busy || this.code === "") {
return;
}
this.busy = true;
this.model
.confirmPasscode(this.passcode)
.confirmPasscode(this.code)
.then((resp) => {
this.key = resp;
this.$notify.success(this.$gettext("Successfully verified"));
})
.finally(() => {
this.busy = false;
this.passcode = "";
this.code = "";
this.password = "";
this.showPassword = false;
this.recoveryCodeCopied = false;

View File

@@ -524,7 +524,7 @@ msgstr "Verander privaat vlag"
#: src/dialog/album/edit.vue:56 src/dialog/photo/edit/files.vue:147
#: src/dialog/service/edit.vue:99 src/dialog/share.vue:96
#: src/dialog/share.vue:115 src/page/albums.vue:563
#: src/page/settings/account.vue:129 src/page/settings/account.vue:147
#: src/page/settings/account.vue:128 src/page/settings/account.vue:146
#: src/page/settings/advanced.vue:53 src/page/settings/general.vue:100
#: src/page/settings/library.vue:52
msgid "Changes successfully saved"
@@ -712,7 +712,7 @@ msgid "Debug Logs"
msgstr "Ontfout logs"
#: src/options/admin.js:31 src/page/admin/sessions.vue:60
#: src/page/admin/users.vue:100 src/model/session.js:63 src/model/user.js:234
#: src/page/admin/users.vue:100 src/model/session.js:64 src/model/user.js:234
#: src/options/auth.js:20 src/options/auth.js:21 src/options/auth.js:38
#: src/options/auth.js:39 src/options/options.js:309 src/options/options.js:373
#: src/options/themes.js:492 src/page/places.vue:141
@@ -2183,7 +2183,7 @@ msgid "Scan"
msgstr "Skandeer"
#: src/dialog/account/passcode.vue:51 src/dialog/account/passcode.vue:4
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated passcode for verification:"
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated verification code:"
msgstr ""
#: src/component/navigation.vue:125
@@ -2256,7 +2256,7 @@ msgstr "Diens-URL"
msgid "Services"
msgstr "Dienste"
#: src/model/session.js:100 src/options/auth.js:40
#: src/model/session.js:101 src/options/auth.js:40
msgid "Session"
msgstr "Sessie"
@@ -2611,7 +2611,8 @@ msgid "Unknown"
msgstr "Onbekend"
#: src/page/settings/account.vue:40 src/page/settings/account.vue:48
#: src/component/navigation.vue:93
#: src/component/navigation.vue:93 src/page/settings/account.vue:39
#: src/page/settings/account.vue:47
msgid "Unregistered"
msgstr "Ongeregistreer"
@@ -2640,7 +2641,7 @@ msgstr "Dateer indeks op"
msgid "Updating moments"
msgstr "Dateer tans oomblikke op"
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:140
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:139
msgid "Updating picture…"
msgstr "Dateer tans prent op …"
@@ -2721,7 +2722,7 @@ msgid "Use Presets"
msgstr "Gebruik voorafinstellings"
#: src/dialog/account/passcode.vue:82 src/dialog/account/passcode.vue:4
msgid "Use the following recovery code to access your account when you are unable to generate a valid passcode with your authenticator app or device:"
msgid "Use the following recovery code to access your account when you are unable to generate a verification code with your authenticator app:"
msgstr ""
#: src/options/admin.js:10 src/page/admin/sessions.vue:59

View File

@@ -527,7 +527,7 @@ msgstr "تغيير العلم الخاص"
#: src/dialog/album/edit.vue:56 src/dialog/photo/edit/files.vue:147
#: src/dialog/service/edit.vue:99 src/dialog/share.vue:96
#: src/dialog/share.vue:115 src/page/albums.vue:563
#: src/page/settings/account.vue:129 src/page/settings/account.vue:147
#: src/page/settings/account.vue:128 src/page/settings/account.vue:146
#: src/page/settings/advanced.vue:53 src/page/settings/general.vue:100
#: src/page/settings/library.vue:52
msgid "Changes successfully saved"
@@ -715,7 +715,7 @@ msgid "Debug Logs"
msgstr "سجلات التصحيح"
#: src/options/admin.js:31 src/page/admin/sessions.vue:60
#: src/page/admin/users.vue:100 src/model/session.js:63 src/model/user.js:234
#: src/page/admin/users.vue:100 src/model/session.js:64 src/model/user.js:234
#: src/options/auth.js:20 src/options/auth.js:21 src/options/auth.js:38
#: src/options/auth.js:39 src/options/options.js:309 src/options/options.js:373
#: src/options/themes.js:492 src/page/places.vue:141
@@ -2186,7 +2186,7 @@ msgid "Scan"
msgstr "مسح"
#: src/dialog/account/passcode.vue:51 src/dialog/account/passcode.vue:4
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated passcode for verification:"
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated verification code:"
msgstr ""
#: src/component/navigation.vue:125
@@ -2259,7 +2259,7 @@ msgstr "URL الخدمة"
msgid "Services"
msgstr "خدمات"
#: src/model/session.js:100 src/options/auth.js:40
#: src/model/session.js:101 src/options/auth.js:40
msgid "Session"
msgstr "حصة"
@@ -2614,7 +2614,8 @@ msgid "Unknown"
msgstr "مجهول"
#: src/page/settings/account.vue:40 src/page/settings/account.vue:48
#: src/component/navigation.vue:93
#: src/component/navigation.vue:93 src/page/settings/account.vue:39
#: src/page/settings/account.vue:47
msgid "Unregistered"
msgstr "غير مسجل"
@@ -2643,7 +2644,7 @@ msgstr "تحديث الفهرس"
msgid "Updating moments"
msgstr "تحديث اللحظات"
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:140
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:139
msgid "Updating picture…"
msgstr "جارٍ تحديث الصورة ..."
@@ -2724,7 +2725,7 @@ msgid "Use Presets"
msgstr "استخدم الإعدادات المسبقة"
#: src/dialog/account/passcode.vue:82 src/dialog/account/passcode.vue:4
msgid "Use the following recovery code to access your account when you are unable to generate a valid passcode with your authenticator app or device:"
msgid "Use the following recovery code to access your account when you are unable to generate a verification code with your authenticator app:"
msgstr ""
#: src/options/admin.js:10 src/page/admin/sessions.vue:59

View File

@@ -524,7 +524,7 @@ msgstr "Змяніць прыватны тэг"
#: src/dialog/album/edit.vue:56 src/dialog/photo/edit/files.vue:147
#: src/dialog/service/edit.vue:99 src/dialog/share.vue:96
#: src/dialog/share.vue:115 src/page/albums.vue:563
#: src/page/settings/account.vue:129 src/page/settings/account.vue:147
#: src/page/settings/account.vue:128 src/page/settings/account.vue:146
#: src/page/settings/advanced.vue:53 src/page/settings/general.vue:100
#: src/page/settings/library.vue:52
msgid "Changes successfully saved"
@@ -712,7 +712,7 @@ msgid "Debug Logs"
msgstr "Журналы адладкі"
#: src/options/admin.js:31 src/page/admin/sessions.vue:60
#: src/page/admin/users.vue:100 src/model/session.js:63 src/model/user.js:234
#: src/page/admin/users.vue:100 src/model/session.js:64 src/model/user.js:234
#: src/options/auth.js:20 src/options/auth.js:21 src/options/auth.js:38
#: src/options/auth.js:39 src/options/options.js:309 src/options/options.js:373
#: src/options/themes.js:492 src/page/places.vue:141
@@ -2183,7 +2183,7 @@ msgid "Scan"
msgstr "сканаваць"
#: src/dialog/account/passcode.vue:51 src/dialog/account/passcode.vue:4
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated passcode for verification:"
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated verification code:"
msgstr ""
#: src/component/navigation.vue:125
@@ -2256,7 +2256,7 @@ msgstr "URL службы"
msgid "Services"
msgstr "Сэрвісы"
#: src/model/session.js:100 src/options/auth.js:40
#: src/model/session.js:101 src/options/auth.js:40
msgid "Session"
msgstr "сесія"
@@ -2611,7 +2611,8 @@ msgid "Unknown"
msgstr "Невядомы"
#: src/page/settings/account.vue:40 src/page/settings/account.vue:48
#: src/component/navigation.vue:93
#: src/component/navigation.vue:93 src/page/settings/account.vue:39
#: src/page/settings/account.vue:47
msgid "Unregistered"
msgstr "Незарэгістраваны"
@@ -2640,7 +2641,7 @@ msgstr "Абнаўленне індэкса"
msgid "Updating moments"
msgstr "Абнаўленне момантаў"
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:140
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:139
msgid "Updating picture…"
msgstr "Абнаўленне выявы…"
@@ -2721,7 +2722,7 @@ msgid "Use Presets"
msgstr "Выкарыстоўвайце налады"
#: src/dialog/account/passcode.vue:82 src/dialog/account/passcode.vue:4
msgid "Use the following recovery code to access your account when you are unable to generate a valid passcode with your authenticator app or device:"
msgid "Use the following recovery code to access your account when you are unable to generate a verification code with your authenticator app:"
msgstr ""
#: src/options/admin.js:10 src/page/admin/sessions.vue:59

View File

@@ -527,7 +527,7 @@ msgstr "Промяна на частния флаг"
#: src/dialog/album/edit.vue:56 src/dialog/photo/edit/files.vue:147
#: src/dialog/service/edit.vue:99 src/dialog/share.vue:96
#: src/dialog/share.vue:115 src/page/albums.vue:563
#: src/page/settings/account.vue:129 src/page/settings/account.vue:147
#: src/page/settings/account.vue:128 src/page/settings/account.vue:146
#: src/page/settings/advanced.vue:53 src/page/settings/general.vue:100
#: src/page/settings/library.vue:52
msgid "Changes successfully saved"
@@ -715,7 +715,7 @@ msgid "Debug Logs"
msgstr "Протоколи за отработване"
#: src/options/admin.js:31 src/page/admin/sessions.vue:60
#: src/page/admin/users.vue:100 src/model/session.js:63 src/model/user.js:234
#: src/page/admin/users.vue:100 src/model/session.js:64 src/model/user.js:234
#: src/options/auth.js:20 src/options/auth.js:21 src/options/auth.js:38
#: src/options/auth.js:39 src/options/options.js:309 src/options/options.js:373
#: src/options/themes.js:492 src/page/places.vue:141
@@ -2186,7 +2186,7 @@ msgid "Scan"
msgstr "Сканиране"
#: src/dialog/account/passcode.vue:51 src/dialog/account/passcode.vue:4
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated passcode for verification:"
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated verification code:"
msgstr ""
#: src/component/navigation.vue:125
@@ -2259,7 +2259,7 @@ msgstr "URL адрес на услугата"
msgid "Services"
msgstr "URL адрес на услугата"
#: src/model/session.js:100 src/options/auth.js:40
#: src/model/session.js:101 src/options/auth.js:40
msgid "Session"
msgstr "Сесия"
@@ -2614,7 +2614,8 @@ msgid "Unknown"
msgstr "Неизвестно"
#: src/page/settings/account.vue:40 src/page/settings/account.vue:48
#: src/component/navigation.vue:93
#: src/component/navigation.vue:93 src/page/settings/account.vue:39
#: src/page/settings/account.vue:47
msgid "Unregistered"
msgstr "Нерегистриран"
@@ -2643,7 +2644,7 @@ msgstr "Актуализиране на индекса"
msgid "Updating moments"
msgstr "Актуализиране на моменти"
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:140
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:139
msgid "Updating picture…"
msgstr "Актуализиране на визуализациите"
@@ -2724,7 +2725,7 @@ msgid "Use Presets"
msgstr "Използване на предварителни настройки"
#: src/dialog/account/passcode.vue:82 src/dialog/account/passcode.vue:4
msgid "Use the following recovery code to access your account when you are unable to generate a valid passcode with your authenticator app or device:"
msgid "Use the following recovery code to access your account when you are unable to generate a verification code with your authenticator app:"
msgstr ""
#: src/options/admin.js:10 src/page/admin/sessions.vue:59

View File

@@ -527,7 +527,7 @@ msgstr "Canvia la bandera privada"
#: src/dialog/album/edit.vue:56 src/dialog/photo/edit/files.vue:147
#: src/dialog/service/edit.vue:99 src/dialog/share.vue:96
#: src/dialog/share.vue:115 src/page/albums.vue:563
#: src/page/settings/account.vue:129 src/page/settings/account.vue:147
#: src/page/settings/account.vue:128 src/page/settings/account.vue:146
#: src/page/settings/advanced.vue:53 src/page/settings/general.vue:100
#: src/page/settings/library.vue:52
msgid "Changes successfully saved"
@@ -715,7 +715,7 @@ msgid "Debug Logs"
msgstr "Registres de depuració"
#: src/options/admin.js:31 src/page/admin/sessions.vue:60
#: src/page/admin/users.vue:100 src/model/session.js:63 src/model/user.js:234
#: src/page/admin/users.vue:100 src/model/session.js:64 src/model/user.js:234
#: src/options/auth.js:20 src/options/auth.js:21 src/options/auth.js:38
#: src/options/auth.js:39 src/options/options.js:309 src/options/options.js:373
#: src/options/themes.js:492 src/page/places.vue:141
@@ -2186,7 +2186,7 @@ msgid "Scan"
msgstr "Escaneig"
#: src/dialog/account/passcode.vue:51 src/dialog/account/passcode.vue:4
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated passcode for verification:"
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated verification code:"
msgstr ""
#: src/component/navigation.vue:125
@@ -2259,7 +2259,7 @@ msgstr "URL del servei"
msgid "Services"
msgstr "Serveis"
#: src/model/session.js:100 src/options/auth.js:40
#: src/model/session.js:101 src/options/auth.js:40
msgid "Session"
msgstr "Sessió"
@@ -2614,7 +2614,8 @@ msgid "Unknown"
msgstr "Desconegut"
#: src/page/settings/account.vue:40 src/page/settings/account.vue:48
#: src/component/navigation.vue:93
#: src/component/navigation.vue:93 src/page/settings/account.vue:39
#: src/page/settings/account.vue:47
msgid "Unregistered"
msgstr "No registrat"
@@ -2643,7 +2644,7 @@ msgstr "S'està actualitzant l'índex"
msgid "Updating moments"
msgstr "Moments d'actualització"
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:140
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:139
msgid "Updating picture…"
msgstr "S'està actualitzant la imatge..."
@@ -2724,7 +2725,7 @@ msgid "Use Presets"
msgstr "Utilitzeu presets"
#: src/dialog/account/passcode.vue:82 src/dialog/account/passcode.vue:4
msgid "Use the following recovery code to access your account when you are unable to generate a valid passcode with your authenticator app or device:"
msgid "Use the following recovery code to access your account when you are unable to generate a verification code with your authenticator app:"
msgstr ""
#: src/options/admin.js:10 src/page/admin/sessions.vue:59

View File

@@ -527,7 +527,7 @@ msgstr "Změnit soukromou vlaječku"
#: src/dialog/album/edit.vue:56 src/dialog/photo/edit/files.vue:147
#: src/dialog/service/edit.vue:99 src/dialog/share.vue:96
#: src/dialog/share.vue:115 src/page/albums.vue:563
#: src/page/settings/account.vue:129 src/page/settings/account.vue:147
#: src/page/settings/account.vue:128 src/page/settings/account.vue:146
#: src/page/settings/advanced.vue:53 src/page/settings/general.vue:100
#: src/page/settings/library.vue:52
msgid "Changes successfully saved"
@@ -715,7 +715,7 @@ msgid "Debug Logs"
msgstr "Protokoly ladění"
#: src/options/admin.js:31 src/page/admin/sessions.vue:60
#: src/page/admin/users.vue:100 src/model/session.js:63 src/model/user.js:234
#: src/page/admin/users.vue:100 src/model/session.js:64 src/model/user.js:234
#: src/options/auth.js:20 src/options/auth.js:21 src/options/auth.js:38
#: src/options/auth.js:39 src/options/options.js:309 src/options/options.js:373
#: src/options/themes.js:492 src/page/places.vue:141
@@ -2186,7 +2186,7 @@ msgid "Scan"
msgstr "Sken"
#: src/dialog/account/passcode.vue:51 src/dialog/account/passcode.vue:4
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated passcode for verification:"
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated verification code:"
msgstr ""
#: src/component/navigation.vue:125
@@ -2259,7 +2259,7 @@ msgstr "URL služby"
msgid "Services"
msgstr "Služby"
#: src/model/session.js:100 src/options/auth.js:40
#: src/model/session.js:101 src/options/auth.js:40
msgid "Session"
msgstr "Relace"
@@ -2614,7 +2614,8 @@ msgid "Unknown"
msgstr "Neznámé"
#: src/page/settings/account.vue:40 src/page/settings/account.vue:48
#: src/component/navigation.vue:93
#: src/component/navigation.vue:93 src/page/settings/account.vue:39
#: src/page/settings/account.vue:47
msgid "Unregistered"
msgstr "Neregistrovaný"
@@ -2643,7 +2644,7 @@ msgstr "Aktualizace indexace"
msgid "Updating moments"
msgstr "Aktualizace okamžiků"
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:140
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:139
msgid "Updating picture…"
msgstr "Aktualizace obrázku…"
@@ -2724,7 +2725,7 @@ msgid "Use Presets"
msgstr "Použít předvolby"
#: src/dialog/account/passcode.vue:82 src/dialog/account/passcode.vue:4
msgid "Use the following recovery code to access your account when you are unable to generate a valid passcode with your authenticator app or device:"
msgid "Use the following recovery code to access your account when you are unable to generate a verification code with your authenticator app:"
msgstr ""
#: src/options/admin.js:10 src/page/admin/sessions.vue:59

View File

@@ -527,7 +527,7 @@ msgstr "Ændre privat flag"
#: src/dialog/album/edit.vue:56 src/dialog/photo/edit/files.vue:147
#: src/dialog/service/edit.vue:99 src/dialog/share.vue:96
#: src/dialog/share.vue:115 src/page/albums.vue:563
#: src/page/settings/account.vue:129 src/page/settings/account.vue:147
#: src/page/settings/account.vue:128 src/page/settings/account.vue:146
#: src/page/settings/advanced.vue:53 src/page/settings/general.vue:100
#: src/page/settings/library.vue:52
msgid "Changes successfully saved"
@@ -715,7 +715,7 @@ msgid "Debug Logs"
msgstr "Fejlfindingslog"
#: src/options/admin.js:31 src/page/admin/sessions.vue:60
#: src/page/admin/users.vue:100 src/model/session.js:63 src/model/user.js:234
#: src/page/admin/users.vue:100 src/model/session.js:64 src/model/user.js:234
#: src/options/auth.js:20 src/options/auth.js:21 src/options/auth.js:38
#: src/options/auth.js:39 src/options/options.js:309 src/options/options.js:373
#: src/options/themes.js:492 src/page/places.vue:141
@@ -2186,7 +2186,7 @@ msgid "Scan"
msgstr "Scan"
#: src/dialog/account/passcode.vue:51 src/dialog/account/passcode.vue:4
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated passcode for verification:"
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated verification code:"
msgstr ""
#: src/component/navigation.vue:125
@@ -2259,7 +2259,7 @@ msgstr "Service-URL"
msgid "Services"
msgstr "Tjenester"
#: src/model/session.js:100 src/options/auth.js:40
#: src/model/session.js:101 src/options/auth.js:40
msgid "Session"
msgstr "Session"
@@ -2614,7 +2614,8 @@ msgid "Unknown"
msgstr "Ukendt"
#: src/page/settings/account.vue:40 src/page/settings/account.vue:48
#: src/component/navigation.vue:93
#: src/component/navigation.vue:93 src/page/settings/account.vue:39
#: src/page/settings/account.vue:47
msgid "Unregistered"
msgstr "Uregistreret"
@@ -2643,7 +2644,7 @@ msgstr "Opdaterer indeks"
msgid "Updating moments"
msgstr "Opdaterer øjeblikke"
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:140
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:139
msgid "Updating picture…"
msgstr "Opdatering af billede…"
@@ -2724,7 +2725,7 @@ msgid "Use Presets"
msgstr "Brug forudindstillinger"
#: src/dialog/account/passcode.vue:82 src/dialog/account/passcode.vue:4
msgid "Use the following recovery code to access your account when you are unable to generate a valid passcode with your authenticator app or device:"
msgid "Use the following recovery code to access your account when you are unable to generate a verification code with your authenticator app:"
msgstr ""
#: src/options/admin.js:10 src/page/admin/sessions.vue:59

View File

@@ -527,7 +527,7 @@ msgstr "Als privat markieren"
#: src/dialog/album/edit.vue:56 src/dialog/photo/edit/files.vue:147
#: src/dialog/service/edit.vue:99 src/dialog/share.vue:96
#: src/dialog/share.vue:115 src/page/albums.vue:563
#: src/page/settings/account.vue:129 src/page/settings/account.vue:147
#: src/page/settings/account.vue:128 src/page/settings/account.vue:146
#: src/page/settings/advanced.vue:53 src/page/settings/general.vue:100
#: src/page/settings/library.vue:52
msgid "Changes successfully saved"
@@ -715,7 +715,7 @@ msgid "Debug Logs"
msgstr "Debug Logs"
#: src/options/admin.js:31 src/page/admin/sessions.vue:60
#: src/page/admin/users.vue:100 src/model/session.js:63 src/model/user.js:234
#: src/page/admin/users.vue:100 src/model/session.js:64 src/model/user.js:234
#: src/options/auth.js:20 src/options/auth.js:21 src/options/auth.js:38
#: src/options/auth.js:39 src/options/options.js:309 src/options/options.js:373
#: src/options/themes.js:492 src/page/places.vue:141
@@ -2186,8 +2186,8 @@ msgid "Scan"
msgstr "Scan"
#: src/dialog/account/passcode.vue:51 src/dialog/account/passcode.vue:4
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated passcode for verification:"
msgstr "Scanne den QR-Code mit deiner Authenticator-App oder verwende den unten gezeigten Schlüssel für die Einrichtung und gib dann den generierten Verifizierungscode ein:"
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated verification code:"
msgstr ""
#: src/component/navigation.vue:125
msgid "Scans"
@@ -2259,7 +2259,7 @@ msgstr "Dienst-URL"
msgid "Services"
msgstr "Dienste"
#: src/model/session.js:100 src/options/auth.js:40
#: src/model/session.js:101 src/options/auth.js:40
msgid "Session"
msgstr "Session"
@@ -2614,7 +2614,8 @@ msgid "Unknown"
msgstr "Unbekannt"
#: src/page/settings/account.vue:40 src/page/settings/account.vue:48
#: src/component/navigation.vue:93
#: src/component/navigation.vue:93 src/page/settings/account.vue:39
#: src/page/settings/account.vue:47
msgid "Unregistered"
msgstr "Unregistriert"
@@ -2643,7 +2644,7 @@ msgstr "Aktualisiere Index"
msgid "Updating moments"
msgstr "Aktualisiere Erlebnisse"
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:140
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:139
msgid "Updating picture…"
msgstr "Bild wird aktualisiert…"
@@ -2724,8 +2725,8 @@ msgid "Use Presets"
msgstr "Presets anwenden"
#: src/dialog/account/passcode.vue:82 src/dialog/account/passcode.vue:4
msgid "Use the following recovery code to access your account when you are unable to generate a valid passcode with your authenticator app or device:"
msgstr "Verwende den folgenden Wiederherstellungscode, um auf dein Konto zuzugreifen, wenn du mit deiner Authenticator-App bzw. deinem Gerät keinen gültigen Verifizierungscode generieren kannst:"
msgid "Use the following recovery code to access your account when you are unable to generate a verification code with your authenticator app:"
msgstr ""
#: src/options/admin.js:10 src/page/admin/sessions.vue:59
#: src/page/admin/sessions.vue:2 src/page/admin/sessions.vue:26
@@ -2910,6 +2911,12 @@ msgstr "Deine Bilder werden kontinuierlich analysiert, um automatisch Alben von
msgid "Zoom in/out"
msgstr "Herein/Herauszoomen"
#~ msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated passcode for verification:"
#~ msgstr "Scanne den QR-Code mit deiner Authenticator-App oder verwende den unten gezeigten Schlüssel für die Einrichtung und gib dann den generierten Verifizierungscode ein:"
#~ msgid "Use the following recovery code to access your account when you are unable to generate a valid passcode with your authenticator app or device:"
#~ msgstr "Verwende den folgenden Wiederherstellungscode, um auf dein Konto zuzugreifen, wenn du mit deiner Authenticator-App bzw. deinem Gerät keinen gültigen Verifizierungscode generieren kannst:"
#~ msgid "providerName"
#~ msgstr "providerName"

View File

@@ -524,7 +524,7 @@ msgstr "Αλλαγή ιδιωτικής κατάστασης"
#: src/dialog/album/edit.vue:56 src/dialog/photo/edit/files.vue:147
#: src/dialog/service/edit.vue:99 src/dialog/share.vue:96
#: src/dialog/share.vue:115 src/page/albums.vue:563
#: src/page/settings/account.vue:129 src/page/settings/account.vue:147
#: src/page/settings/account.vue:128 src/page/settings/account.vue:146
#: src/page/settings/advanced.vue:53 src/page/settings/general.vue:100
#: src/page/settings/library.vue:52
msgid "Changes successfully saved"
@@ -712,7 +712,7 @@ msgid "Debug Logs"
msgstr "Αρχεία καταγραφής σφαλμάτων"
#: src/options/admin.js:31 src/page/admin/sessions.vue:60
#: src/page/admin/users.vue:100 src/model/session.js:63 src/model/user.js:234
#: src/page/admin/users.vue:100 src/model/session.js:64 src/model/user.js:234
#: src/options/auth.js:20 src/options/auth.js:21 src/options/auth.js:38
#: src/options/auth.js:39 src/options/options.js:309 src/options/options.js:373
#: src/options/themes.js:492 src/page/places.vue:141
@@ -2183,7 +2183,7 @@ msgid "Scan"
msgstr "Σάρωση"
#: src/dialog/account/passcode.vue:51 src/dialog/account/passcode.vue:4
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated passcode for verification:"
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated verification code:"
msgstr ""
#: src/component/navigation.vue:125
@@ -2256,7 +2256,7 @@ msgstr "URL υπηρεσίας"
msgid "Services"
msgstr "URL υπηρεσίας"
#: src/model/session.js:100 src/options/auth.js:40
#: src/model/session.js:101 src/options/auth.js:40
msgid "Session"
msgstr "Σύνοδος"
@@ -2611,7 +2611,8 @@ msgid "Unknown"
msgstr "Άγνωστος"
#: src/page/settings/account.vue:40 src/page/settings/account.vue:48
#: src/component/navigation.vue:93
#: src/component/navigation.vue:93 src/page/settings/account.vue:39
#: src/page/settings/account.vue:47
msgid "Unregistered"
msgstr "Μη εγγεγραμμένο"
@@ -2640,7 +2641,7 @@ msgstr "Ενημέρωση ευρετηρίου"
msgid "Updating moments"
msgstr "Ενημέρωση στιγμών"
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:140
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:139
msgid "Updating picture…"
msgstr "Ενημέρωση εικόνας…"
@@ -2721,7 +2722,7 @@ msgid "Use Presets"
msgstr "Χρήση Προεπιλογών"
#: src/dialog/account/passcode.vue:82 src/dialog/account/passcode.vue:4
msgid "Use the following recovery code to access your account when you are unable to generate a valid passcode with your authenticator app or device:"
msgid "Use the following recovery code to access your account when you are unable to generate a verification code with your authenticator app:"
msgstr ""
#: src/options/admin.js:10 src/page/admin/sessions.vue:59

View File

@@ -526,7 +526,7 @@ msgstr ""
#: src/dialog/album/edit.vue:56 src/dialog/photo/edit/files.vue:147
#: src/dialog/service/edit.vue:99 src/dialog/share.vue:96
#: src/dialog/share.vue:115 src/page/albums.vue:563
#: src/page/settings/account.vue:129 src/page/settings/account.vue:147
#: src/page/settings/account.vue:128 src/page/settings/account.vue:146
#: src/page/settings/advanced.vue:53 src/page/settings/general.vue:100
#: src/page/settings/library.vue:52
msgid "Changes successfully saved"
@@ -714,7 +714,7 @@ msgid "Debug Logs"
msgstr ""
#: src/options/admin.js:31 src/page/admin/sessions.vue:60
#: src/page/admin/users.vue:100 src/model/session.js:63 src/model/user.js:234
#: src/page/admin/users.vue:100 src/model/session.js:64 src/model/user.js:234
#: src/options/auth.js:20 src/options/auth.js:21 src/options/auth.js:38
#: src/options/auth.js:39 src/options/options.js:309 src/options/options.js:373
#: src/options/themes.js:492 src/page/places.vue:141
@@ -2185,7 +2185,7 @@ msgid "Scan"
msgstr ""
#: src/dialog/account/passcode.vue:51 src/dialog/account/passcode.vue:4
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated passcode for verification:"
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated verification code:"
msgstr ""
#: src/component/navigation.vue:125
@@ -2258,7 +2258,7 @@ msgstr ""
msgid "Services"
msgstr ""
#: src/model/session.js:100 src/options/auth.js:40
#: src/model/session.js:101 src/options/auth.js:40
msgid "Session"
msgstr ""
@@ -2613,7 +2613,8 @@ msgid "Unknown"
msgstr ""
#: src/page/settings/account.vue:40 src/page/settings/account.vue:48
#: src/component/navigation.vue:93
#: src/component/navigation.vue:93 src/page/settings/account.vue:39
#: src/page/settings/account.vue:47
msgid "Unregistered"
msgstr ""
@@ -2642,7 +2643,7 @@ msgstr ""
msgid "Updating moments"
msgstr ""
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:140
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:139
msgid "Updating picture…"
msgstr ""
@@ -2723,7 +2724,7 @@ msgid "Use Presets"
msgstr ""
#: src/dialog/account/passcode.vue:82 src/dialog/account/passcode.vue:4
msgid "Use the following recovery code to access your account when you are unable to generate a valid passcode with your authenticator app or device:"
msgid "Use the following recovery code to access your account when you are unable to generate a verification code with your authenticator app:"
msgstr ""
#: src/options/admin.js:10 src/page/admin/sessions.vue:59

View File

@@ -527,7 +527,7 @@ msgstr "Cambiar indicador de privado"
#: src/dialog/album/edit.vue:56 src/dialog/photo/edit/files.vue:147
#: src/dialog/service/edit.vue:99 src/dialog/share.vue:96
#: src/dialog/share.vue:115 src/page/albums.vue:563
#: src/page/settings/account.vue:129 src/page/settings/account.vue:147
#: src/page/settings/account.vue:128 src/page/settings/account.vue:146
#: src/page/settings/advanced.vue:53 src/page/settings/general.vue:100
#: src/page/settings/library.vue:52
msgid "Changes successfully saved"
@@ -715,7 +715,7 @@ msgid "Debug Logs"
msgstr "Registros de depuración"
#: src/options/admin.js:31 src/page/admin/sessions.vue:60
#: src/page/admin/users.vue:100 src/model/session.js:63 src/model/user.js:234
#: src/page/admin/users.vue:100 src/model/session.js:64 src/model/user.js:234
#: src/options/auth.js:20 src/options/auth.js:21 src/options/auth.js:38
#: src/options/auth.js:39 src/options/options.js:309 src/options/options.js:373
#: src/options/themes.js:492 src/page/places.vue:141
@@ -2187,7 +2187,7 @@ msgid "Scan"
msgstr "Escanear"
#: src/dialog/account/passcode.vue:51 src/dialog/account/passcode.vue:4
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated passcode for verification:"
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated verification code:"
msgstr ""
#: src/component/navigation.vue:125
@@ -2260,7 +2260,7 @@ msgstr "URL del servicio"
msgid "Services"
msgstr "Servicios"
#: src/model/session.js:100 src/options/auth.js:40
#: src/model/session.js:101 src/options/auth.js:40
msgid "Session"
msgstr "Sesión"
@@ -2615,7 +2615,8 @@ msgid "Unknown"
msgstr "Desconocido"
#: src/page/settings/account.vue:40 src/page/settings/account.vue:48
#: src/component/navigation.vue:93
#: src/component/navigation.vue:93 src/page/settings/account.vue:39
#: src/page/settings/account.vue:47
msgid "Unregistered"
msgstr "No registrado"
@@ -2644,7 +2645,7 @@ msgstr "Actualizando índice"
msgid "Updating moments"
msgstr "Actualizando Momentos"
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:140
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:139
msgid "Updating picture…"
msgstr "Actualizando la imagen…"
@@ -2725,7 +2726,7 @@ msgid "Use Presets"
msgstr "Usar preselecciones"
#: src/dialog/account/passcode.vue:82 src/dialog/account/passcode.vue:4
msgid "Use the following recovery code to access your account when you are unable to generate a valid passcode with your authenticator app or device:"
msgid "Use the following recovery code to access your account when you are unable to generate a verification code with your authenticator app:"
msgstr ""
#: src/options/admin.js:10 src/page/admin/sessions.vue:59

View File

@@ -524,7 +524,7 @@ msgstr "Muuda isiklikkust"
#: src/dialog/album/edit.vue:56 src/dialog/photo/edit/files.vue:147
#: src/dialog/service/edit.vue:99 src/dialog/share.vue:96
#: src/dialog/share.vue:115 src/page/albums.vue:563
#: src/page/settings/account.vue:129 src/page/settings/account.vue:147
#: src/page/settings/account.vue:128 src/page/settings/account.vue:146
#: src/page/settings/advanced.vue:53 src/page/settings/general.vue:100
#: src/page/settings/library.vue:52
msgid "Changes successfully saved"
@@ -712,7 +712,7 @@ msgid "Debug Logs"
msgstr "Tõrkeotsingu logid"
#: src/options/admin.js:31 src/page/admin/sessions.vue:60
#: src/page/admin/users.vue:100 src/model/session.js:63 src/model/user.js:234
#: src/page/admin/users.vue:100 src/model/session.js:64 src/model/user.js:234
#: src/options/auth.js:20 src/options/auth.js:21 src/options/auth.js:38
#: src/options/auth.js:39 src/options/options.js:309 src/options/options.js:373
#: src/options/themes.js:492 src/page/places.vue:141
@@ -2183,7 +2183,7 @@ msgid "Scan"
msgstr "Skannitud"
#: src/dialog/account/passcode.vue:51 src/dialog/account/passcode.vue:4
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated passcode for verification:"
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated verification code:"
msgstr ""
#: src/component/navigation.vue:125
@@ -2256,7 +2256,7 @@ msgstr "Teenuse URL"
msgid "Services"
msgstr "Teenused"
#: src/model/session.js:100 src/options/auth.js:40
#: src/model/session.js:101 src/options/auth.js:40
msgid "Session"
msgstr "Sessioon"
@@ -2611,7 +2611,8 @@ msgid "Unknown"
msgstr "Teadmata"
#: src/page/settings/account.vue:40 src/page/settings/account.vue:48
#: src/component/navigation.vue:93
#: src/component/navigation.vue:93 src/page/settings/account.vue:39
#: src/page/settings/account.vue:47
msgid "Unregistered"
msgstr "Mitteregistreeritud"
@@ -2640,7 +2641,7 @@ msgstr "Indeksi uuendamine"
msgid "Updating moments"
msgstr "Hetkede uuendamine"
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:140
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:139
msgid "Updating picture…"
msgstr "Pildi uuendamine…"
@@ -2721,7 +2722,7 @@ msgid "Use Presets"
msgstr "Kasuta eelseadistusi"
#: src/dialog/account/passcode.vue:82 src/dialog/account/passcode.vue:4
msgid "Use the following recovery code to access your account when you are unable to generate a valid passcode with your authenticator app or device:"
msgid "Use the following recovery code to access your account when you are unable to generate a verification code with your authenticator app:"
msgstr ""
#: src/options/admin.js:10 src/page/admin/sessions.vue:59

View File

@@ -524,7 +524,7 @@ msgstr "Aldatu bandera pribatua"
#: src/dialog/album/edit.vue:56 src/dialog/photo/edit/files.vue:147
#: src/dialog/service/edit.vue:99 src/dialog/share.vue:96
#: src/dialog/share.vue:115 src/page/albums.vue:563
#: src/page/settings/account.vue:129 src/page/settings/account.vue:147
#: src/page/settings/account.vue:128 src/page/settings/account.vue:146
#: src/page/settings/advanced.vue:53 src/page/settings/general.vue:100
#: src/page/settings/library.vue:52
msgid "Changes successfully saved"
@@ -712,7 +712,7 @@ msgid "Debug Logs"
msgstr "Arazte-erregistroak"
#: src/options/admin.js:31 src/page/admin/sessions.vue:60
#: src/page/admin/users.vue:100 src/model/session.js:63 src/model/user.js:234
#: src/page/admin/users.vue:100 src/model/session.js:64 src/model/user.js:234
#: src/options/auth.js:20 src/options/auth.js:21 src/options/auth.js:38
#: src/options/auth.js:39 src/options/options.js:309 src/options/options.js:373
#: src/options/themes.js:492 src/page/places.vue:141
@@ -2183,7 +2183,7 @@ msgid "Scan"
msgstr "Eskaneatu"
#: src/dialog/account/passcode.vue:51 src/dialog/account/passcode.vue:4
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated passcode for verification:"
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated verification code:"
msgstr ""
#: src/component/navigation.vue:125
@@ -2256,7 +2256,7 @@ msgstr "Zerbitzuaren URLa"
msgid "Services"
msgstr "Zerbitzuak"
#: src/model/session.js:100 src/options/auth.js:40
#: src/model/session.js:101 src/options/auth.js:40
msgid "Session"
msgstr "Saioa"
@@ -2611,7 +2611,8 @@ msgid "Unknown"
msgstr "Ezezaguna"
#: src/page/settings/account.vue:40 src/page/settings/account.vue:48
#: src/component/navigation.vue:93
#: src/component/navigation.vue:93 src/page/settings/account.vue:39
#: src/page/settings/account.vue:47
msgid "Unregistered"
msgstr "Erregistratu gabe"
@@ -2640,7 +2641,7 @@ msgstr "Indizea eguneratzen"
msgid "Updating moments"
msgstr "Eguneratzea uneak"
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:140
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:139
msgid "Updating picture…"
msgstr "Irudia eguneratzen…"
@@ -2721,7 +2722,7 @@ msgid "Use Presets"
msgstr "Erabili aurrezarpenak"
#: src/dialog/account/passcode.vue:82 src/dialog/account/passcode.vue:4
msgid "Use the following recovery code to access your account when you are unable to generate a valid passcode with your authenticator app or device:"
msgid "Use the following recovery code to access your account when you are unable to generate a verification code with your authenticator app:"
msgstr ""
#: src/options/admin.js:10 src/page/admin/sessions.vue:59

View File

@@ -527,7 +527,7 @@ msgstr "تغییر پرچم خصوصی"
#: src/dialog/album/edit.vue:56 src/dialog/photo/edit/files.vue:147
#: src/dialog/service/edit.vue:99 src/dialog/share.vue:96
#: src/dialog/share.vue:115 src/page/albums.vue:563
#: src/page/settings/account.vue:129 src/page/settings/account.vue:147
#: src/page/settings/account.vue:128 src/page/settings/account.vue:146
#: src/page/settings/advanced.vue:53 src/page/settings/general.vue:100
#: src/page/settings/library.vue:52
msgid "Changes successfully saved"
@@ -715,7 +715,7 @@ msgid "Debug Logs"
msgstr "گزارش‌های اشکال زدایی"
#: src/options/admin.js:31 src/page/admin/sessions.vue:60
#: src/page/admin/users.vue:100 src/model/session.js:63 src/model/user.js:234
#: src/page/admin/users.vue:100 src/model/session.js:64 src/model/user.js:234
#: src/options/auth.js:20 src/options/auth.js:21 src/options/auth.js:38
#: src/options/auth.js:39 src/options/options.js:309 src/options/options.js:373
#: src/options/themes.js:492 src/page/places.vue:141
@@ -2186,7 +2186,7 @@ msgid "Scan"
msgstr "اسکن"
#: src/dialog/account/passcode.vue:51 src/dialog/account/passcode.vue:4
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated passcode for verification:"
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated verification code:"
msgstr ""
#: src/component/navigation.vue:125
@@ -2259,7 +2259,7 @@ msgstr "URL سرویس"
msgid "Services"
msgstr "URL سرویس"
#: src/model/session.js:100 src/options/auth.js:40
#: src/model/session.js:101 src/options/auth.js:40
msgid "Session"
msgstr "جلسه"
@@ -2614,7 +2614,8 @@ msgid "Unknown"
msgstr "ناشناس"
#: src/page/settings/account.vue:40 src/page/settings/account.vue:48
#: src/component/navigation.vue:93
#: src/component/navigation.vue:93 src/page/settings/account.vue:39
#: src/page/settings/account.vue:47
msgid "Unregistered"
msgstr "ثبت نشده"
@@ -2643,7 +2644,7 @@ msgstr "به روزرسانی نمایه ها"
msgid "Updating moments"
msgstr "به روزرسانی لحظه ها"
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:140
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:139
msgid "Updating picture…"
msgstr "به روزرسانی پیش نمایش ها"
@@ -2724,7 +2725,7 @@ msgid "Use Presets"
msgstr "از Presets استفاده کنید"
#: src/dialog/account/passcode.vue:82 src/dialog/account/passcode.vue:4
msgid "Use the following recovery code to access your account when you are unable to generate a valid passcode with your authenticator app or device:"
msgid "Use the following recovery code to access your account when you are unable to generate a verification code with your authenticator app:"
msgstr ""
#: src/options/admin.js:10 src/page/admin/sessions.vue:59

View File

@@ -524,7 +524,7 @@ msgstr "Muuta yksityisyyden tilaa"
#: src/dialog/album/edit.vue:56 src/dialog/photo/edit/files.vue:147
#: src/dialog/service/edit.vue:99 src/dialog/share.vue:96
#: src/dialog/share.vue:115 src/page/albums.vue:563
#: src/page/settings/account.vue:129 src/page/settings/account.vue:147
#: src/page/settings/account.vue:128 src/page/settings/account.vue:146
#: src/page/settings/advanced.vue:53 src/page/settings/general.vue:100
#: src/page/settings/library.vue:52
msgid "Changes successfully saved"
@@ -712,7 +712,7 @@ msgid "Debug Logs"
msgstr "Vianmäärityslokit"
#: src/options/admin.js:31 src/page/admin/sessions.vue:60
#: src/page/admin/users.vue:100 src/model/session.js:63 src/model/user.js:234
#: src/page/admin/users.vue:100 src/model/session.js:64 src/model/user.js:234
#: src/options/auth.js:20 src/options/auth.js:21 src/options/auth.js:38
#: src/options/auth.js:39 src/options/options.js:309 src/options/options.js:373
#: src/options/themes.js:492 src/page/places.vue:141
@@ -2183,7 +2183,7 @@ msgid "Scan"
msgstr "Skannaa"
#: src/dialog/account/passcode.vue:51 src/dialog/account/passcode.vue:4
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated passcode for verification:"
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated verification code:"
msgstr ""
#: src/component/navigation.vue:125
@@ -2256,7 +2256,7 @@ msgstr "Palvelun URL-osoite"
msgid "Services"
msgstr "Palvelun URL-osoite"
#: src/model/session.js:100 src/options/auth.js:40
#: src/model/session.js:101 src/options/auth.js:40
msgid "Session"
msgstr "Istunto"
@@ -2611,7 +2611,8 @@ msgid "Unknown"
msgstr "Tuntematon"
#: src/page/settings/account.vue:40 src/page/settings/account.vue:48
#: src/component/navigation.vue:93
#: src/component/navigation.vue:93 src/page/settings/account.vue:39
#: src/page/settings/account.vue:47
msgid "Unregistered"
msgstr "Rekisteröimätön"
@@ -2640,7 +2641,7 @@ msgstr "Päivitetään indeksiä"
msgid "Updating moments"
msgstr "Päivitetään hetkiä"
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:140
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:139
msgid "Updating picture…"
msgstr "Päivitetään esikatseluita"
@@ -2721,7 +2722,7 @@ msgid "Use Presets"
msgstr "Käytä esiasetuksia"
#: src/dialog/account/passcode.vue:82 src/dialog/account/passcode.vue:4
msgid "Use the following recovery code to access your account when you are unable to generate a valid passcode with your authenticator app or device:"
msgid "Use the following recovery code to access your account when you are unable to generate a verification code with your authenticator app:"
msgstr ""
#: src/options/admin.js:10 src/page/admin/sessions.vue:59

View File

@@ -527,7 +527,7 @@ msgstr "Modifier le statut privé"
#: src/dialog/album/edit.vue:56 src/dialog/photo/edit/files.vue:147
#: src/dialog/service/edit.vue:99 src/dialog/share.vue:96
#: src/dialog/share.vue:115 src/page/albums.vue:563
#: src/page/settings/account.vue:129 src/page/settings/account.vue:147
#: src/page/settings/account.vue:128 src/page/settings/account.vue:146
#: src/page/settings/advanced.vue:53 src/page/settings/general.vue:100
#: src/page/settings/library.vue:52
msgid "Changes successfully saved"
@@ -715,7 +715,7 @@ msgid "Debug Logs"
msgstr "Journaux de débogage"
#: src/options/admin.js:31 src/page/admin/sessions.vue:60
#: src/page/admin/users.vue:100 src/model/session.js:63 src/model/user.js:234
#: src/page/admin/users.vue:100 src/model/session.js:64 src/model/user.js:234
#: src/options/auth.js:20 src/options/auth.js:21 src/options/auth.js:38
#: src/options/auth.js:39 src/options/options.js:309 src/options/options.js:373
#: src/options/themes.js:492 src/page/places.vue:141
@@ -2186,8 +2186,8 @@ msgid "Scan"
msgstr "Numérisée"
#: src/dialog/account/passcode.vue:51 src/dialog/account/passcode.vue:4
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated passcode for verification:"
msgstr "Scannez le code QR avec votre application d'authentification ou utilisez la clé d'installation indiquée ci-dessous, puis entrez le code généré pour la vérification :"
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated verification code:"
msgstr ""
#: src/component/navigation.vue:125
msgid "Scans"
@@ -2259,7 +2259,7 @@ msgstr "URL du service"
msgid "Services"
msgstr "Services"
#: src/model/session.js:100 src/options/auth.js:40
#: src/model/session.js:101 src/options/auth.js:40
msgid "Session"
msgstr "Session"
@@ -2614,7 +2614,8 @@ msgid "Unknown"
msgstr "Inconnu"
#: src/page/settings/account.vue:40 src/page/settings/account.vue:48
#: src/component/navigation.vue:93
#: src/component/navigation.vue:93 src/page/settings/account.vue:39
#: src/page/settings/account.vue:47
msgid "Unregistered"
msgstr "Non enregistré"
@@ -2643,7 +2644,7 @@ msgstr "Mise à jour de l'index"
msgid "Updating moments"
msgstr "Mise à jour des moments"
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:140
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:139
msgid "Updating picture…"
msgstr "Mise à jour de l'image…"
@@ -2724,8 +2725,8 @@ msgid "Use Presets"
msgstr "Utiliser les préréglages"
#: src/dialog/account/passcode.vue:82 src/dialog/account/passcode.vue:4
msgid "Use the following recovery code to access your account when you are unable to generate a valid passcode with your authenticator app or device:"
msgstr "Utilisez le code de récupération suivant pour accéder à votre compte si vous ne parvenez pas à générer un mot de passe valide avec votre application ou appareil d'authentification :"
msgid "Use the following recovery code to access your account when you are unable to generate a verification code with your authenticator app:"
msgstr ""
#: src/options/admin.js:10 src/page/admin/sessions.vue:59
#: src/page/admin/sessions.vue:2 src/page/admin/sessions.vue:26
@@ -2910,6 +2911,12 @@ msgstr "Votre bibliothèque est analysée en permanence pour créer automatiquem
msgid "Zoom in/out"
msgstr "Agrandir/Dézoomer"
#~ msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated passcode for verification:"
#~ msgstr "Scannez le code QR avec votre application d'authentification ou utilisez la clé d'installation indiquée ci-dessous, puis entrez le code généré pour la vérification :"
#~ msgid "Use the following recovery code to access your account when you are unable to generate a valid passcode with your authenticator app or device:"
#~ msgstr "Utilisez le code de récupération suivant pour accéder à votre compte si vous ne parvenez pas à générer un mot de passe valide avec votre application ou appareil d'authentification :"
#~ msgid "App Name"
#~ msgstr "Nom de l'application"

View File

@@ -527,7 +527,7 @@ msgstr "שינוי דגל הפרטיות"
#: src/dialog/album/edit.vue:56 src/dialog/photo/edit/files.vue:147
#: src/dialog/service/edit.vue:99 src/dialog/share.vue:96
#: src/dialog/share.vue:115 src/page/albums.vue:563
#: src/page/settings/account.vue:129 src/page/settings/account.vue:147
#: src/page/settings/account.vue:128 src/page/settings/account.vue:146
#: src/page/settings/advanced.vue:53 src/page/settings/general.vue:100
#: src/page/settings/library.vue:52
msgid "Changes successfully saved"
@@ -715,7 +715,7 @@ msgid "Debug Logs"
msgstr "Debug Logs"
#: src/options/admin.js:31 src/page/admin/sessions.vue:60
#: src/page/admin/users.vue:100 src/model/session.js:63 src/model/user.js:234
#: src/page/admin/users.vue:100 src/model/session.js:64 src/model/user.js:234
#: src/options/auth.js:20 src/options/auth.js:21 src/options/auth.js:38
#: src/options/auth.js:39 src/options/options.js:309 src/options/options.js:373
#: src/options/themes.js:492 src/page/places.vue:141
@@ -2186,7 +2186,7 @@ msgid "Scan"
msgstr "סרוק"
#: src/dialog/account/passcode.vue:51 src/dialog/account/passcode.vue:4
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated passcode for verification:"
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated verification code:"
msgstr ""
#: src/component/navigation.vue:125
@@ -2259,7 +2259,7 @@ msgstr "נתיב השרות"
msgid "Services"
msgstr "שירותים"
#: src/model/session.js:100 src/options/auth.js:40
#: src/model/session.js:101 src/options/auth.js:40
msgid "Session"
msgstr "מוֹשָׁב"
@@ -2614,7 +2614,8 @@ msgid "Unknown"
msgstr "לא ידוע"
#: src/page/settings/account.vue:40 src/page/settings/account.vue:48
#: src/component/navigation.vue:93
#: src/component/navigation.vue:93 src/page/settings/account.vue:39
#: src/page/settings/account.vue:47
msgid "Unregistered"
msgstr "משתמש לא רשום"
@@ -2643,7 +2644,7 @@ msgstr "מעדכן אינדקס"
msgid "Updating moments"
msgstr "מעדכן רגעים"
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:140
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:139
msgid "Updating picture…"
msgstr "מעדכן תמונה..."
@@ -2724,7 +2725,7 @@ msgid "Use Presets"
msgstr "השתמש בהגדרות קבועות מראש"
#: src/dialog/account/passcode.vue:82 src/dialog/account/passcode.vue:4
msgid "Use the following recovery code to access your account when you are unable to generate a valid passcode with your authenticator app or device:"
msgid "Use the following recovery code to access your account when you are unable to generate a verification code with your authenticator app:"
msgstr ""
#: src/options/admin.js:10 src/page/admin/sessions.vue:59

View File

@@ -527,7 +527,7 @@ msgstr "निजी ध्वज बदलें"
#: src/dialog/album/edit.vue:56 src/dialog/photo/edit/files.vue:147
#: src/dialog/service/edit.vue:99 src/dialog/share.vue:96
#: src/dialog/share.vue:115 src/page/albums.vue:563
#: src/page/settings/account.vue:129 src/page/settings/account.vue:147
#: src/page/settings/account.vue:128 src/page/settings/account.vue:146
#: src/page/settings/advanced.vue:53 src/page/settings/general.vue:100
#: src/page/settings/library.vue:52
msgid "Changes successfully saved"
@@ -715,7 +715,7 @@ msgid "Debug Logs"
msgstr "दोषमार्जन लॉग"
#: src/options/admin.js:31 src/page/admin/sessions.vue:60
#: src/page/admin/users.vue:100 src/model/session.js:63 src/model/user.js:234
#: src/page/admin/users.vue:100 src/model/session.js:64 src/model/user.js:234
#: src/options/auth.js:20 src/options/auth.js:21 src/options/auth.js:38
#: src/options/auth.js:39 src/options/options.js:309 src/options/options.js:373
#: src/options/themes.js:492 src/page/places.vue:141
@@ -2186,7 +2186,7 @@ msgid "Scan"
msgstr "स्कैन"
#: src/dialog/account/passcode.vue:51 src/dialog/account/passcode.vue:4
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated passcode for verification:"
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated verification code:"
msgstr ""
#: src/component/navigation.vue:125
@@ -2259,7 +2259,7 @@ msgstr "सेवा URL"
msgid "Services"
msgstr "सेवाएं"
#: src/model/session.js:100 src/options/auth.js:40
#: src/model/session.js:101 src/options/auth.js:40
msgid "Session"
msgstr "सत्र"
@@ -2614,7 +2614,8 @@ msgid "Unknown"
msgstr "अनजान"
#: src/page/settings/account.vue:40 src/page/settings/account.vue:48
#: src/component/navigation.vue:93
#: src/component/navigation.vue:93 src/page/settings/account.vue:39
#: src/page/settings/account.vue:47
msgid "Unregistered"
msgstr "अपंजीकृत"
@@ -2643,7 +2644,7 @@ msgstr "इंडेक्स अपडेट कर रहा है"
msgid "Updating moments"
msgstr "पल-पल का अपडेट"
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:140
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:139
msgid "Updating picture…"
msgstr "चित्र अपडेट किया जा रहा है…"
@@ -2724,7 +2725,7 @@ msgid "Use Presets"
msgstr "प्रीसेट का उपयोग करें"
#: src/dialog/account/passcode.vue:82 src/dialog/account/passcode.vue:4
msgid "Use the following recovery code to access your account when you are unable to generate a valid passcode with your authenticator app or device:"
msgid "Use the following recovery code to access your account when you are unable to generate a verification code with your authenticator app:"
msgstr ""
#: src/options/admin.js:10 src/page/admin/sessions.vue:59

View File

@@ -527,7 +527,7 @@ msgstr "Promijenite privatnu zastavu"
#: src/dialog/album/edit.vue:56 src/dialog/photo/edit/files.vue:147
#: src/dialog/service/edit.vue:99 src/dialog/share.vue:96
#: src/dialog/share.vue:115 src/page/albums.vue:563
#: src/page/settings/account.vue:129 src/page/settings/account.vue:147
#: src/page/settings/account.vue:128 src/page/settings/account.vue:146
#: src/page/settings/advanced.vue:53 src/page/settings/general.vue:100
#: src/page/settings/library.vue:52
msgid "Changes successfully saved"
@@ -715,7 +715,7 @@ msgid "Debug Logs"
msgstr "Zapisnici otklanjanja pogrešaka"
#: src/options/admin.js:31 src/page/admin/sessions.vue:60
#: src/page/admin/users.vue:100 src/model/session.js:63 src/model/user.js:234
#: src/page/admin/users.vue:100 src/model/session.js:64 src/model/user.js:234
#: src/options/auth.js:20 src/options/auth.js:21 src/options/auth.js:38
#: src/options/auth.js:39 src/options/options.js:309 src/options/options.js:373
#: src/options/themes.js:492 src/page/places.vue:141
@@ -2186,7 +2186,7 @@ msgid "Scan"
msgstr "Skeniraj"
#: src/dialog/account/passcode.vue:51 src/dialog/account/passcode.vue:4
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated passcode for verification:"
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated verification code:"
msgstr ""
#: src/component/navigation.vue:125
@@ -2259,7 +2259,7 @@ msgstr "URL usluge"
msgid "Services"
msgstr "URL usluge"
#: src/model/session.js:100 src/options/auth.js:40
#: src/model/session.js:101 src/options/auth.js:40
msgid "Session"
msgstr "Sjednica"
@@ -2614,7 +2614,8 @@ msgid "Unknown"
msgstr "Nepoznato"
#: src/page/settings/account.vue:40 src/page/settings/account.vue:48
#: src/component/navigation.vue:93
#: src/component/navigation.vue:93 src/page/settings/account.vue:39
#: src/page/settings/account.vue:47
msgid "Unregistered"
msgstr "Neregistriran"
@@ -2643,7 +2644,7 @@ msgstr "Ažuriranje indeksa"
msgid "Updating moments"
msgstr "Trenuci ažuriranja"
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:140
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:139
msgid "Updating picture…"
msgstr "Ažuriranje pregleda"
@@ -2724,7 +2725,7 @@ msgid "Use Presets"
msgstr "Koristite unaprijed postavljene postavke"
#: src/dialog/account/passcode.vue:82 src/dialog/account/passcode.vue:4
msgid "Use the following recovery code to access your account when you are unable to generate a valid passcode with your authenticator app or device:"
msgid "Use the following recovery code to access your account when you are unable to generate a verification code with your authenticator app:"
msgstr ""
#: src/options/admin.js:10 src/page/admin/sessions.vue:59

View File

@@ -526,7 +526,7 @@ msgstr "Privát fotóként jelölés"
#: src/dialog/album/edit.vue:56 src/dialog/photo/edit/files.vue:147
#: src/dialog/service/edit.vue:99 src/dialog/share.vue:96
#: src/dialog/share.vue:115 src/page/albums.vue:563
#: src/page/settings/account.vue:129 src/page/settings/account.vue:147
#: src/page/settings/account.vue:128 src/page/settings/account.vue:146
#: src/page/settings/advanced.vue:53 src/page/settings/general.vue:100
#: src/page/settings/library.vue:52
msgid "Changes successfully saved"
@@ -714,7 +714,7 @@ msgid "Debug Logs"
msgstr "Hibakeresési naplók"
#: src/options/admin.js:31 src/page/admin/sessions.vue:60
#: src/page/admin/users.vue:100 src/model/session.js:63 src/model/user.js:234
#: src/page/admin/users.vue:100 src/model/session.js:64 src/model/user.js:234
#: src/options/auth.js:20 src/options/auth.js:21 src/options/auth.js:38
#: src/options/auth.js:39 src/options/options.js:309 src/options/options.js:373
#: src/options/themes.js:492 src/page/places.vue:141
@@ -2185,7 +2185,7 @@ msgid "Scan"
msgstr "Felderítés"
#: src/dialog/account/passcode.vue:51 src/dialog/account/passcode.vue:4
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated passcode for verification:"
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated verification code:"
msgstr ""
#: src/component/navigation.vue:125
@@ -2258,7 +2258,7 @@ msgstr "Szolgáltatás URL-je"
msgid "Services"
msgstr "Szolgáltatások"
#: src/model/session.js:100 src/options/auth.js:40
#: src/model/session.js:101 src/options/auth.js:40
msgid "Session"
msgstr "Ülés"
@@ -2613,7 +2613,8 @@ msgid "Unknown"
msgstr "Ismeretlen"
#: src/page/settings/account.vue:40 src/page/settings/account.vue:48
#: src/component/navigation.vue:93
#: src/component/navigation.vue:93 src/page/settings/account.vue:39
#: src/page/settings/account.vue:47
msgid "Unregistered"
msgstr "Nem regisztrált"
@@ -2642,7 +2643,7 @@ msgstr "Index frissítése"
msgid "Updating moments"
msgstr "Frissítő pillanatok"
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:140
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:139
msgid "Updating picture…"
msgstr "Kép frissítése…"
@@ -2723,7 +2724,7 @@ msgid "Use Presets"
msgstr "Előbeállítások használata"
#: src/dialog/account/passcode.vue:82 src/dialog/account/passcode.vue:4
msgid "Use the following recovery code to access your account when you are unable to generate a valid passcode with your authenticator app or device:"
msgid "Use the following recovery code to access your account when you are unable to generate a verification code with your authenticator app:"
msgstr ""
#: src/options/admin.js:10 src/page/admin/sessions.vue:59

View File

@@ -527,7 +527,7 @@ msgstr "Ubah bendera pribadi"
#: src/dialog/album/edit.vue:56 src/dialog/photo/edit/files.vue:147
#: src/dialog/service/edit.vue:99 src/dialog/share.vue:96
#: src/dialog/share.vue:115 src/page/albums.vue:563
#: src/page/settings/account.vue:129 src/page/settings/account.vue:147
#: src/page/settings/account.vue:128 src/page/settings/account.vue:146
#: src/page/settings/advanced.vue:53 src/page/settings/general.vue:100
#: src/page/settings/library.vue:52
msgid "Changes successfully saved"
@@ -715,7 +715,7 @@ msgid "Debug Logs"
msgstr "Log Debug"
#: src/options/admin.js:31 src/page/admin/sessions.vue:60
#: src/page/admin/users.vue:100 src/model/session.js:63 src/model/user.js:234
#: src/page/admin/users.vue:100 src/model/session.js:64 src/model/user.js:234
#: src/options/auth.js:20 src/options/auth.js:21 src/options/auth.js:38
#: src/options/auth.js:39 src/options/options.js:309 src/options/options.js:373
#: src/options/themes.js:492 src/page/places.vue:141
@@ -2186,7 +2186,7 @@ msgid "Scan"
msgstr "Pindai"
#: src/dialog/account/passcode.vue:51 src/dialog/account/passcode.vue:4
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated passcode for verification:"
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated verification code:"
msgstr ""
#: src/component/navigation.vue:125
@@ -2259,7 +2259,7 @@ msgstr "URL Layanan"
msgid "Services"
msgstr "URL Layanan"
#: src/model/session.js:100 src/options/auth.js:40
#: src/model/session.js:101 src/options/auth.js:40
msgid "Session"
msgstr "Sesi"
@@ -2614,7 +2614,8 @@ msgid "Unknown"
msgstr "Tidak diketahui"
#: src/page/settings/account.vue:40 src/page/settings/account.vue:48
#: src/component/navigation.vue:93
#: src/component/navigation.vue:93 src/page/settings/account.vue:39
#: src/page/settings/account.vue:47
msgid "Unregistered"
msgstr "Tidak terdaftar"
@@ -2643,7 +2644,7 @@ msgstr "Memperbarui indeks"
msgid "Updating moments"
msgstr "Memperbarui momen"
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:140
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:139
msgid "Updating picture…"
msgstr "Memperbarui pratinjau"
@@ -2724,7 +2725,7 @@ msgid "Use Presets"
msgstr "Gunakan Preset"
#: src/dialog/account/passcode.vue:82 src/dialog/account/passcode.vue:4
msgid "Use the following recovery code to access your account when you are unable to generate a valid passcode with your authenticator app or device:"
msgid "Use the following recovery code to access your account when you are unable to generate a verification code with your authenticator app:"
msgstr ""
#: src/options/admin.js:10 src/page/admin/sessions.vue:59

View File

@@ -527,7 +527,7 @@ msgstr "Cambiare la bandiera privata"
#: src/dialog/album/edit.vue:56 src/dialog/photo/edit/files.vue:147
#: src/dialog/service/edit.vue:99 src/dialog/share.vue:96
#: src/dialog/share.vue:115 src/page/albums.vue:563
#: src/page/settings/account.vue:129 src/page/settings/account.vue:147
#: src/page/settings/account.vue:128 src/page/settings/account.vue:146
#: src/page/settings/advanced.vue:53 src/page/settings/general.vue:100
#: src/page/settings/library.vue:52
msgid "Changes successfully saved"
@@ -715,7 +715,7 @@ msgid "Debug Logs"
msgstr "Registri di debug"
#: src/options/admin.js:31 src/page/admin/sessions.vue:60
#: src/page/admin/users.vue:100 src/model/session.js:63 src/model/user.js:234
#: src/page/admin/users.vue:100 src/model/session.js:64 src/model/user.js:234
#: src/options/auth.js:20 src/options/auth.js:21 src/options/auth.js:38
#: src/options/auth.js:39 src/options/options.js:309 src/options/options.js:373
#: src/options/themes.js:492 src/page/places.vue:141
@@ -2186,8 +2186,8 @@ msgid "Scan"
msgstr "Scansione"
#: src/dialog/account/passcode.vue:51 src/dialog/account/passcode.vue:4
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated passcode for verification:"
msgstr "Scannerizza il codice QR con la tua app di autenticazione, oppure utilizza la chiave di configurazione qui sotto. Successivamente, inserisci il codice generato per verificare:"
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated verification code:"
msgstr ""
#: src/component/navigation.vue:125
msgid "Scans"
@@ -2259,7 +2259,7 @@ msgstr "URL Servizio"
msgid "Services"
msgstr "Servizi"
#: src/model/session.js:100 src/options/auth.js:40
#: src/model/session.js:101 src/options/auth.js:40
msgid "Session"
msgstr "Sessione"
@@ -2614,7 +2614,8 @@ msgid "Unknown"
msgstr "Sconosciuto"
#: src/page/settings/account.vue:40 src/page/settings/account.vue:48
#: src/component/navigation.vue:93
#: src/component/navigation.vue:93 src/page/settings/account.vue:39
#: src/page/settings/account.vue:47
msgid "Unregistered"
msgstr "Non registrato"
@@ -2643,7 +2644,7 @@ msgstr "Aggiornamento indici in corso"
msgid "Updating moments"
msgstr "Aggiornamento momenti in corso"
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:140
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:139
msgid "Updating picture…"
msgstr "Aggiornamento dell'immagine…"
@@ -2724,8 +2725,8 @@ msgid "Use Presets"
msgstr "Utilizzare le preimpostazioni"
#: src/dialog/account/passcode.vue:82 src/dialog/account/passcode.vue:4
msgid "Use the following recovery code to access your account when you are unable to generate a valid passcode with your authenticator app or device:"
msgstr "Usa il codice di recupero seguente per accedere al tuo account quando ti è impossibile generare un codice con la tua app o il tuo dispositivo di autenticazione:"
msgid "Use the following recovery code to access your account when you are unable to generate a verification code with your authenticator app:"
msgstr ""
#: src/options/admin.js:10 src/page/admin/sessions.vue:59
#: src/page/admin/sessions.vue:2 src/page/admin/sessions.vue:26
@@ -2910,6 +2911,12 @@ msgstr "La tua libreria viene continuamente analizzata per creare automaticament
msgid "Zoom in/out"
msgstr "Zoom avanti/indietro"
#~ msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated passcode for verification:"
#~ msgstr "Scannerizza il codice QR con la tua app di autenticazione, oppure utilizza la chiave di configurazione qui sotto. Successivamente, inserisci il codice generato per verificare:"
#~ msgid "Use the following recovery code to access your account when you are unable to generate a valid passcode with your authenticator app or device:"
#~ msgstr "Usa il codice di recupero seguente per accedere al tuo account quando ti è impossibile generare un codice con la tua app o il tuo dispositivo di autenticazione:"
#~ msgid "providerName"
#~ msgstr "nome del fornitore"

View File

@@ -527,7 +527,7 @@ msgstr "プライベートであるかどうかを変更"
#: src/dialog/album/edit.vue:56 src/dialog/photo/edit/files.vue:147
#: src/dialog/service/edit.vue:99 src/dialog/share.vue:96
#: src/dialog/share.vue:115 src/page/albums.vue:563
#: src/page/settings/account.vue:129 src/page/settings/account.vue:147
#: src/page/settings/account.vue:128 src/page/settings/account.vue:146
#: src/page/settings/advanced.vue:53 src/page/settings/general.vue:100
#: src/page/settings/library.vue:52
msgid "Changes successfully saved"
@@ -715,7 +715,7 @@ msgid "Debug Logs"
msgstr "デバッグログ"
#: src/options/admin.js:31 src/page/admin/sessions.vue:60
#: src/page/admin/users.vue:100 src/model/session.js:63 src/model/user.js:234
#: src/page/admin/users.vue:100 src/model/session.js:64 src/model/user.js:234
#: src/options/auth.js:20 src/options/auth.js:21 src/options/auth.js:38
#: src/options/auth.js:39 src/options/options.js:309 src/options/options.js:373
#: src/options/themes.js:492 src/page/places.vue:141
@@ -2186,7 +2186,7 @@ msgid "Scan"
msgstr "スキャン"
#: src/dialog/account/passcode.vue:51 src/dialog/account/passcode.vue:4
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated passcode for verification:"
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated verification code:"
msgstr ""
#: src/component/navigation.vue:125
@@ -2259,7 +2259,7 @@ msgstr "サービス URL"
msgid "Services"
msgstr "サービス"
#: src/model/session.js:100 src/options/auth.js:40
#: src/model/session.js:101 src/options/auth.js:40
msgid "Session"
msgstr "セッション"
@@ -2614,7 +2614,8 @@ msgid "Unknown"
msgstr "不明"
#: src/page/settings/account.vue:40 src/page/settings/account.vue:48
#: src/component/navigation.vue:93
#: src/component/navigation.vue:93 src/page/settings/account.vue:39
#: src/page/settings/account.vue:47
msgid "Unregistered"
msgstr "未登録"
@@ -2643,7 +2644,7 @@ msgstr "インデックスを更新しています"
msgid "Updating moments"
msgstr "モーメントを更新しています"
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:140
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:139
msgid "Updating picture…"
msgstr "画像更新中…"
@@ -2724,7 +2725,7 @@ msgid "Use Presets"
msgstr "プリセットを使用"
#: src/dialog/account/passcode.vue:82 src/dialog/account/passcode.vue:4
msgid "Use the following recovery code to access your account when you are unable to generate a valid passcode with your authenticator app or device:"
msgid "Use the following recovery code to access your account when you are unable to generate a verification code with your authenticator app:"
msgstr ""
#: src/options/admin.js:10 src/page/admin/sessions.vue:59

View File

@@ -527,7 +527,7 @@ msgstr "비공개 플래그 변경하기"
#: src/dialog/album/edit.vue:56 src/dialog/photo/edit/files.vue:147
#: src/dialog/service/edit.vue:99 src/dialog/share.vue:96
#: src/dialog/share.vue:115 src/page/albums.vue:563
#: src/page/settings/account.vue:129 src/page/settings/account.vue:147
#: src/page/settings/account.vue:128 src/page/settings/account.vue:146
#: src/page/settings/advanced.vue:53 src/page/settings/general.vue:100
#: src/page/settings/library.vue:52
msgid "Changes successfully saved"
@@ -715,7 +715,7 @@ msgid "Debug Logs"
msgstr "디버그 로그"
#: src/options/admin.js:31 src/page/admin/sessions.vue:60
#: src/page/admin/users.vue:100 src/model/session.js:63 src/model/user.js:234
#: src/page/admin/users.vue:100 src/model/session.js:64 src/model/user.js:234
#: src/options/auth.js:20 src/options/auth.js:21 src/options/auth.js:38
#: src/options/auth.js:39 src/options/options.js:309 src/options/options.js:373
#: src/options/themes.js:492 src/page/places.vue:141
@@ -2186,7 +2186,7 @@ msgid "Scan"
msgstr "스켄"
#: src/dialog/account/passcode.vue:51 src/dialog/account/passcode.vue:4
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated passcode for verification:"
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated verification code:"
msgstr ""
#: src/component/navigation.vue:125
@@ -2259,7 +2259,7 @@ msgstr "서비스 URL"
msgid "Services"
msgstr "서비스"
#: src/model/session.js:100 src/options/auth.js:40
#: src/model/session.js:101 src/options/auth.js:40
msgid "Session"
msgstr "세션"
@@ -2614,7 +2614,8 @@ msgid "Unknown"
msgstr "알 수 없는"
#: src/page/settings/account.vue:40 src/page/settings/account.vue:48
#: src/component/navigation.vue:93
#: src/component/navigation.vue:93 src/page/settings/account.vue:39
#: src/page/settings/account.vue:47
msgid "Unregistered"
msgstr "미등록"
@@ -2643,7 +2644,7 @@ msgstr "색인을 업데이트 중"
msgid "Updating moments"
msgstr "\"나의 순간\" 업데이트 중"
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:140
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:139
msgid "Updating picture…"
msgstr "사진 업데이트 중…"
@@ -2724,7 +2725,7 @@ msgid "Use Presets"
msgstr "사전 설정 사용"
#: src/dialog/account/passcode.vue:82 src/dialog/account/passcode.vue:4
msgid "Use the following recovery code to access your account when you are unable to generate a valid passcode with your authenticator app or device:"
msgid "Use the following recovery code to access your account when you are unable to generate a verification code with your authenticator app:"
msgstr ""
#: src/options/admin.js:10 src/page/admin/sessions.vue:59

View File

@@ -527,7 +527,7 @@ msgstr "گۆڕینی نیشانەنوێنی تایبەت"
#: src/dialog/album/edit.vue:56 src/dialog/photo/edit/files.vue:147
#: src/dialog/service/edit.vue:99 src/dialog/share.vue:96
#: src/dialog/share.vue:115 src/page/albums.vue:563
#: src/page/settings/account.vue:129 src/page/settings/account.vue:147
#: src/page/settings/account.vue:128 src/page/settings/account.vue:146
#: src/page/settings/advanced.vue:53 src/page/settings/general.vue:100
#: src/page/settings/library.vue:52
msgid "Changes successfully saved"
@@ -715,7 +715,7 @@ msgid "Debug Logs"
msgstr "تۆماری هەڵەکان"
#: src/options/admin.js:31 src/page/admin/sessions.vue:60
#: src/page/admin/users.vue:100 src/model/session.js:63 src/model/user.js:234
#: src/page/admin/users.vue:100 src/model/session.js:64 src/model/user.js:234
#: src/options/auth.js:20 src/options/auth.js:21 src/options/auth.js:38
#: src/options/auth.js:39 src/options/options.js:309 src/options/options.js:373
#: src/options/themes.js:492 src/page/places.vue:141
@@ -2186,7 +2186,7 @@ msgid "Scan"
msgstr "پشکنین"
#: src/dialog/account/passcode.vue:51 src/dialog/account/passcode.vue:4
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated passcode for verification:"
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated verification code:"
msgstr ""
#: src/component/navigation.vue:125
@@ -2259,7 +2259,7 @@ msgstr "بەستەری خزمەتگوزاری"
msgid "Services"
msgstr "بەستەری خزمەتگوزاری"
#: src/model/session.js:100 src/options/auth.js:40
#: src/model/session.js:101 src/options/auth.js:40
msgid "Session"
msgstr "Rûniştinî"
@@ -2614,7 +2614,8 @@ msgid "Unknown"
msgstr "نەزانراو"
#: src/page/settings/account.vue:40 src/page/settings/account.vue:48
#: src/component/navigation.vue:93
#: src/component/navigation.vue:93 src/page/settings/account.vue:39
#: src/page/settings/account.vue:47
msgid "Unregistered"
msgstr "Neqeydkirî"
@@ -2643,7 +2644,7 @@ msgstr "نوێکردنەوەی نیشانە"
msgid "Updating moments"
msgstr "نوێکردنەوەی ساتەکان"
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:140
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:139
msgid "Updating picture…"
msgstr "Rojanekirina pêşdîtinan"
@@ -2724,7 +2725,7 @@ msgid "Use Presets"
msgstr "بەکارهێنانی پێش ڕێکخستنەکان"
#: src/dialog/account/passcode.vue:82 src/dialog/account/passcode.vue:4
msgid "Use the following recovery code to access your account when you are unable to generate a valid passcode with your authenticator app or device:"
msgid "Use the following recovery code to access your account when you are unable to generate a verification code with your authenticator app:"
msgstr ""
#: src/options/admin.js:10 src/page/admin/sessions.vue:59

View File

@@ -527,7 +527,7 @@ msgstr "Keisti privačią vėliavą"
#: src/dialog/album/edit.vue:56 src/dialog/photo/edit/files.vue:147
#: src/dialog/service/edit.vue:99 src/dialog/share.vue:96
#: src/dialog/share.vue:115 src/page/albums.vue:563
#: src/page/settings/account.vue:129 src/page/settings/account.vue:147
#: src/page/settings/account.vue:128 src/page/settings/account.vue:146
#: src/page/settings/advanced.vue:53 src/page/settings/general.vue:100
#: src/page/settings/library.vue:52
msgid "Changes successfully saved"
@@ -715,7 +715,7 @@ msgid "Debug Logs"
msgstr "Derinimo žurnalai"
#: src/options/admin.js:31 src/page/admin/sessions.vue:60
#: src/page/admin/users.vue:100 src/model/session.js:63 src/model/user.js:234
#: src/page/admin/users.vue:100 src/model/session.js:64 src/model/user.js:234
#: src/options/auth.js:20 src/options/auth.js:21 src/options/auth.js:38
#: src/options/auth.js:39 src/options/options.js:309 src/options/options.js:373
#: src/options/themes.js:492 src/page/places.vue:141
@@ -2186,7 +2186,7 @@ msgid "Scan"
msgstr "Skenuoti"
#: src/dialog/account/passcode.vue:51 src/dialog/account/passcode.vue:4
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated passcode for verification:"
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated verification code:"
msgstr ""
#: src/component/navigation.vue:125
@@ -2259,7 +2259,7 @@ msgstr "Paslaugos URL"
msgid "Services"
msgstr "Paslaugos URL"
#: src/model/session.js:100 src/options/auth.js:40
#: src/model/session.js:101 src/options/auth.js:40
msgid "Session"
msgstr "Sesija"
@@ -2614,7 +2614,8 @@ msgid "Unknown"
msgstr "Nežinomas"
#: src/page/settings/account.vue:40 src/page/settings/account.vue:48
#: src/component/navigation.vue:93
#: src/component/navigation.vue:93 src/page/settings/account.vue:39
#: src/page/settings/account.vue:47
msgid "Unregistered"
msgstr "Neregistruotas"
@@ -2643,7 +2644,7 @@ msgstr "Indekso atnaujinimas"
msgid "Updating moments"
msgstr "Akimirkų atnaujinimas"
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:140
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:139
msgid "Updating picture…"
msgstr "Peržiūrų atnaujinimas"
@@ -2724,7 +2725,7 @@ msgid "Use Presets"
msgstr "Naudokite išankstinius nustatymus"
#: src/dialog/account/passcode.vue:82 src/dialog/account/passcode.vue:4
msgid "Use the following recovery code to access your account when you are unable to generate a valid passcode with your authenticator app or device:"
msgid "Use the following recovery code to access your account when you are unable to generate a verification code with your authenticator app:"
msgstr ""
#: src/options/admin.js:10 src/page/admin/sessions.vue:59

View File

@@ -527,7 +527,7 @@ msgstr "Tukar petanda peribadi"
#: src/dialog/album/edit.vue:56 src/dialog/photo/edit/files.vue:147
#: src/dialog/service/edit.vue:99 src/dialog/share.vue:96
#: src/dialog/share.vue:115 src/page/albums.vue:563
#: src/page/settings/account.vue:129 src/page/settings/account.vue:147
#: src/page/settings/account.vue:128 src/page/settings/account.vue:146
#: src/page/settings/advanced.vue:53 src/page/settings/general.vue:100
#: src/page/settings/library.vue:52
msgid "Changes successfully saved"
@@ -715,7 +715,7 @@ msgid "Debug Logs"
msgstr "Log Nyahpepijat"
#: src/options/admin.js:31 src/page/admin/sessions.vue:60
#: src/page/admin/users.vue:100 src/model/session.js:63 src/model/user.js:234
#: src/page/admin/users.vue:100 src/model/session.js:64 src/model/user.js:234
#: src/options/auth.js:20 src/options/auth.js:21 src/options/auth.js:38
#: src/options/auth.js:39 src/options/options.js:309 src/options/options.js:373
#: src/options/themes.js:492 src/page/places.vue:141
@@ -2186,7 +2186,7 @@ msgid "Scan"
msgstr "Imbas"
#: src/dialog/account/passcode.vue:51 src/dialog/account/passcode.vue:4
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated passcode for verification:"
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated verification code:"
msgstr ""
#: src/component/navigation.vue:125
@@ -2259,7 +2259,7 @@ msgstr "URL perkhidmatan"
msgid "Services"
msgstr "URL perkhidmatan"
#: src/model/session.js:100 src/options/auth.js:40
#: src/model/session.js:101 src/options/auth.js:40
msgid "Session"
msgstr "Sesi"
@@ -2614,7 +2614,8 @@ msgid "Unknown"
msgstr "Tidak Diketahui"
#: src/page/settings/account.vue:40 src/page/settings/account.vue:48
#: src/component/navigation.vue:93
#: src/component/navigation.vue:93 src/page/settings/account.vue:39
#: src/page/settings/account.vue:47
msgid "Unregistered"
msgstr "Tidak berdaftar"
@@ -2643,7 +2644,7 @@ msgstr "Mengemas kini indeks"
msgid "Updating moments"
msgstr "Mengemas kini detik"
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:140
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:139
msgid "Updating picture…"
msgstr "Mengemas kini pratonton"
@@ -2724,7 +2725,7 @@ msgid "Use Presets"
msgstr "Gunakan Pratetap"
#: src/dialog/account/passcode.vue:82 src/dialog/account/passcode.vue:4
msgid "Use the following recovery code to access your account when you are unable to generate a valid passcode with your authenticator app or device:"
msgid "Use the following recovery code to access your account when you are unable to generate a verification code with your authenticator app:"
msgstr ""
#: src/options/admin.js:10 src/page/admin/sessions.vue:59

View File

@@ -527,7 +527,7 @@ msgstr "Endre private flagg"
#: src/dialog/album/edit.vue:56 src/dialog/photo/edit/files.vue:147
#: src/dialog/service/edit.vue:99 src/dialog/share.vue:96
#: src/dialog/share.vue:115 src/page/albums.vue:563
#: src/page/settings/account.vue:129 src/page/settings/account.vue:147
#: src/page/settings/account.vue:128 src/page/settings/account.vue:146
#: src/page/settings/advanced.vue:53 src/page/settings/general.vue:100
#: src/page/settings/library.vue:52
msgid "Changes successfully saved"
@@ -715,7 +715,7 @@ msgid "Debug Logs"
msgstr "Feilsøkingslogger"
#: src/options/admin.js:31 src/page/admin/sessions.vue:60
#: src/page/admin/users.vue:100 src/model/session.js:63 src/model/user.js:234
#: src/page/admin/users.vue:100 src/model/session.js:64 src/model/user.js:234
#: src/options/auth.js:20 src/options/auth.js:21 src/options/auth.js:38
#: src/options/auth.js:39 src/options/options.js:309 src/options/options.js:373
#: src/options/themes.js:492 src/page/places.vue:141
@@ -2186,7 +2186,7 @@ msgid "Scan"
msgstr "Skann"
#: src/dialog/account/passcode.vue:51 src/dialog/account/passcode.vue:4
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated passcode for verification:"
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated verification code:"
msgstr ""
#: src/component/navigation.vue:125
@@ -2259,7 +2259,7 @@ msgstr "Tjeneste-URL"
msgid "Services"
msgstr "Tjenester"
#: src/model/session.js:100 src/options/auth.js:40
#: src/model/session.js:101 src/options/auth.js:40
msgid "Session"
msgstr "Sesjon"
@@ -2614,7 +2614,8 @@ msgid "Unknown"
msgstr "Ukjent"
#: src/page/settings/account.vue:40 src/page/settings/account.vue:48
#: src/component/navigation.vue:93
#: src/component/navigation.vue:93 src/page/settings/account.vue:39
#: src/page/settings/account.vue:47
msgid "Unregistered"
msgstr "Uregistrert"
@@ -2643,7 +2644,7 @@ msgstr "Oppdaterer indeks"
msgid "Updating moments"
msgstr "Oppdaterer øyeblikk"
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:140
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:139
msgid "Updating picture…"
msgstr "Oppdaterer forhåndsvisninger"
@@ -2724,7 +2725,7 @@ msgid "Use Presets"
msgstr "Bruk Forhåndsinnstillinger"
#: src/dialog/account/passcode.vue:82 src/dialog/account/passcode.vue:4
msgid "Use the following recovery code to access your account when you are unable to generate a valid passcode with your authenticator app or device:"
msgid "Use the following recovery code to access your account when you are unable to generate a verification code with your authenticator app:"
msgstr ""
#: src/options/admin.js:10 src/page/admin/sessions.vue:59

View File

@@ -527,7 +527,7 @@ msgstr "Privé vlag geschakeld"
#: src/dialog/album/edit.vue:56 src/dialog/photo/edit/files.vue:147
#: src/dialog/service/edit.vue:99 src/dialog/share.vue:96
#: src/dialog/share.vue:115 src/page/albums.vue:563
#: src/page/settings/account.vue:129 src/page/settings/account.vue:147
#: src/page/settings/account.vue:128 src/page/settings/account.vue:146
#: src/page/settings/advanced.vue:53 src/page/settings/general.vue:100
#: src/page/settings/library.vue:52
msgid "Changes successfully saved"
@@ -715,7 +715,7 @@ msgid "Debug Logs"
msgstr "Debug-logs"
#: src/options/admin.js:31 src/page/admin/sessions.vue:60
#: src/page/admin/users.vue:100 src/model/session.js:63 src/model/user.js:234
#: src/page/admin/users.vue:100 src/model/session.js:64 src/model/user.js:234
#: src/options/auth.js:20 src/options/auth.js:21 src/options/auth.js:38
#: src/options/auth.js:39 src/options/options.js:309 src/options/options.js:373
#: src/options/themes.js:492 src/page/places.vue:141
@@ -2186,7 +2186,7 @@ msgid "Scan"
msgstr "Scan"
#: src/dialog/account/passcode.vue:51 src/dialog/account/passcode.vue:4
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated passcode for verification:"
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated verification code:"
msgstr ""
#: src/component/navigation.vue:125
@@ -2259,7 +2259,7 @@ msgstr "Service URL"
msgid "Services"
msgstr "Diensten"
#: src/model/session.js:100 src/options/auth.js:40
#: src/model/session.js:101 src/options/auth.js:40
msgid "Session"
msgstr "Sessie"
@@ -2614,7 +2614,8 @@ msgid "Unknown"
msgstr "Onbekend"
#: src/page/settings/account.vue:40 src/page/settings/account.vue:48
#: src/component/navigation.vue:93
#: src/component/navigation.vue:93 src/page/settings/account.vue:39
#: src/page/settings/account.vue:47
msgid "Unregistered"
msgstr "Ongeregistreerd"
@@ -2643,7 +2644,7 @@ msgstr "Bijwerken van de index"
msgid "Updating moments"
msgstr "Momenten van actualisering"
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:140
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:139
msgid "Updating picture…"
msgstr "Foto bijwerken…"
@@ -2724,7 +2725,7 @@ msgid "Use Presets"
msgstr "Gebruik Voorinstellingen"
#: src/dialog/account/passcode.vue:82 src/dialog/account/passcode.vue:4
msgid "Use the following recovery code to access your account when you are unable to generate a valid passcode with your authenticator app or device:"
msgid "Use the following recovery code to access your account when you are unable to generate a verification code with your authenticator app:"
msgstr ""
#: src/options/admin.js:10 src/page/admin/sessions.vue:59

View File

@@ -527,7 +527,7 @@ msgstr "Zmień prywatność"
#: src/dialog/album/edit.vue:56 src/dialog/photo/edit/files.vue:147
#: src/dialog/service/edit.vue:99 src/dialog/share.vue:96
#: src/dialog/share.vue:115 src/page/albums.vue:563
#: src/page/settings/account.vue:129 src/page/settings/account.vue:147
#: src/page/settings/account.vue:128 src/page/settings/account.vue:146
#: src/page/settings/advanced.vue:53 src/page/settings/general.vue:100
#: src/page/settings/library.vue:52
msgid "Changes successfully saved"
@@ -715,7 +715,7 @@ msgid "Debug Logs"
msgstr "Logi debugowania"
#: src/options/admin.js:31 src/page/admin/sessions.vue:60
#: src/page/admin/users.vue:100 src/model/session.js:63 src/model/user.js:234
#: src/page/admin/users.vue:100 src/model/session.js:64 src/model/user.js:234
#: src/options/auth.js:20 src/options/auth.js:21 src/options/auth.js:38
#: src/options/auth.js:39 src/options/options.js:309 src/options/options.js:373
#: src/options/themes.js:492 src/page/places.vue:141
@@ -2186,7 +2186,7 @@ msgid "Scan"
msgstr "Zeskanowany dokument"
#: src/dialog/account/passcode.vue:51 src/dialog/account/passcode.vue:4
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated passcode for verification:"
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated verification code:"
msgstr ""
#: src/component/navigation.vue:125
@@ -2259,7 +2259,7 @@ msgstr "Adres URL do usługi"
msgid "Services"
msgstr "Usługi"
#: src/model/session.js:100 src/options/auth.js:40
#: src/model/session.js:101 src/options/auth.js:40
msgid "Session"
msgstr "Sesja"
@@ -2614,7 +2614,8 @@ msgid "Unknown"
msgstr "Nieznany"
#: src/page/settings/account.vue:40 src/page/settings/account.vue:48
#: src/component/navigation.vue:93
#: src/component/navigation.vue:93 src/page/settings/account.vue:39
#: src/page/settings/account.vue:47
msgid "Unregistered"
msgstr "Niezarejestrowany"
@@ -2643,7 +2644,7 @@ msgstr "Aktualizowanie indeksu"
msgid "Updating moments"
msgstr "Aktualizowanie chwil"
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:140
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:139
msgid "Updating picture…"
msgstr "Aktualizacja zdjęć…"
@@ -2724,7 +2725,7 @@ msgid "Use Presets"
msgstr "Użyj ustawień wstępnych"
#: src/dialog/account/passcode.vue:82 src/dialog/account/passcode.vue:4
msgid "Use the following recovery code to access your account when you are unable to generate a valid passcode with your authenticator app or device:"
msgid "Use the following recovery code to access your account when you are unable to generate a verification code with your authenticator app:"
msgstr ""
#: src/options/admin.js:10 src/page/admin/sessions.vue:59

View File

@@ -527,7 +527,7 @@ msgstr "Mudar marcação como privado"
#: src/dialog/album/edit.vue:56 src/dialog/photo/edit/files.vue:147
#: src/dialog/service/edit.vue:99 src/dialog/share.vue:96
#: src/dialog/share.vue:115 src/page/albums.vue:563
#: src/page/settings/account.vue:129 src/page/settings/account.vue:147
#: src/page/settings/account.vue:128 src/page/settings/account.vue:146
#: src/page/settings/advanced.vue:53 src/page/settings/general.vue:100
#: src/page/settings/library.vue:52
msgid "Changes successfully saved"
@@ -715,7 +715,7 @@ msgid "Debug Logs"
msgstr "Registros de depuração"
#: src/options/admin.js:31 src/page/admin/sessions.vue:60
#: src/page/admin/users.vue:100 src/model/session.js:63 src/model/user.js:234
#: src/page/admin/users.vue:100 src/model/session.js:64 src/model/user.js:234
#: src/options/auth.js:20 src/options/auth.js:21 src/options/auth.js:38
#: src/options/auth.js:39 src/options/options.js:309 src/options/options.js:373
#: src/options/themes.js:492 src/page/places.vue:141
@@ -2186,7 +2186,7 @@ msgid "Scan"
msgstr "Pesquisar"
#: src/dialog/account/passcode.vue:51 src/dialog/account/passcode.vue:4
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated passcode for verification:"
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated verification code:"
msgstr ""
#: src/component/navigation.vue:125
@@ -2259,7 +2259,7 @@ msgstr "URL do serviço"
msgid "Services"
msgstr "Serviços"
#: src/model/session.js:100 src/options/auth.js:40
#: src/model/session.js:101 src/options/auth.js:40
msgid "Session"
msgstr "Sessão"
@@ -2614,7 +2614,8 @@ msgid "Unknown"
msgstr "Desconhecido"
#: src/page/settings/account.vue:40 src/page/settings/account.vue:48
#: src/component/navigation.vue:93
#: src/component/navigation.vue:93 src/page/settings/account.vue:39
#: src/page/settings/account.vue:47
msgid "Unregistered"
msgstr "Não registrado"
@@ -2643,7 +2644,7 @@ msgstr "Atualizando o índice"
msgid "Updating moments"
msgstr "Atualizando momentos"
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:140
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:139
msgid "Updating picture…"
msgstr "Atualizando a imagem.."
@@ -2724,7 +2725,7 @@ msgid "Use Presets"
msgstr "Usar pré-definições"
#: src/dialog/account/passcode.vue:82 src/dialog/account/passcode.vue:4
msgid "Use the following recovery code to access your account when you are unable to generate a valid passcode with your authenticator app or device:"
msgid "Use the following recovery code to access your account when you are unable to generate a verification code with your authenticator app:"
msgstr ""
#: src/options/admin.js:10 src/page/admin/sessions.vue:59

View File

@@ -527,7 +527,7 @@ msgstr "Mudar marcação como privado"
#: src/dialog/album/edit.vue:56 src/dialog/photo/edit/files.vue:147
#: src/dialog/service/edit.vue:99 src/dialog/share.vue:96
#: src/dialog/share.vue:115 src/page/albums.vue:563
#: src/page/settings/account.vue:129 src/page/settings/account.vue:147
#: src/page/settings/account.vue:128 src/page/settings/account.vue:146
#: src/page/settings/advanced.vue:53 src/page/settings/general.vue:100
#: src/page/settings/library.vue:52
msgid "Changes successfully saved"
@@ -715,7 +715,7 @@ msgid "Debug Logs"
msgstr "Registros de depuração"
#: src/options/admin.js:31 src/page/admin/sessions.vue:60
#: src/page/admin/users.vue:100 src/model/session.js:63 src/model/user.js:234
#: src/page/admin/users.vue:100 src/model/session.js:64 src/model/user.js:234
#: src/options/auth.js:20 src/options/auth.js:21 src/options/auth.js:38
#: src/options/auth.js:39 src/options/options.js:309 src/options/options.js:373
#: src/options/themes.js:492 src/page/places.vue:141
@@ -2186,7 +2186,7 @@ msgid "Scan"
msgstr "Buscar"
#: src/dialog/account/passcode.vue:51 src/dialog/account/passcode.vue:4
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated passcode for verification:"
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated verification code:"
msgstr ""
#: src/component/navigation.vue:125
@@ -2259,7 +2259,7 @@ msgstr "URL do serviço"
msgid "Services"
msgstr "Serviços"
#: src/model/session.js:100 src/options/auth.js:40
#: src/model/session.js:101 src/options/auth.js:40
msgid "Session"
msgstr "Sessão"
@@ -2614,7 +2614,8 @@ msgid "Unknown"
msgstr "Desconhecido"
#: src/page/settings/account.vue:40 src/page/settings/account.vue:48
#: src/component/navigation.vue:93
#: src/component/navigation.vue:93 src/page/settings/account.vue:39
#: src/page/settings/account.vue:47
msgid "Unregistered"
msgstr "Não registrado"
@@ -2643,7 +2644,7 @@ msgstr "Atualizando índice"
msgid "Updating moments"
msgstr "Atualizando momentos"
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:140
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:139
msgid "Updating picture…"
msgstr "Atualizando a imagem.."
@@ -2724,7 +2725,7 @@ msgid "Use Presets"
msgstr "Usar pré-definições"
#: src/dialog/account/passcode.vue:82 src/dialog/account/passcode.vue:4
msgid "Use the following recovery code to access your account when you are unable to generate a valid passcode with your authenticator app or device:"
msgid "Use the following recovery code to access your account when you are unable to generate a verification code with your authenticator app:"
msgstr ""
#: src/options/admin.js:10 src/page/admin/sessions.vue:59

View File

@@ -527,7 +527,7 @@ msgstr "Schimbarea steagului privat"
#: src/dialog/album/edit.vue:56 src/dialog/photo/edit/files.vue:147
#: src/dialog/service/edit.vue:99 src/dialog/share.vue:96
#: src/dialog/share.vue:115 src/page/albums.vue:563
#: src/page/settings/account.vue:129 src/page/settings/account.vue:147
#: src/page/settings/account.vue:128 src/page/settings/account.vue:146
#: src/page/settings/advanced.vue:53 src/page/settings/general.vue:100
#: src/page/settings/library.vue:52
msgid "Changes successfully saved"
@@ -715,7 +715,7 @@ msgid "Debug Logs"
msgstr "Jurnalele de depanare"
#: src/options/admin.js:31 src/page/admin/sessions.vue:60
#: src/page/admin/users.vue:100 src/model/session.js:63 src/model/user.js:234
#: src/page/admin/users.vue:100 src/model/session.js:64 src/model/user.js:234
#: src/options/auth.js:20 src/options/auth.js:21 src/options/auth.js:38
#: src/options/auth.js:39 src/options/options.js:309 src/options/options.js:373
#: src/options/themes.js:492 src/page/places.vue:141
@@ -2186,7 +2186,7 @@ msgid "Scan"
msgstr "Scanare"
#: src/dialog/account/passcode.vue:51 src/dialog/account/passcode.vue:4
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated passcode for verification:"
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated verification code:"
msgstr ""
#: src/component/navigation.vue:125
@@ -2259,7 +2259,7 @@ msgstr "URL de serviciu"
msgid "Services"
msgstr "Servicii"
#: src/model/session.js:100 src/options/auth.js:40
#: src/model/session.js:101 src/options/auth.js:40
msgid "Session"
msgstr "Sesiunea"
@@ -2614,7 +2614,8 @@ msgid "Unknown"
msgstr "Necunoscut"
#: src/page/settings/account.vue:40 src/page/settings/account.vue:48
#: src/component/navigation.vue:93
#: src/component/navigation.vue:93 src/page/settings/account.vue:39
#: src/page/settings/account.vue:47
msgid "Unregistered"
msgstr "Neînregistrat"
@@ -2643,7 +2644,7 @@ msgstr "Actualizarea index-ului"
msgid "Updating moments"
msgstr "Actualizarea momentelor"
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:140
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:139
msgid "Updating picture…"
msgstr "Actualizarea imaginii…"
@@ -2724,7 +2725,7 @@ msgid "Use Presets"
msgstr "Utilizați presetări"
#: src/dialog/account/passcode.vue:82 src/dialog/account/passcode.vue:4
msgid "Use the following recovery code to access your account when you are unable to generate a valid passcode with your authenticator app or device:"
msgid "Use the following recovery code to access your account when you are unable to generate a verification code with your authenticator app:"
msgstr ""
#: src/options/admin.js:10 src/page/admin/sessions.vue:59

View File

@@ -527,7 +527,7 @@ msgstr "Изменить флаг приватности"
#: src/dialog/album/edit.vue:56 src/dialog/photo/edit/files.vue:147
#: src/dialog/service/edit.vue:99 src/dialog/share.vue:96
#: src/dialog/share.vue:115 src/page/albums.vue:563
#: src/page/settings/account.vue:129 src/page/settings/account.vue:147
#: src/page/settings/account.vue:128 src/page/settings/account.vue:146
#: src/page/settings/advanced.vue:53 src/page/settings/general.vue:100
#: src/page/settings/library.vue:52
msgid "Changes successfully saved"
@@ -715,7 +715,7 @@ msgid "Debug Logs"
msgstr "Отладочные Логи"
#: src/options/admin.js:31 src/page/admin/sessions.vue:60
#: src/page/admin/users.vue:100 src/model/session.js:63 src/model/user.js:234
#: src/page/admin/users.vue:100 src/model/session.js:64 src/model/user.js:234
#: src/options/auth.js:20 src/options/auth.js:21 src/options/auth.js:38
#: src/options/auth.js:39 src/options/options.js:309 src/options/options.js:373
#: src/options/themes.js:492 src/page/places.vue:141
@@ -2186,7 +2186,7 @@ msgid "Scan"
msgstr "Сканировать"
#: src/dialog/account/passcode.vue:51 src/dialog/account/passcode.vue:4
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated passcode for verification:"
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated verification code:"
msgstr ""
#: src/component/navigation.vue:125
@@ -2259,7 +2259,7 @@ msgstr "URL сервиса"
msgid "Services"
msgstr "Сервисы"
#: src/model/session.js:100 src/options/auth.js:40
#: src/model/session.js:101 src/options/auth.js:40
msgid "Session"
msgstr "Сессия"
@@ -2614,7 +2614,8 @@ msgid "Unknown"
msgstr "Неизвестно"
#: src/page/settings/account.vue:40 src/page/settings/account.vue:48
#: src/component/navigation.vue:93
#: src/component/navigation.vue:93 src/page/settings/account.vue:39
#: src/page/settings/account.vue:47
msgid "Unregistered"
msgstr "Незарегистрированный"
@@ -2643,7 +2644,7 @@ msgstr "Обновление индекса"
msgid "Updating moments"
msgstr "Обновление моментов"
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:140
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:139
msgid "Updating picture…"
msgstr "Обновление изображения…"
@@ -2724,7 +2725,7 @@ msgid "Use Presets"
msgstr "Использовать предустановки"
#: src/dialog/account/passcode.vue:82 src/dialog/account/passcode.vue:4
msgid "Use the following recovery code to access your account when you are unable to generate a valid passcode with your authenticator app or device:"
msgid "Use the following recovery code to access your account when you are unable to generate a verification code with your authenticator app:"
msgstr ""
#: src/options/admin.js:10 src/page/admin/sessions.vue:59

View File

@@ -527,7 +527,7 @@ msgstr "Zmeniť privátne označenie"
#: src/dialog/album/edit.vue:56 src/dialog/photo/edit/files.vue:147
#: src/dialog/service/edit.vue:99 src/dialog/share.vue:96
#: src/dialog/share.vue:115 src/page/albums.vue:563
#: src/page/settings/account.vue:129 src/page/settings/account.vue:147
#: src/page/settings/account.vue:128 src/page/settings/account.vue:146
#: src/page/settings/advanced.vue:53 src/page/settings/general.vue:100
#: src/page/settings/library.vue:52
msgid "Changes successfully saved"
@@ -715,7 +715,7 @@ msgid "Debug Logs"
msgstr "Denníky ladenia"
#: src/options/admin.js:31 src/page/admin/sessions.vue:60
#: src/page/admin/users.vue:100 src/model/session.js:63 src/model/user.js:234
#: src/page/admin/users.vue:100 src/model/session.js:64 src/model/user.js:234
#: src/options/auth.js:20 src/options/auth.js:21 src/options/auth.js:38
#: src/options/auth.js:39 src/options/options.js:309 src/options/options.js:373
#: src/options/themes.js:492 src/page/places.vue:141
@@ -2186,7 +2186,7 @@ msgid "Scan"
msgstr "Sken"
#: src/dialog/account/passcode.vue:51 src/dialog/account/passcode.vue:4
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated passcode for verification:"
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated verification code:"
msgstr ""
#: src/component/navigation.vue:125
@@ -2259,7 +2259,7 @@ msgstr "URL Služby"
msgid "Services"
msgstr "Služby"
#: src/model/session.js:100 src/options/auth.js:40
#: src/model/session.js:101 src/options/auth.js:40
msgid "Session"
msgstr "Zasadnutie"
@@ -2614,7 +2614,8 @@ msgid "Unknown"
msgstr "Neznámy"
#: src/page/settings/account.vue:40 src/page/settings/account.vue:48
#: src/component/navigation.vue:93
#: src/component/navigation.vue:93 src/page/settings/account.vue:39
#: src/page/settings/account.vue:47
msgid "Unregistered"
msgstr "Neregistrovaný"
@@ -2643,7 +2644,7 @@ msgstr "Aktualizuje sa index"
msgid "Updating moments"
msgstr "Aktualizujú sa momenty"
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:140
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:139
msgid "Updating picture…"
msgstr "Aktualizácia obrázku.."
@@ -2724,7 +2725,7 @@ msgid "Use Presets"
msgstr "Použiť predvoľbu"
#: src/dialog/account/passcode.vue:82 src/dialog/account/passcode.vue:4
msgid "Use the following recovery code to access your account when you are unable to generate a valid passcode with your authenticator app or device:"
msgid "Use the following recovery code to access your account when you are unable to generate a verification code with your authenticator app:"
msgstr ""
#: src/options/admin.js:10 src/page/admin/sessions.vue:59

View File

@@ -524,7 +524,7 @@ msgstr "Sprememba zasebne zastave"
#: src/dialog/album/edit.vue:56 src/dialog/photo/edit/files.vue:147
#: src/dialog/service/edit.vue:99 src/dialog/share.vue:96
#: src/dialog/share.vue:115 src/page/albums.vue:563
#: src/page/settings/account.vue:129 src/page/settings/account.vue:147
#: src/page/settings/account.vue:128 src/page/settings/account.vue:146
#: src/page/settings/advanced.vue:53 src/page/settings/general.vue:100
#: src/page/settings/library.vue:52
msgid "Changes successfully saved"
@@ -712,7 +712,7 @@ msgid "Debug Logs"
msgstr "Dnevniki za odpravljanje napak"
#: src/options/admin.js:31 src/page/admin/sessions.vue:60
#: src/page/admin/users.vue:100 src/model/session.js:63 src/model/user.js:234
#: src/page/admin/users.vue:100 src/model/session.js:64 src/model/user.js:234
#: src/options/auth.js:20 src/options/auth.js:21 src/options/auth.js:38
#: src/options/auth.js:39 src/options/options.js:309 src/options/options.js:373
#: src/options/themes.js:492 src/page/places.vue:141
@@ -2183,7 +2183,7 @@ msgid "Scan"
msgstr "Skeniranje"
#: src/dialog/account/passcode.vue:51 src/dialog/account/passcode.vue:4
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated passcode for verification:"
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated verification code:"
msgstr ""
#: src/component/navigation.vue:125
@@ -2256,7 +2256,7 @@ msgstr "URL storitve"
msgid "Services"
msgstr "URL storitve"
#: src/model/session.js:100 src/options/auth.js:40
#: src/model/session.js:101 src/options/auth.js:40
msgid "Session"
msgstr "Seja"
@@ -2611,7 +2611,8 @@ msgid "Unknown"
msgstr "Neznano"
#: src/page/settings/account.vue:40 src/page/settings/account.vue:48
#: src/component/navigation.vue:93
#: src/component/navigation.vue:93 src/page/settings/account.vue:39
#: src/page/settings/account.vue:47
msgid "Unregistered"
msgstr "Neregistrirani"
@@ -2640,7 +2641,7 @@ msgstr "Posodabljanje indeksa"
msgid "Updating moments"
msgstr "Posodabljanje trenutkov"
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:140
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:139
msgid "Updating picture…"
msgstr "Posodabljanje predogledov"
@@ -2721,7 +2722,7 @@ msgid "Use Presets"
msgstr "Uporaba prednastavitev"
#: src/dialog/account/passcode.vue:82 src/dialog/account/passcode.vue:4
msgid "Use the following recovery code to access your account when you are unable to generate a valid passcode with your authenticator app or device:"
msgid "Use the following recovery code to access your account when you are unable to generate a verification code with your authenticator app:"
msgstr ""
#: src/options/admin.js:10 src/page/admin/sessions.vue:59

View File

@@ -527,7 +527,7 @@ msgstr "Ändra den privata flaggan"
#: src/dialog/album/edit.vue:56 src/dialog/photo/edit/files.vue:147
#: src/dialog/service/edit.vue:99 src/dialog/share.vue:96
#: src/dialog/share.vue:115 src/page/albums.vue:563
#: src/page/settings/account.vue:129 src/page/settings/account.vue:147
#: src/page/settings/account.vue:128 src/page/settings/account.vue:146
#: src/page/settings/advanced.vue:53 src/page/settings/general.vue:100
#: src/page/settings/library.vue:52
msgid "Changes successfully saved"
@@ -715,7 +715,7 @@ msgid "Debug Logs"
msgstr "Felsökningsloggar"
#: src/options/admin.js:31 src/page/admin/sessions.vue:60
#: src/page/admin/users.vue:100 src/model/session.js:63 src/model/user.js:234
#: src/page/admin/users.vue:100 src/model/session.js:64 src/model/user.js:234
#: src/options/auth.js:20 src/options/auth.js:21 src/options/auth.js:38
#: src/options/auth.js:39 src/options/options.js:309 src/options/options.js:373
#: src/options/themes.js:492 src/page/places.vue:141
@@ -2186,7 +2186,7 @@ msgid "Scan"
msgstr "Skanna"
#: src/dialog/account/passcode.vue:51 src/dialog/account/passcode.vue:4
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated passcode for verification:"
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated verification code:"
msgstr ""
#: src/component/navigation.vue:125
@@ -2259,7 +2259,7 @@ msgstr "Tjänstens URL"
msgid "Services"
msgstr "Tjänstens URL"
#: src/model/session.js:100 src/options/auth.js:40
#: src/model/session.js:101 src/options/auth.js:40
msgid "Session"
msgstr "Sammanträde"
@@ -2614,7 +2614,8 @@ msgid "Unknown"
msgstr "Okänd"
#: src/page/settings/account.vue:40 src/page/settings/account.vue:48
#: src/component/navigation.vue:93
#: src/component/navigation.vue:93 src/page/settings/account.vue:39
#: src/page/settings/account.vue:47
msgid "Unregistered"
msgstr "Oregistrerad"
@@ -2643,7 +2644,7 @@ msgstr "Uppdatering av index"
msgid "Updating moments"
msgstr "Uppdatering av ögonblick"
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:140
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:139
msgid "Updating picture…"
msgstr "Uppdatering av förhandsgranskningar…"
@@ -2724,7 +2725,7 @@ msgid "Use Presets"
msgstr "Använd förinställningar"
#: src/dialog/account/passcode.vue:82 src/dialog/account/passcode.vue:4
msgid "Use the following recovery code to access your account when you are unable to generate a valid passcode with your authenticator app or device:"
msgid "Use the following recovery code to access your account when you are unable to generate a verification code with your authenticator app:"
msgstr ""
#: src/options/admin.js:10 src/page/admin/sessions.vue:59

View File

@@ -527,7 +527,7 @@ msgstr "เปลี่ยนแฟล็กส่วนตัว"
#: src/dialog/album/edit.vue:56 src/dialog/photo/edit/files.vue:147
#: src/dialog/service/edit.vue:99 src/dialog/share.vue:96
#: src/dialog/share.vue:115 src/page/albums.vue:563
#: src/page/settings/account.vue:129 src/page/settings/account.vue:147
#: src/page/settings/account.vue:128 src/page/settings/account.vue:146
#: src/page/settings/advanced.vue:53 src/page/settings/general.vue:100
#: src/page/settings/library.vue:52
msgid "Changes successfully saved"
@@ -715,7 +715,7 @@ msgid "Debug Logs"
msgstr "บันทึกการดีบัก"
#: src/options/admin.js:31 src/page/admin/sessions.vue:60
#: src/page/admin/users.vue:100 src/model/session.js:63 src/model/user.js:234
#: src/page/admin/users.vue:100 src/model/session.js:64 src/model/user.js:234
#: src/options/auth.js:20 src/options/auth.js:21 src/options/auth.js:38
#: src/options/auth.js:39 src/options/options.js:309 src/options/options.js:373
#: src/options/themes.js:492 src/page/places.vue:141
@@ -2186,7 +2186,7 @@ msgid "Scan"
msgstr "สแกน"
#: src/dialog/account/passcode.vue:51 src/dialog/account/passcode.vue:4
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated passcode for verification:"
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated verification code:"
msgstr ""
#: src/component/navigation.vue:125
@@ -2259,7 +2259,7 @@ msgstr "URL บริการ"
msgid "Services"
msgstr "URL บริการ"
#: src/model/session.js:100 src/options/auth.js:40
#: src/model/session.js:101 src/options/auth.js:40
msgid "Session"
msgstr "การประชุม"
@@ -2614,7 +2614,8 @@ msgid "Unknown"
msgstr "ไม่รู้จัก"
#: src/page/settings/account.vue:40 src/page/settings/account.vue:48
#: src/component/navigation.vue:93
#: src/component/navigation.vue:93 src/page/settings/account.vue:39
#: src/page/settings/account.vue:47
msgid "Unregistered"
msgstr "ไม่ได้ลงทะเบียน"
@@ -2643,7 +2644,7 @@ msgstr "กำลังอัปเดตดัชนี"
msgid "Updating moments"
msgstr "กำลังอัปเดตช่วงเวลา"
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:140
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:139
msgid "Updating picture…"
msgstr "กำลังอัปเดตตัวอย่าง"
@@ -2724,7 +2725,7 @@ msgid "Use Presets"
msgstr "ใช้พรีเซ็ต"
#: src/dialog/account/passcode.vue:82 src/dialog/account/passcode.vue:4
msgid "Use the following recovery code to access your account when you are unable to generate a valid passcode with your authenticator app or device:"
msgid "Use the following recovery code to access your account when you are unable to generate a verification code with your authenticator app:"
msgstr ""
#: src/options/admin.js:10 src/page/admin/sessions.vue:59

View File

@@ -527,7 +527,7 @@ msgstr "Özel bayrağı değiştir"
#: src/dialog/album/edit.vue:56 src/dialog/photo/edit/files.vue:147
#: src/dialog/service/edit.vue:99 src/dialog/share.vue:96
#: src/dialog/share.vue:115 src/page/albums.vue:563
#: src/page/settings/account.vue:129 src/page/settings/account.vue:147
#: src/page/settings/account.vue:128 src/page/settings/account.vue:146
#: src/page/settings/advanced.vue:53 src/page/settings/general.vue:100
#: src/page/settings/library.vue:52
msgid "Changes successfully saved"
@@ -715,7 +715,7 @@ msgid "Debug Logs"
msgstr "Hata Kayıtları"
#: src/options/admin.js:31 src/page/admin/sessions.vue:60
#: src/page/admin/users.vue:100 src/model/session.js:63 src/model/user.js:234
#: src/page/admin/users.vue:100 src/model/session.js:64 src/model/user.js:234
#: src/options/auth.js:20 src/options/auth.js:21 src/options/auth.js:38
#: src/options/auth.js:39 src/options/options.js:309 src/options/options.js:373
#: src/options/themes.js:492 src/page/places.vue:141
@@ -2186,7 +2186,7 @@ msgid "Scan"
msgstr "Tara"
#: src/dialog/account/passcode.vue:51 src/dialog/account/passcode.vue:4
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated passcode for verification:"
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated verification code:"
msgstr ""
#: src/component/navigation.vue:125
@@ -2259,7 +2259,7 @@ msgstr "Hizmet URL'si"
msgid "Services"
msgstr "Hizmetler"
#: src/model/session.js:100 src/options/auth.js:40
#: src/model/session.js:101 src/options/auth.js:40
msgid "Session"
msgstr "Oturum"
@@ -2614,7 +2614,8 @@ msgid "Unknown"
msgstr "Bilinmeyen"
#: src/page/settings/account.vue:40 src/page/settings/account.vue:48
#: src/component/navigation.vue:93
#: src/component/navigation.vue:93 src/page/settings/account.vue:39
#: src/page/settings/account.vue:47
msgid "Unregistered"
msgstr "Kaydedilmemiş"
@@ -2643,7 +2644,7 @@ msgstr "Dizin güncelleniyor"
msgid "Updating moments"
msgstr "Anların güncellenmesi"
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:140
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:139
msgid "Updating picture…"
msgstr "Resim güncelleniyor.."
@@ -2724,7 +2725,7 @@ msgid "Use Presets"
msgstr "Ön Ayarları Kullan"
#: src/dialog/account/passcode.vue:82 src/dialog/account/passcode.vue:4
msgid "Use the following recovery code to access your account when you are unable to generate a valid passcode with your authenticator app or device:"
msgid "Use the following recovery code to access your account when you are unable to generate a verification code with your authenticator app:"
msgstr ""
#: src/options/admin.js:10 src/page/admin/sessions.vue:59

View File

@@ -616,8 +616,8 @@ msgstr ""
#: src/dialog/share.vue:96
#: src/dialog/share.vue:115
#: src/page/albums.vue:563
#: src/page/settings/account.vue:129
#: src/page/settings/account.vue:147
#: src/page/settings/account.vue:128
#: src/page/settings/account.vue:146
#: src/page/settings/advanced.vue:53
#: src/page/settings/general.vue:100
#: src/page/settings/library.vue:52
@@ -842,7 +842,7 @@ msgstr ""
#: src/options/admin.js:31
#: src/page/admin/sessions.vue:60
#: src/page/admin/users.vue:100
#: src/model/session.js:63
#: src/model/session.js:64
#: src/model/user.js:234
#: src/options/auth.js:20
#: src/options/auth.js:21
@@ -2606,7 +2606,7 @@ msgstr ""
#: src/dialog/account/passcode.vue:51
#: src/dialog/account/passcode.vue:4
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated passcode for verification:"
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated verification code:"
msgstr ""
#: src/component/navigation.vue:125
@@ -2690,7 +2690,7 @@ msgstr ""
msgid "Services"
msgstr ""
#: src/model/session.js:100
#: src/model/session.js:101
#: src/options/auth.js:40
msgid "Session"
msgstr ""
@@ -3108,8 +3108,8 @@ msgstr ""
#: src/page/settings/account.vue:40
#: src/page/settings/account.vue:48
#: src/component/navigation.vue:93
#: src/page/settings/account.vue:40
#: src/page/settings/account.vue:48
#: src/page/settings/account.vue:39
#: src/page/settings/account.vue:47
msgid "Unregistered"
msgstr ""
@@ -3142,7 +3142,7 @@ msgid "Updating moments"
msgstr ""
#: src/dialog/admin/users/edit.vue:157
#: src/page/settings/account.vue:140
#: src/page/settings/account.vue:139
msgid "Updating picture…"
msgstr ""
@@ -3237,7 +3237,7 @@ msgstr ""
#: src/dialog/account/passcode.vue:82
#: src/dialog/account/passcode.vue:4
msgid "Use the following recovery code to access your account when you are unable to generate a valid passcode with your authenticator app or device:"
msgid "Use the following recovery code to access your account when you are unable to generate a verification code with your authenticator app:"
msgstr ""
#: src/options/admin.js:10

View File

@@ -527,7 +527,7 @@ msgstr "Змінити означку приватності"
#: src/dialog/album/edit.vue:56 src/dialog/photo/edit/files.vue:147
#: src/dialog/service/edit.vue:99 src/dialog/share.vue:96
#: src/dialog/share.vue:115 src/page/albums.vue:563
#: src/page/settings/account.vue:129 src/page/settings/account.vue:147
#: src/page/settings/account.vue:128 src/page/settings/account.vue:146
#: src/page/settings/advanced.vue:53 src/page/settings/general.vue:100
#: src/page/settings/library.vue:52
msgid "Changes successfully saved"
@@ -715,7 +715,7 @@ msgid "Debug Logs"
msgstr "Журнали налагодження"
#: src/options/admin.js:31 src/page/admin/sessions.vue:60
#: src/page/admin/users.vue:100 src/model/session.js:63 src/model/user.js:234
#: src/page/admin/users.vue:100 src/model/session.js:64 src/model/user.js:234
#: src/options/auth.js:20 src/options/auth.js:21 src/options/auth.js:38
#: src/options/auth.js:39 src/options/options.js:309 src/options/options.js:373
#: src/options/themes.js:492 src/page/places.vue:141
@@ -2186,7 +2186,7 @@ msgid "Scan"
msgstr "Сканувати"
#: src/dialog/account/passcode.vue:51 src/dialog/account/passcode.vue:4
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated passcode for verification:"
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated verification code:"
msgstr ""
#: src/component/navigation.vue:125
@@ -2259,7 +2259,7 @@ msgstr "URL служби"
msgid "Services"
msgstr "Послуги"
#: src/model/session.js:100 src/options/auth.js:40
#: src/model/session.js:101 src/options/auth.js:40
msgid "Session"
msgstr "Сесія"
@@ -2614,7 +2614,8 @@ msgid "Unknown"
msgstr "Невідомий"
#: src/page/settings/account.vue:40 src/page/settings/account.vue:48
#: src/component/navigation.vue:93
#: src/component/navigation.vue:93 src/page/settings/account.vue:39
#: src/page/settings/account.vue:47
msgid "Unregistered"
msgstr "Незареєстрований"
@@ -2643,7 +2644,7 @@ msgstr "Оновлення індексу"
msgid "Updating moments"
msgstr "Оновлення моментів"
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:140
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:139
msgid "Updating picture…"
msgstr "Оновлення зображення…"
@@ -2724,7 +2725,7 @@ msgid "Use Presets"
msgstr "Використовуйте попередні налаштування"
#: src/dialog/account/passcode.vue:82 src/dialog/account/passcode.vue:4
msgid "Use the following recovery code to access your account when you are unable to generate a valid passcode with your authenticator app or device:"
msgid "Use the following recovery code to access your account when you are unable to generate a verification code with your authenticator app:"
msgstr ""
#: src/options/admin.js:10 src/page/admin/sessions.vue:59

View File

@@ -524,7 +524,7 @@ msgstr "Thay đổi cờ riêng tư"
#: src/dialog/album/edit.vue:56 src/dialog/photo/edit/files.vue:147
#: src/dialog/service/edit.vue:99 src/dialog/share.vue:96
#: src/dialog/share.vue:115 src/page/albums.vue:563
#: src/page/settings/account.vue:129 src/page/settings/account.vue:147
#: src/page/settings/account.vue:128 src/page/settings/account.vue:146
#: src/page/settings/advanced.vue:53 src/page/settings/general.vue:100
#: src/page/settings/library.vue:52
msgid "Changes successfully saved"
@@ -712,7 +712,7 @@ msgid "Debug Logs"
msgstr "Nhật ký gỡ lỗi"
#: src/options/admin.js:31 src/page/admin/sessions.vue:60
#: src/page/admin/users.vue:100 src/model/session.js:63 src/model/user.js:234
#: src/page/admin/users.vue:100 src/model/session.js:64 src/model/user.js:234
#: src/options/auth.js:20 src/options/auth.js:21 src/options/auth.js:38
#: src/options/auth.js:39 src/options/options.js:309 src/options/options.js:373
#: src/options/themes.js:492 src/page/places.vue:141
@@ -2183,7 +2183,7 @@ msgid "Scan"
msgstr "Quét"
#: src/dialog/account/passcode.vue:51 src/dialog/account/passcode.vue:4
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated passcode for verification:"
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated verification code:"
msgstr ""
#: src/component/navigation.vue:125
@@ -2256,7 +2256,7 @@ msgstr "URL dịch vụ"
msgid "Services"
msgstr "Dịch vụ"
#: src/model/session.js:100 src/options/auth.js:40
#: src/model/session.js:101 src/options/auth.js:40
msgid "Session"
msgstr "Phiên họp"
@@ -2611,7 +2611,8 @@ msgid "Unknown"
msgstr "không xác định"
#: src/page/settings/account.vue:40 src/page/settings/account.vue:48
#: src/component/navigation.vue:93
#: src/component/navigation.vue:93 src/page/settings/account.vue:39
#: src/page/settings/account.vue:47
msgid "Unregistered"
msgstr "Chưa đăng ký"
@@ -2640,7 +2641,7 @@ msgstr "Đang cập nhật chỉ mục"
msgid "Updating moments"
msgstr "Cập nhật khoảnh khắc"
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:140
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:139
msgid "Updating picture…"
msgstr "Đang cập nhật hình ảnh…"
@@ -2721,7 +2722,7 @@ msgid "Use Presets"
msgstr "Sử dụng cài đặt trước"
#: src/dialog/account/passcode.vue:82 src/dialog/account/passcode.vue:4
msgid "Use the following recovery code to access your account when you are unable to generate a valid passcode with your authenticator app or device:"
msgid "Use the following recovery code to access your account when you are unable to generate a verification code with your authenticator app:"
msgstr ""
#: src/options/admin.js:10 src/page/admin/sessions.vue:59

View File

@@ -527,7 +527,7 @@ msgstr "更改私有标记"
#: src/dialog/album/edit.vue:56 src/dialog/photo/edit/files.vue:147
#: src/dialog/service/edit.vue:99 src/dialog/share.vue:96
#: src/dialog/share.vue:115 src/page/albums.vue:563
#: src/page/settings/account.vue:129 src/page/settings/account.vue:147
#: src/page/settings/account.vue:128 src/page/settings/account.vue:146
#: src/page/settings/advanced.vue:53 src/page/settings/general.vue:100
#: src/page/settings/library.vue:52
msgid "Changes successfully saved"
@@ -715,7 +715,7 @@ msgid "Debug Logs"
msgstr "调试日志"
#: src/options/admin.js:31 src/page/admin/sessions.vue:60
#: src/page/admin/users.vue:100 src/model/session.js:63 src/model/user.js:234
#: src/page/admin/users.vue:100 src/model/session.js:64 src/model/user.js:234
#: src/options/auth.js:20 src/options/auth.js:21 src/options/auth.js:38
#: src/options/auth.js:39 src/options/options.js:309 src/options/options.js:373
#: src/options/themes.js:492 src/page/places.vue:141
@@ -2188,7 +2188,7 @@ msgid "Scan"
msgstr "扫描"
#: src/dialog/account/passcode.vue:51 src/dialog/account/passcode.vue:4
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated passcode for verification:"
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated verification code:"
msgstr ""
#: src/component/navigation.vue:125
@@ -2261,7 +2261,7 @@ msgstr "服务 URL"
msgid "Services"
msgstr "服务"
#: src/model/session.js:100 src/options/auth.js:40
#: src/model/session.js:101 src/options/auth.js:40
msgid "Session"
msgstr "会议"
@@ -2616,7 +2616,8 @@ msgid "Unknown"
msgstr "未知"
#: src/page/settings/account.vue:40 src/page/settings/account.vue:48
#: src/component/navigation.vue:93
#: src/component/navigation.vue:93 src/page/settings/account.vue:39
#: src/page/settings/account.vue:47
msgid "Unregistered"
msgstr "未注册的"
@@ -2645,7 +2646,7 @@ msgstr "更新索引"
msgid "Updating moments"
msgstr "更新时刻"
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:140
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:139
msgid "Updating picture…"
msgstr "更新图片…"
@@ -2726,7 +2727,7 @@ msgid "Use Presets"
msgstr "使用预设"
#: src/dialog/account/passcode.vue:82 src/dialog/account/passcode.vue:4
msgid "Use the following recovery code to access your account when you are unable to generate a valid passcode with your authenticator app or device:"
msgid "Use the following recovery code to access your account when you are unable to generate a verification code with your authenticator app:"
msgstr ""
#: src/options/admin.js:10 src/page/admin/sessions.vue:59

View File

@@ -527,7 +527,7 @@ msgstr "更改私人選項"
#: src/dialog/album/edit.vue:56 src/dialog/photo/edit/files.vue:147
#: src/dialog/service/edit.vue:99 src/dialog/share.vue:96
#: src/dialog/share.vue:115 src/page/albums.vue:563
#: src/page/settings/account.vue:129 src/page/settings/account.vue:147
#: src/page/settings/account.vue:128 src/page/settings/account.vue:146
#: src/page/settings/advanced.vue:53 src/page/settings/general.vue:100
#: src/page/settings/library.vue:52
msgid "Changes successfully saved"
@@ -715,7 +715,7 @@ msgid "Debug Logs"
msgstr "除錯紀錄"
#: src/options/admin.js:31 src/page/admin/sessions.vue:60
#: src/page/admin/users.vue:100 src/model/session.js:63 src/model/user.js:234
#: src/page/admin/users.vue:100 src/model/session.js:64 src/model/user.js:234
#: src/options/auth.js:20 src/options/auth.js:21 src/options/auth.js:38
#: src/options/auth.js:39 src/options/options.js:309 src/options/options.js:373
#: src/options/themes.js:492 src/page/places.vue:141
@@ -2186,7 +2186,7 @@ msgid "Scan"
msgstr "掃描"
#: src/dialog/account/passcode.vue:51 src/dialog/account/passcode.vue:4
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated passcode for verification:"
msgid "Scan the QR code with your authenticator app or use the setup key shown below and then enter the generated verification code:"
msgstr ""
#: src/component/navigation.vue:125
@@ -2259,7 +2259,7 @@ msgstr "服務 URL"
msgid "Services"
msgstr "服務"
#: src/model/session.js:100 src/options/auth.js:40
#: src/model/session.js:101 src/options/auth.js:40
msgid "Session"
msgstr "工作階段"
@@ -2614,7 +2614,8 @@ msgid "Unknown"
msgstr "未知"
#: src/page/settings/account.vue:40 src/page/settings/account.vue:48
#: src/component/navigation.vue:93
#: src/component/navigation.vue:93 src/page/settings/account.vue:39
#: src/page/settings/account.vue:47
msgid "Unregistered"
msgstr "未註冊"
@@ -2643,7 +2644,7 @@ msgstr "更新索引"
msgid "Updating moments"
msgstr "更新時刻"
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:140
#: src/dialog/admin/users/edit.vue:157 src/page/settings/account.vue:139
msgid "Updating picture…"
msgstr "正在更新圖片…"
@@ -2724,7 +2725,7 @@ msgid "Use Presets"
msgstr "使用預設"
#: src/dialog/account/passcode.vue:82 src/dialog/account/passcode.vue:4
msgid "Use the following recovery code to access your account when you are unable to generate a valid passcode with your authenticator app or device:"
msgid "Use the following recovery code to access your account when you are unable to generate a verification code with your authenticator app:"
msgstr ""
#: src/options/admin.js:10 src/page/admin/sessions.vue:59

View File

@@ -41,11 +41,12 @@ export class Session extends RestModel {
UserAgent: "",
ClientUID: "",
ClientName: "",
AuthID: "",
AuthProvider: "",
AuthMethod: "",
AuthDomain: "",
AuthScope: "",
AuthID: "",
GrantType: "",
LastActive: 0,
Expires: 0,
Timeout: 0,

View File

@@ -269,10 +269,10 @@ export class User extends RestModel {
}).then((response) => Promise.resolve(response.data));
}
confirmPasscode(passcode) {
confirmPasscode(code) {
return Api.post(this.getEntityResource() + "/passcode/confirm", {
type: "totp",
passcode: passcode,
code: code,
}).then((response) => Promise.resolve(response.data));
}

View File

@@ -8,16 +8,16 @@
<p-auth-header></p-auth-header>
<v-spacer></v-spacer>
<v-layout wrap align-top>
<template v-if="enterPasscode">
<template v-if="enterCode">
<v-flex xs12 class="pa-2 body-2">
<translate>Please enter a valid verification code to access your account:</translate>
</v-flex>
<v-flex xs12 class="pa-2">
<v-text-field
id="auth-passcode"
v-model="passcode"
id="auth-code"
v-model="code"
:disabled="loading"
name="passcode"
name="code"
type="text"
:label="$gettext('Verification Code')"
mask="nnn nnn nnn nnn"
@@ -32,7 +32,7 @@
autocomplete="one-time-code"
browser-autocomplete="one-time-code"
background-color="grey lighten-5"
class="input-passcode text-selectable"
class="input-code text-selectable"
prepend-inner-icon="verified_user"
color="primary"
@keyup.enter.native="onLogin"
@@ -47,7 +47,7 @@
<v-text-field
id="auth-username"
v-model="username"
:disabled="loading || enterPasscode"
:disabled="loading || enterCode"
name="username"
type="text"
:label="$gettext('Name')"
@@ -97,7 +97,7 @@
</template>
<v-flex xs12 class="px-2 py-1 auth-actions">
<div class="action-buttons auth-buttons text-xs-center">
<v-btn v-if="enterPasscode" :color="colors.secondary" outline :block="$vuetify.breakpoint.xsOnly" :style="`color: ${colors.link}!important`" class="action-cancel ra-6 px-3 py-2 opacity-80" @click.stop.prevent="onCancel">
<v-btn v-if="enterCode" :color="colors.secondary" outline :block="$vuetify.breakpoint.xsOnly" :style="`color: ${colors.link}!important`" class="action-cancel ra-6 px-3 py-2 opacity-80" @click.stop.prevent="onCancel">
<translate>Cancel</translate>
</v-btn>
<v-btn v-else-if="registerUri" :color="colors.secondary" outline :block="$vuetify.breakpoint.xsOnly" :style="`color: ${colors.link}!important`" class="action-register ra-6 px-3 py-2 opacity-80" @click.stop.prevent="onRegister">
@@ -140,8 +140,8 @@ export default {
username: "",
password: "",
showPassword: false,
passcode: "",
enterPasscode: false,
code: "",
enterCode: false,
sponsor: this.$config.isSponsor(),
config: this.$config.values,
siteDescription: this.$config.getSiteDescription(),
@@ -186,8 +186,8 @@ export default {
this.username = "";
this.password = "";
this.showPassword = false;
this.passcode = "";
this.enterPasscode = false;
this.code = "";
this.enterCode = false;
},
onCancel() {
if (this.loading) {
@@ -201,7 +201,7 @@ export default {
onLogin() {
const username = this.username.trim();
const password = this.password.trim();
const passcode = this.passcode.trim();
const code = this.code.trim();
if (username === "" || password === "") {
return;
@@ -209,13 +209,13 @@ export default {
this.loading = true;
this.$session
.login(username, password, passcode)
.login(username, password, code)
.then(() => {
this.load();
})
.catch((e) => {
if (e.response?.data?.code === 32) {
this.enterPasscode = true;
this.enterCode = true;
}
this.loading = false;
});

View File

@@ -352,7 +352,6 @@
import PAccountPasswordDialog from "dialog/account/password.vue";
import countries from "options/countries.json";
import Notify from "common/notify";
import User from "model/user";
import * as options from "options/options";
export default {

8
go.mod
View File

@@ -43,8 +43,8 @@ require (
github.com/ulule/deepcopier v0.0.0-20200430083143-45decc6639b6
github.com/urfave/cli v1.22.14
go4.org v0.0.0-20230225012048-214862532bf5 // indirect
golang.org/x/crypto v0.21.0
golang.org/x/net v0.23.0
golang.org/x/crypto v0.22.0
golang.org/x/net v0.24.0
gonum.org/v1/gonum v0.15.0
gopkg.in/photoprism/go-tz.v2 v2.1.2
gopkg.in/yaml.v2 v2.4.0
@@ -67,7 +67,7 @@ require (
require github.com/gabriel-vasile/mimetype v1.4.3
require (
golang.org/x/sync v0.6.0
golang.org/x/sync v0.7.0
golang.org/x/time v0.5.0
)
@@ -124,7 +124,7 @@ require (
github.com/zitadel/logging v0.5.0 // indirect
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a // indirect
golang.org/x/oauth2 v0.18.0 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/sys v0.19.0 // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/square/go-jose.v2 v2.6.0 // indirect

16
go.sum
View File

@@ -385,8 +385,8 @@ golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -449,8 +449,8 @@ golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfS
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -466,8 +466,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -499,8 +499,8 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20191110171634-ad39bd3f0407/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=

View File

@@ -103,11 +103,11 @@ func AuthenticateAdmin(app *gin.Engine, router *gin.RouterGroup) (authToken stri
// AuthenticateUser Register session routes and returns valid SessionId.
// Call this func after registering other routes and before performing other requests.
func AuthenticateUser(app *gin.Engine, router *gin.RouterGroup, name string, password string) (authToken string) {
func AuthenticateUser(app *gin.Engine, router *gin.RouterGroup, username string, password string) (authToken string) {
CreateSession(router)
r := PerformRequestWithBody(app, http.MethodPost, "/api/v1/session", form.AsJson(form.Login{
UserName: name,
Username: username,
Password: password,
}))

View File

@@ -0,0 +1,148 @@
package api
import (
"net/http"
"github.com/dustin/go-humanize/english"
"github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/internal/event"
"github.com/photoprism/photoprism/internal/form"
"github.com/photoprism/photoprism/internal/get"
"github.com/photoprism/photoprism/internal/server/limiter"
"github.com/photoprism/photoprism/pkg/authn"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/header"
"github.com/photoprism/photoprism/pkg/i18n"
)
// CreateOAuthToken creates a new access token for clients that
// authenticate with valid OAuth2 client credentials.
//
// POST /api/v1/oauth/token
func CreateOAuthToken(router *gin.RouterGroup) {
router.POST("/oauth/token", func(c *gin.Context) {
// Prevent CDNs from caching this endpoint.
if header.IsCdn(c.Request) {
AbortNotFound(c)
return
}
// Get client IP address for logs and rate limiting checks.
clientIp := ClientIP(c)
// Abort if running in public mode.
if get.Config().Public() {
event.AuditErr([]string{clientIp, "client", "create session", "oauth2", authn.ErrDisabledInPublicMode.Error()})
AbortForbidden(c)
return
}
// Disable caching of responses.
c.Header(header.CacheControl, header.CacheControlNoStore)
// Token create request form.
var f form.OAuthCreateToken
var sess *entity.Session
var client *entity.Client
var err error
// Allow authentication with basic auth and form values.
if clientId, clientSecret, _ := header.BasicAuth(c); clientId != "" && clientSecret != "" {
f.GrantType = authn.GrantClientCredentials
f.ClientID = clientId
f.ClientSecret = clientSecret
} else if err = c.ShouldBind(&f); err != nil {
event.AuditWarn([]string{clientIp, "client", "create session", "oauth2", "%s"}, err)
AbortBadRequest(c)
return
}
// Check the credentials for completeness and the correct format.
if err = f.Validate(); err != nil {
event.AuditWarn([]string{clientIp, "client", "create session", "oauth2", "%s"}, err)
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": i18n.Msg(i18n.ErrInvalidCredentials)})
return
}
// Check request rate limit.
r := limiter.Login.Request(clientIp)
// Abort if request rate limit is exceeded.
if r.Reject() || limiter.Auth.Reject(clientIp) {
limiter.AbortJSON(c)
return
}
// Create a new session (access token) based on the grant type specified in the request.
switch f.GrantType {
case authn.GrantClientCredentials, authn.GrantUndefined:
// Find client with the specified ID.
client = entity.FindClientByUID(f.ClientID)
// Check if a client has been found, it is enabled, and the credentials are valid.
if client == nil {
event.AuditWarn([]string{clientIp, "client %s", "create session", "oauth2", authn.ErrInvalidClientID.Error()}, f.ClientID)
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": i18n.Msg(i18n.ErrInvalidCredentials)})
return
} else if !client.AuthEnabled {
event.AuditWarn([]string{clientIp, "client %s", "create session", "oauth2", authn.ErrAuthenticationDisabled.Error()}, f.ClientID)
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": i18n.Msg(i18n.ErrInvalidCredentials)})
return
} else if method := client.Method(); !method.IsDefault() && method != authn.MethodOAuth2 {
event.AuditWarn([]string{clientIp, "client %s", "create session", "oauth2", "method %s not supported"}, f.ClientID, clean.LogQuote(method.String()))
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": i18n.Msg(i18n.ErrInvalidCredentials)})
return
} else if client.InvalidSecret(f.ClientSecret) {
event.AuditWarn([]string{clientIp, "client %s", "create session", "oauth2", authn.ErrInvalidClientSecret.Error()}, f.ClientID)
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": i18n.Msg(i18n.ErrInvalidCredentials)})
return
}
// Cancel failure rate limit reservation.
r.Success()
// Create new client session.
sess = client.NewSession(c, authn.GrantClientCredentials)
case authn.GrantPassword:
// Generate an app password for a user account and accept the password for confirmation.
event.AuditWarn([]string{clientIp, "client %s", "create session", "oauth2", "password grant type is not implemented yet"}, f.ClientID)
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": i18n.Msg(i18n.ErrInvalidCredentials)})
return
default:
event.AuditErr([]string{clientIp, "client %s", "create session", "oauth2", authn.ErrInvalidGrantType.Error()}, f.ClientID)
c.AbortWithStatusJSON(sess.HttpStatus(), gin.H{"error": i18n.Msg(i18n.ErrInvalidCredentials)})
return
}
// Save new session.
if sess, err = get.Session().Save(sess); err != nil {
event.AuditErr([]string{clientIp, "client %s", "create session", "oauth2", err.Error()}, f.ClientID)
c.AbortWithStatusJSON(sess.HttpStatus(), gin.H{"error": i18n.Msg(i18n.ErrInvalidCredentials)})
return
} else if sess == nil {
event.AuditErr([]string{clientIp, "client %s", "create session", "oauth2", StatusFailed.String()}, f.ClientID)
c.AbortWithStatusJSON(sess.HttpStatus(), gin.H{"error": i18n.Msg(i18n.ErrUnexpected)})
return
} else {
event.AuditInfo([]string{clientIp, "client %s", "session %s", "oauth2", "created"}, f.ClientID, sess.RefID)
}
// Delete any existing client sessions above the configured limit.
if client == nil {
// Skip deletion if not created by a client.
} else if deleted := client.EnforceAuthTokenLimit(); deleted > 0 {
event.AuditInfo([]string{clientIp, "client %s", "session %s", "oauth2", "deleted %s"}, f.ClientID, sess.RefID, english.Plural(deleted, "previously created client session", "previously created client sessions"))
}
// Send response with access token, token type, and token lifetime.
response := gin.H{
"access_token": sess.AuthToken(),
"token_type": sess.AuthTokenType(),
"expires_in": sess.ExpiresIn(),
}
c.JSON(http.StatusOK, response)
})
}

View File

@@ -8,11 +8,9 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/tidwall/gjson"
"github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/internal/form"
"github.com/photoprism/photoprism/pkg/authn"
"github.com/photoprism/photoprism/pkg/header"
)
@@ -28,7 +26,7 @@ func TestCreateOAuthToken(t *testing.T) {
var path = "/api/v1/oauth/token"
data := url.Values{
"grant_type": {"client_credentials"},
"grant_type": {authn.GrantClientCredentials.String()},
"client_id": {"cs5cpu17n6gj2qo5"},
"client_secret": {"xcCbOrw6I0vcoXzhnOmXhjpVSyFq0l0e"},
"scope": {"metrics"},
@@ -53,7 +51,7 @@ func TestCreateOAuthToken(t *testing.T) {
var path = "/api/v1/oauth/token"
data := url.Values{
"grant_type": {"client_credentials"},
"grant_type": {authn.GrantClientCredentials.String()},
"client_id": {"cs5cpu17n6gj2qo5"},
"client_secret": {"xcCbOrw6I0vcoXzhnOmXhjpVSyFq0l0e"},
"scope": {"metrics"},
@@ -80,7 +78,7 @@ func TestCreateOAuthToken(t *testing.T) {
var path = "/api/v1/oauth/token"
data := url.Values{
"grant_type": {"client_credentials"},
"grant_type": {authn.GrantClientCredentials.String()},
"client_id": {"123"},
"client_secret": {"xcCbOrw6I0vcoXzhnOmXhjpVSyFq0l0e"},
"scope": {"metrics"},
@@ -107,7 +105,7 @@ func TestCreateOAuthToken(t *testing.T) {
var path = "/api/v1/oauth/token"
data := url.Values{
"grant_type": {"client_credentials"},
"grant_type": {authn.GrantClientCredentials.String()},
"client_id": {"cs5cpu17n6gj2yy6"},
"client_secret": {"xcCbOrw6I0vcoXzhnOmXhjpVSyFq0l0e"},
"scope": {"metrics"},
@@ -134,7 +132,7 @@ func TestCreateOAuthToken(t *testing.T) {
var path = "/api/v1/oauth/token"
data := url.Values{
"grant_type": {"client_credentials"},
"grant_type": {authn.GrantClientCredentials.String()},
"client_id": {"cs5cpu17n6gj2qo5"},
"client_secret": {"xcCbOrw6I0vcoXzhnOmXhjpVSyFq0l0f"},
"scope": {"metrics"},
@@ -161,7 +159,7 @@ func TestCreateOAuthToken(t *testing.T) {
var path = "/api/v1/oauth/token"
data := url.Values{
"grant_type": {"client_credentials"},
"grant_type": {authn.GrantClientCredentials.String()},
"client_id": {"cs5gfsvbd7ejzn8m"},
"client_secret": {"aaCbOrw6I0vcoXzhnOmXhjpVSyFq0l0e"},
"scope": {"metrics"},
@@ -188,7 +186,7 @@ func TestCreateOAuthToken(t *testing.T) {
var path = "/api/v1/oauth/token"
data := url.Values{
"grant_type": {"client_credentials"},
"grant_type": {authn.GrantClientCredentials.String()},
"client_id": {"cs5cpu17n6gj2jh6"},
"client_secret": {"aaCbOrw6I0vcoXzhnOmXhjpVSyFq0l0e"},
"scope": {"*"},
@@ -205,103 +203,3 @@ func TestCreateOAuthToken(t *testing.T) {
assert.Equal(t, http.StatusUnauthorized, w.Code)
})
}
func TestRevokeOAuthToken(t *testing.T) {
const tokenPath = "/api/v1/oauth/token"
const revokePath = "/api/v1/oauth/revoke"
t.Run("Success", func(t *testing.T) {
app, router, conf := NewApiTest()
conf.SetAuthMode(config.AuthModePasswd)
defer conf.SetAuthMode(config.AuthModePublic)
CreateOAuthToken(router)
RevokeOAuthToken(router)
data := url.Values{
"grant_type": {"client_credentials"},
"client_id": {"cs5cpu17n6gj2qo5"},
"client_secret": {"xcCbOrw6I0vcoXzhnOmXhjpVSyFq0l0e"},
"scope": {"metrics"},
}
createToken, _ := http.NewRequest("POST", tokenPath, strings.NewReader(data.Encode()))
createToken.Header.Add(header.ContentType, header.ContentTypeForm)
createResp := httptest.NewRecorder()
app.ServeHTTP(createResp, createToken)
t.Logf("Header: %s", createResp.Header())
t.Logf("BODY: %s", createResp.Body.String())
assert.Equal(t, http.StatusOK, createResp.Code)
authToken := gjson.Get(createResp.Body.String(), "access_token").String()
revokeToken, _ := http.NewRequest("POST", revokePath, nil)
revokeToken.Header.Add(header.XAuthToken, authToken)
revokeResp := httptest.NewRecorder()
app.ServeHTTP(revokeResp, revokeToken)
t.Logf("Header: %s", revokeResp.Header())
t.Logf("BODY: %s", revokeResp.Body.String())
assert.Equal(t, http.StatusOK, revokeResp.Code)
})
t.Run("FormData", func(t *testing.T) {
app, router, conf := NewApiTest()
conf.SetAuthMode(config.AuthModePasswd)
defer conf.SetAuthMode(config.AuthModePublic)
CreateOAuthToken(router)
RevokeOAuthToken(router)
createData := url.Values{
"grant_type": {"client_credentials"},
"client_id": {"cs5cpu17n6gj2qo5"},
"client_secret": {"xcCbOrw6I0vcoXzhnOmXhjpVSyFq0l0e"},
"scope": {"metrics"},
}
createToken, _ := http.NewRequest("POST", tokenPath, strings.NewReader(createData.Encode()))
createToken.Header.Add(header.ContentType, header.ContentTypeForm)
createResp := httptest.NewRecorder()
app.ServeHTTP(createResp, createToken)
t.Logf("Header: %s", createResp.Header())
t.Logf("BODY: %s", createResp.Body.String())
assert.Equal(t, http.StatusOK, createResp.Code)
authToken := gjson.Get(createResp.Body.String(), "access_token").String()
revokeData := url.Values{
"token": {authToken},
"token_type_hint": {form.ClientAccessToken},
}
revokeToken, _ := http.NewRequest("POST", revokePath, strings.NewReader(revokeData.Encode()))
revokeToken.Header.Add(header.ContentType, header.ContentTypeForm)
revokeResp := httptest.NewRecorder()
app.ServeHTTP(revokeResp, revokeToken)
t.Logf("Header: %s", revokeResp.Header())
t.Logf("BODY: %s", revokeResp.Body.String())
assert.Equal(t, http.StatusOK, revokeResp.Code)
})
t.Run("PublicMode", func(t *testing.T) {
app, router, _ := NewApiTest()
RevokeOAuthToken(router)
sess := entity.SessionFixtures.Get("alice_token")
revokeToken, _ := http.NewRequest("POST", revokePath, nil)
revokeToken.Header.Add(header.XAuthToken, sess.AuthToken())
revokeResp := httptest.NewRecorder()
app.ServeHTTP(revokeResp, revokeToken)
t.Logf("Header: %s", revokeResp.Header())
t.Logf("BODY: %s", revokeResp.Body.String())
assert.Equal(t, http.StatusForbidden, revokeResp.Code)
})
}

View File

@@ -0,0 +1,106 @@
package api
import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/internal/event"
"github.com/photoprism/photoprism/internal/form"
"github.com/photoprism/photoprism/internal/get"
"github.com/photoprism/photoprism/pkg/authn"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/header"
"github.com/photoprism/photoprism/pkg/i18n"
"github.com/photoprism/photoprism/pkg/rnd"
)
// RevokeOAuthToken takes an access token and deletes it. A client may only delete its own tokens.
//
// POST /api/v1/oauth/revoke
func RevokeOAuthToken(router *gin.RouterGroup) {
router.POST("/oauth/revoke", func(c *gin.Context) {
// Prevent CDNs from caching this endpoint.
if header.IsCdn(c.Request) {
AbortNotFound(c)
return
}
// Get client IP address for logs and rate limiting checks.
clientIp := ClientIP(c)
// Abort if running in public mode.
if get.Config().Public() {
event.AuditErr([]string{clientIp, "client", "delete session", "oauth2", authn.ErrDisabledInPublicMode.Error()})
Abort(c, http.StatusForbidden, i18n.ErrForbidden)
return
}
var err error
// Token revokation request form.
var f form.OAuthRevokeToken
// Get token from request header.
authToken := AuthToken(c)
// Get the auth token to be revoked from the submitted form values or the request header.
if err = c.ShouldBind(&f); err != nil && authToken == "" {
event.AuditWarn([]string{clientIp, "client", "delete session", "oauth2", "%s"}, err)
AbortBadRequest(c)
return
} else if f.Empty() {
f.AuthToken = authToken
f.TypeHint = form.ClientAccessToken
}
// Check the token form values.
if err = f.Validate(); err != nil {
event.AuditWarn([]string{clientIp, "client", "delete session", "oauth2", "%s"}, err)
AbortBadRequest(c)
return
}
// Disable caching of responses.
c.Header(header.CacheControl, header.CacheControlNoStore)
// Find session based on auth token.
sess, err := entity.FindSession(rnd.SessionID(f.AuthToken))
if err != nil {
event.AuditErr([]string{clientIp, "client %s", "session %s", "delete session as %s", "oauth2", "%s"}, clean.Log(sess.ClientInfo()), clean.Log(sess.RefID), sess.ClientRole().String(), err.Error())
c.AbortWithStatusJSON(http.StatusUnauthorized, i18n.NewResponse(http.StatusUnauthorized, i18n.ErrUnauthorized))
return
} else if sess == nil {
event.AuditErr([]string{clientIp, "client %s", "session %s", "delete session as %s", "oauth2", authn.Denied}, clean.Log(sess.ClientInfo()), clean.Log(sess.RefID), sess.ClientRole().String())
c.AbortWithStatusJSON(http.StatusUnauthorized, i18n.NewResponse(http.StatusUnauthorized, i18n.ErrUnauthorized))
return
} else if sess.Abort(c) {
event.AuditErr([]string{clientIp, "client %s", "session %s", "delete session as %s", "oauth2", authn.Denied}, clean.Log(sess.ClientInfo()), clean.Log(sess.RefID), sess.ClientRole().String())
return
} else if !sess.IsClient() {
event.AuditErr([]string{clientIp, "client %s", "session %s", "delete session as %s", "oauth2", authn.Denied}, clean.Log(sess.ClientInfo()), clean.Log(sess.RefID), sess.ClientRole().String())
c.AbortWithStatusJSON(http.StatusForbidden, i18n.NewResponse(http.StatusForbidden, i18n.ErrForbidden))
return
} else {
event.AuditInfo([]string{clientIp, "client %s", "session %s", "delete session as %s", "oauth2", authn.Granted}, clean.Log(sess.ClientInfo()), clean.Log(sess.RefID), sess.ClientRole().String())
}
// Delete session cache and database record.
if err = sess.Delete(); err != nil {
// Log error.
event.AuditErr([]string{clientIp, "client %s", "session %s", "delete session as %s", "oauth2", "%s"}, clean.Log(sess.ClientInfo()), clean.Log(sess.RefID), sess.ClientRole().String(), err)
// Return JSON error.
c.AbortWithStatusJSON(http.StatusNotFound, i18n.NewResponse(http.StatusNotFound, i18n.ErrNotFound))
return
}
// Log event.
event.AuditInfo([]string{clientIp, "client %s", "session %s", "oauth2", "deleted"}, clean.Log(sess.ClientInfo()), clean.Log(sess.RefID))
// Send response.
c.JSON(http.StatusOK, DeleteSessionResponse(sess.ID))
})
}

View File

@@ -0,0 +1,117 @@
package api
import (
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/tidwall/gjson"
"github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/internal/form"
"github.com/photoprism/photoprism/pkg/header"
)
func TestRevokeOAuthToken(t *testing.T) {
const tokenPath = "/api/v1/oauth/token"
const revokePath = "/api/v1/oauth/revoke"
t.Run("Success", func(t *testing.T) {
app, router, conf := NewApiTest()
conf.SetAuthMode(config.AuthModePasswd)
defer conf.SetAuthMode(config.AuthModePublic)
CreateOAuthToken(router)
RevokeOAuthToken(router)
data := url.Values{
"grant_type": {"client_credentials"},
"client_id": {"cs5cpu17n6gj2qo5"},
"client_secret": {"xcCbOrw6I0vcoXzhnOmXhjpVSyFq0l0e"},
"scope": {"metrics"},
}
createToken, _ := http.NewRequest("POST", tokenPath, strings.NewReader(data.Encode()))
createToken.Header.Add(header.ContentType, header.ContentTypeForm)
createResp := httptest.NewRecorder()
app.ServeHTTP(createResp, createToken)
t.Logf("Header: %s", createResp.Header())
t.Logf("BODY: %s", createResp.Body.String())
assert.Equal(t, http.StatusOK, createResp.Code)
authToken := gjson.Get(createResp.Body.String(), "access_token").String()
revokeToken, _ := http.NewRequest("POST", revokePath, nil)
revokeToken.Header.Add(header.XAuthToken, authToken)
revokeResp := httptest.NewRecorder()
app.ServeHTTP(revokeResp, revokeToken)
t.Logf("Header: %s", revokeResp.Header())
t.Logf("BODY: %s", revokeResp.Body.String())
assert.Equal(t, http.StatusOK, revokeResp.Code)
})
t.Run("FormData", func(t *testing.T) {
app, router, conf := NewApiTest()
conf.SetAuthMode(config.AuthModePasswd)
defer conf.SetAuthMode(config.AuthModePublic)
CreateOAuthToken(router)
RevokeOAuthToken(router)
createData := url.Values{
"grant_type": {"client_credentials"},
"client_id": {"cs5cpu17n6gj2qo5"},
"client_secret": {"xcCbOrw6I0vcoXzhnOmXhjpVSyFq0l0e"},
"scope": {"metrics"},
}
createToken, _ := http.NewRequest("POST", tokenPath, strings.NewReader(createData.Encode()))
createToken.Header.Add(header.ContentType, header.ContentTypeForm)
createResp := httptest.NewRecorder()
app.ServeHTTP(createResp, createToken)
t.Logf("Header: %s", createResp.Header())
t.Logf("BODY: %s", createResp.Body.String())
assert.Equal(t, http.StatusOK, createResp.Code)
authToken := gjson.Get(createResp.Body.String(), "access_token").String()
revokeData := url.Values{
"token": {authToken},
"token_type_hint": {form.ClientAccessToken},
}
revokeToken, _ := http.NewRequest("POST", revokePath, strings.NewReader(revokeData.Encode()))
revokeToken.Header.Add(header.ContentType, header.ContentTypeForm)
revokeResp := httptest.NewRecorder()
app.ServeHTTP(revokeResp, revokeToken)
t.Logf("Header: %s", revokeResp.Header())
t.Logf("BODY: %s", revokeResp.Body.String())
assert.Equal(t, http.StatusOK, revokeResp.Code)
})
t.Run("PublicMode", func(t *testing.T) {
app, router, _ := NewApiTest()
RevokeOAuthToken(router)
sess := entity.SessionFixtures.Get("alice_token")
revokeToken, _ := http.NewRequest("POST", revokePath, nil)
revokeToken.Header.Add(header.XAuthToken, sess.AuthToken())
revokeResp := httptest.NewRecorder()
app.ServeHTTP(revokeResp, revokeToken)
t.Logf("Header: %s", revokeResp.Header())
t.Logf("BODY: %s", revokeResp.Body.String())
assert.Equal(t, http.StatusForbidden, revokeResp.Code)
})
}

View File

@@ -58,10 +58,10 @@ func CreateSession(router *gin.RouterGroup) {
// Check request rate limit.
var r *limiter.Request
if f.Passcode == "" {
r = limiter.Login.Request(clientIp)
} else {
if f.HasPasscode() {
r = limiter.Login.RequestN(clientIp, 3)
} else {
r = limiter.Login.Request(clientIp)
}
// Abort if failure rate limit is exceeded.

View File

@@ -1,221 +0,0 @@
package api
import (
"net/http"
"github.com/dustin/go-humanize/english"
"github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/internal/event"
"github.com/photoprism/photoprism/internal/form"
"github.com/photoprism/photoprism/internal/get"
"github.com/photoprism/photoprism/internal/server/limiter"
"github.com/photoprism/photoprism/pkg/authn"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/header"
"github.com/photoprism/photoprism/pkg/i18n"
"github.com/photoprism/photoprism/pkg/rnd"
)
// CreateOAuthToken creates a new access token for clients that
// authenticate with valid OAuth2 client credentials.
//
// POST /api/v1/oauth/token
func CreateOAuthToken(router *gin.RouterGroup) {
router.POST("/oauth/token", func(c *gin.Context) {
// Prevent CDNs from caching this endpoint.
if header.IsCdn(c.Request) {
AbortNotFound(c)
return
}
// Get client IP address for logs and rate limiting checks.
clientIp := ClientIP(c)
if get.Config().Public() {
// Abort if running in public mode.
event.AuditErr([]string{clientIp, "client", "create session", "oauth2", authn.ErrDisabledInPublicMode.Error()})
AbortForbidden(c)
return
}
var err error
// Client authentication request credentials.
var f form.ClientCredentials
// Allow authentication with basic auth and form values.
if clientId, clientSecret, _ := header.BasicAuth(c); clientId != "" && clientSecret != "" {
f.ClientID = clientId
f.ClientSecret = clientSecret
} else if err = c.ShouldBind(&f); err != nil {
event.AuditWarn([]string{clientIp, "client", "create session", "oauth2", "%s"}, err)
AbortBadRequest(c)
return
}
// Check the credentials for completeness and the correct format.
if err = f.Validate(); err != nil {
event.AuditWarn([]string{clientIp, "client", "create session", "oauth2", "%s"}, err)
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": i18n.Msg(i18n.ErrInvalidCredentials)})
return
}
// Disable caching of responses.
c.Header(header.CacheControl, header.CacheControlNoStore)
// Check request rate limit.
r := limiter.Login.Request(clientIp)
// Abort if request rate limit is exceeded.
if r.Reject() || limiter.Auth.Reject(clientIp) {
limiter.AbortJSON(c)
return
}
// Find the client that has the ID specified in the authentication request.
client := entity.FindClientByUID(f.ClientID)
// Abort if the client ID or secret are invalid.
if client == nil {
event.AuditWarn([]string{clientIp, "client %s", "create session", "oauth2", authn.ErrInvalidClientID.Error()}, f.ClientID)
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": i18n.Msg(i18n.ErrInvalidCredentials)})
return
} else if !client.AuthEnabled {
event.AuditWarn([]string{clientIp, "client %s", "create session", "oauth2", authn.ErrAuthenticationDisabled.Error()}, f.ClientID)
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": i18n.Msg(i18n.ErrInvalidCredentials)})
return
} else if method := client.Method(); !method.IsDefault() && method != authn.MethodOAuth2 {
event.AuditWarn([]string{clientIp, "client %s", "create session", "oauth2", "method %s not supported"}, f.ClientID, clean.LogQuote(method.String()))
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": i18n.Msg(i18n.ErrInvalidCredentials)})
return
} else if client.InvalidSecret(f.ClientSecret) {
event.AuditWarn([]string{clientIp, "client %s", "create session", "oauth2", authn.ErrInvalidClientSecret.Error()}, f.ClientID)
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": i18n.Msg(i18n.ErrInvalidCredentials)})
return
}
// Return the reserved request rate limit tokens after successful authentication.
r.Success()
// Create new client session.
sess := client.NewSession(c)
// Save new client session.
if sess, err = get.Session().Save(sess); err != nil {
event.AuditErr([]string{clientIp, "client %s", "create session", "oauth2", "%s"}, f.ClientID, err)
c.AbortWithStatusJSON(sess.HttpStatus(), gin.H{"error": i18n.Msg(i18n.ErrInvalidCredentials)})
return
} else if sess == nil {
event.AuditErr([]string{clientIp, "client %s", "create session", "oauth2", StatusFailed.String()}, f.ClientID)
c.AbortWithStatusJSON(sess.HttpStatus(), gin.H{"error": i18n.Msg(i18n.ErrUnexpected)})
return
} else {
event.AuditInfo([]string{clientIp, "client %s", "session %s", "oauth2", "created"}, f.ClientID, sess.RefID)
}
// Deletes old client sessions above the configured limit.
if deleted := client.EnforceAuthTokenLimit(); deleted > 0 {
event.AuditInfo([]string{clientIp, "client %s", "session %s", "oauth2", "deleted %s"}, f.ClientID, sess.RefID, english.Plural(deleted, "previously created client session", "previously created client sessions"))
}
// Response includes access token, token type, and token lifetime.
data := gin.H{
"access_token": sess.AuthToken(),
"token_type": sess.AuthTokenType(),
"expires_in": sess.ExpiresIn(),
}
// Return JSON response.
c.JSON(http.StatusOK, data)
})
}
// RevokeOAuthToken takes an access token and deletes it. A client may only delete its own tokens.
//
// POST /api/v1/oauth/revoke
func RevokeOAuthToken(router *gin.RouterGroup) {
router.POST("/oauth/revoke", func(c *gin.Context) {
// Prevent CDNs from caching this endpoint.
if header.IsCdn(c.Request) {
AbortNotFound(c)
return
}
// Get client IP address for logs and rate limiting checks.
clientIp := ClientIP(c)
// Abort if running in public mode.
if get.Config().Public() {
event.AuditErr([]string{clientIp, "client", "delete session", "oauth2", authn.ErrDisabledInPublicMode.Error()})
Abort(c, http.StatusForbidden, i18n.ErrForbidden)
return
}
var err error
// Token revocation request data.
var f form.ClientToken
authToken := AuthToken(c)
// Get the auth token to be revoked from the submitted form values or the request header.
if err = c.ShouldBind(&f); err != nil && authToken == "" {
event.AuditWarn([]string{clientIp, "client", "delete session", "oauth2", "%s"}, err)
AbortBadRequest(c)
return
} else if f.Empty() {
f.AuthToken = authToken
f.TypeHint = form.ClientAccessToken
}
// Check the token form values.
if err = f.Validate(); err != nil {
event.AuditWarn([]string{clientIp, "client", "delete session", "oauth2", "%s"}, err)
AbortBadRequest(c)
return
}
// Disable caching of responses.
c.Header(header.CacheControl, header.CacheControlNoStore)
// Find session based on auth token.
sess, err := entity.FindSession(rnd.SessionID(f.AuthToken))
if err != nil {
event.AuditErr([]string{clientIp, "client %s", "session %s", "delete session as %s", "oauth2", "%s"}, clean.Log(sess.ClientInfo()), clean.Log(sess.RefID), sess.ClientRole().String(), err.Error())
c.AbortWithStatusJSON(http.StatusUnauthorized, i18n.NewResponse(http.StatusUnauthorized, i18n.ErrUnauthorized))
return
} else if sess == nil {
event.AuditErr([]string{clientIp, "client %s", "session %s", "delete session as %s", "oauth2", authn.Denied}, clean.Log(sess.ClientInfo()), clean.Log(sess.RefID), sess.ClientRole().String())
c.AbortWithStatusJSON(http.StatusUnauthorized, i18n.NewResponse(http.StatusUnauthorized, i18n.ErrUnauthorized))
return
} else if sess.Abort(c) {
event.AuditErr([]string{clientIp, "client %s", "session %s", "delete session as %s", "oauth2", authn.Denied}, clean.Log(sess.ClientInfo()), clean.Log(sess.RefID), sess.ClientRole().String())
return
} else if !sess.IsClient() {
event.AuditErr([]string{clientIp, "client %s", "session %s", "delete session as %s", "oauth2", authn.Denied}, clean.Log(sess.ClientInfo()), clean.Log(sess.RefID), sess.ClientRole().String())
c.AbortWithStatusJSON(http.StatusForbidden, i18n.NewResponse(http.StatusForbidden, i18n.ErrForbidden))
return
} else {
event.AuditInfo([]string{clientIp, "client %s", "session %s", "delete session as %s", "oauth2", authn.Granted}, clean.Log(sess.ClientInfo()), clean.Log(sess.RefID), sess.ClientRole().String())
}
// Delete session cache and database record.
if err = sess.Delete(); err != nil {
// Log error.
event.AuditErr([]string{clientIp, "client %s", "session %s", "delete session as %s", "oauth2", "%s"}, clean.Log(sess.ClientInfo()), clean.Log(sess.RefID), sess.ClientRole().String(), err)
// Return JSON error.
c.AbortWithStatusJSON(http.StatusNotFound, i18n.NewResponse(http.StatusNotFound, i18n.ErrNotFound))
return
}
// Log event.
event.AuditInfo([]string{clientIp, "client %s", "session %s", "oauth2", "deleted"}, clean.Log(sess.ClientInfo()), clean.Log(sess.RefID))
// Return JSON response for confirmation.
c.JSON(http.StatusOK, DeleteSessionResponse(sess.ID))
})
}

View File

@@ -143,7 +143,7 @@ func TestCreateSession(t *testing.T) {
CreateSession(router)
r := PerformRequestWithBody(app, http.MethodPost, "/api/v1/session", form.AsJson(form.Login{
UserName: "admin",
Username: "admin",
Password: "xxx",
}))

View File

@@ -95,7 +95,7 @@ func ConfirmUserPasscode(router *gin.RouterGroup) {
}
// Verify passcode.
valid, passcode, err := user.VerifyPasscode(frm.Passcode)
valid, passcode, err := user.VerifyPasscode(frm.Passcode())
if err != nil {
event.AuditErr([]string{ClientIP(c), "session %s", "users", user.UserName, "failed to verify passcode", clean.Error(err)}, s.RefID)
@@ -201,7 +201,7 @@ func DeactivateUserPasscode(router *gin.RouterGroup) {
}
// checkUserPasscodeAuth checks authentication and authorization for the passcode endpoints.
func checkUserPasscodeAuth(c *gin.Context, action acl.Permission) (*entity.Session, *entity.User, *form.UserPasscode, error) {
func checkUserPasscodeAuth(c *gin.Context, action acl.Permission) (*entity.Session, *entity.User, *form.Passcode, error) {
conf := get.Config()
// Prevent caching of API response.
@@ -244,7 +244,7 @@ func checkUserPasscodeAuth(c *gin.Context, action acl.Permission) (*entity.Sessi
return s, nil, nil, errors.New("unsupported")
}
frm := &form.UserPasscode{}
frm := &form.Passcode{}
// Validate request parameters.
if err := c.BindJSON(frm); err != nil {

View File

@@ -6,12 +6,12 @@ import (
"testing"
"time"
"github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/internal/form"
"github.com/pquerna/otp/totp"
"github.com/stretchr/testify/assert"
"github.com/tidwall/gjson"
"github.com/stretchr/testify/assert"
"github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/internal/form"
)
func TestCreateUserPasscode(t *testing.T) {
@@ -37,10 +37,10 @@ func TestCreateUserPasscode(t *testing.T) {
CreateUserPasscode(router)
sessId := AuthenticateUser(app, router, "alice", "Alice123!")
f := form.UserPasscode{
Passcode: "",
Password: "Alice123!",
f := form.Passcode{
Type: "totp",
Password: "Alice123!",
Code: "",
}
if pcStr, err := json.Marshal(f); err != nil {
log.Fatal(err)
@@ -56,10 +56,10 @@ func TestCreateUserPasscode(t *testing.T) {
CreateUserPasscode(router)
sessId := AuthenticateUser(app, router, "alice", "Alice123!")
f := form.UserPasscode{
Passcode: "",
Password: "abcdef",
f := form.Passcode{
Type: "xxx",
Password: "abcdef",
Code: "",
}
if pcStr, err := json.Marshal(f); err != nil {
log.Fatal(err)
@@ -75,10 +75,10 @@ func TestCreateUserPasscode(t *testing.T) {
CreateUserPasscode(router)
sessId := AuthenticateUser(app, router, "alice", "Alice123!")
f := form.UserPasscode{
Passcode: "",
Password: "wrong",
f := form.Passcode{
Type: "totp",
Password: "wrong",
Code: "",
}
if pcStr, err := json.Marshal(f); err != nil {
log.Fatal(err)
@@ -94,10 +94,10 @@ func TestCreateUserPasscode(t *testing.T) {
CreateUserPasscode(router)
sessId := AuthenticateUser(app, router, "alice", "Alice123!")
f := form.UserPasscode{
Passcode: "",
Password: "Alice123!",
f := form.Passcode{
Type: "totp",
Password: "Alice123!",
Code: "",
}
if pcStr, err := json.Marshal(f); err != nil {
log.Fatal(err)
@@ -122,10 +122,10 @@ func TestConfirmUserPasscode(t *testing.T) {
ConfirmUserPasscode(router)
sessId := AuthenticateUser(app, router, "alice", "Alice123!")
f := form.UserPasscode{
Passcode: "123",
Password: "Alice123!",
f := form.Passcode{
Type: "totp",
Password: "Alice123!",
Code: "123",
}
if pcStr, err := json.Marshal(f); err != nil {
log.Fatal(err)
@@ -141,10 +141,10 @@ func TestConfirmUserPasscode(t *testing.T) {
ConfirmUserPasscode(router)
sessId := AuthenticateUser(app, router, "alice", "Alice123!")
f := form.UserPasscode{
Passcode: "123456",
Password: "Alice123!",
f := form.Passcode{
Type: "totp",
Password: "Alice123!",
Code: "123456",
}
if pcStr, err := json.Marshal(f); err != nil {
log.Fatal(err)
@@ -163,10 +163,10 @@ func TestActivateUserPasscode(t *testing.T) {
ActivateUserPasscode(router)
sessId := AuthenticateUser(app, router, "alice", "Alice123!")
f := form.UserPasscode{
Passcode: "",
Password: "Alice123!",
f := form.Passcode{
Type: "totp",
Password: "Alice123!",
Code: "",
}
if pcStr, err := json.Marshal(f); err != nil {
log.Fatal(err)
@@ -182,10 +182,10 @@ func TestActivateUserPasscode(t *testing.T) {
ActivateUserPasscode(router)
sessId := AuthenticateUser(app, router, "alice", "Alice123!")
f := form.UserPasscode{
Passcode: "",
Password: "Alice123!",
f := form.Passcode{
Type: "totp",
Password: "Alice123!",
Code: "",
}
if pcStr, err := json.Marshal(f); err != nil {
log.Fatal(err)
@@ -213,10 +213,10 @@ func TestDeactivateUserPasscode(t *testing.T) {
DeactivateUserPasscode(router)
sessId := AuthenticateUser(app, router, "alice", "Alice123!")
f := form.UserPasscode{
Passcode: "",
Password: "wrong",
f := form.Passcode{
Type: "totp",
Password: "wrong",
Code: "",
}
if pcStr, err := json.Marshal(f); err != nil {
log.Fatal(err)
@@ -235,10 +235,10 @@ func TestUserPasscode(t *testing.T) {
CreateUserPasscode(router)
sessId := AuthenticateUser(app, router, "alice", "Alice123!")
f0 := form.UserPasscode{
Passcode: "",
Password: "Alice123!",
f0 := form.Passcode{
Type: "totp",
Password: "Alice123!",
Code: "",
}
pcStr, err := json.Marshal(f0)
@@ -266,11 +266,12 @@ func TestUserPasscode(t *testing.T) {
t.Fatal(err)
}
f := form.UserPasscode{
Passcode: code,
Password: "Alice123!",
f := form.Passcode{
Type: "totp",
Password: "Alice123!",
Code: code,
}
pcStr, err = json.Marshal(f)
if err != nil {

View File

@@ -443,9 +443,9 @@ func (m *Client) UpdateLastActive() *Client {
}
// NewSession creates a new client session.
func (m *Client) NewSession(c *gin.Context) *Session {
func (m *Client) NewSession(c *gin.Context, t authn.GrantType) *Session {
// Create, initialize, and return new session.
return NewSession(m.AuthExpires, 0).SetContext(c).SetClient(m)
return NewSession(m.AuthExpires, 0).SetContext(c).SetClient(m).SetGrantType(t)
}
// EnforceAuthTokenLimit deletes client sessions above the configured limit and returns the number of deleted sessions.

View File

@@ -51,6 +51,7 @@ type Session struct {
AuthDomain string `gorm:"type:VARBINARY(255);default:'';" json:"AuthDomain" yaml:"AuthDomain,omitempty"`
AuthID string `gorm:"type:VARBINARY(255);index;default:'';" json:"AuthID" yaml:"AuthID,omitempty"`
AuthScope string `gorm:"size:1024;default:'';" json:"AuthScope" yaml:"AuthScope,omitempty"`
GrantType string `gorm:"type:VARBINARY(64);default:'';" json:"GrantType" yaml:"GrantType,omitempty"`
LastActive int64 `json:"LastActive" yaml:"LastActive,omitempty"`
SessExpires int64 `gorm:"index" json:"Expires" yaml:"Expires,omitempty"`
SessTimeout int64 `json:"Timeout" yaml:"Timeout,omitempty"`
@@ -514,6 +515,38 @@ func (m *Session) SetAuthID(id string) *Session {
return m
}
// Provider returns the authentication provider.
func (m *Session) Provider() authn.ProviderType {
return authn.Provider(m.AuthProvider)
}
// SetProvider updates the session's authentication provider.
func (m *Session) SetProvider(provider authn.ProviderType) *Session {
if provider == "" {
return m
}
m.AuthProvider = provider.String()
return m
}
// Method returns the authentication method.
func (m *Session) Method() authn.MethodType {
return authn.Method(m.AuthMethod)
}
// SetMethod sets a custom authentication method.
func (m *Session) SetMethod(method authn.MethodType) *Session {
if method == "" {
return m
}
m.AuthMethod = method.String()
return m
}
// Scope returns the authorization scope as a sanitized string.
func (m *Session) Scope() string {
return clean.Scope(m.AuthScope)
@@ -573,34 +606,18 @@ func (m *Session) SetScope(scope string) *Session {
return m
}
// Provider returns the authentication provider.
func (m *Session) Provider() authn.ProviderType {
return authn.Provider(m.AuthProvider)
// AuthGrantType returns the session's grant type as authn.GrantType.
func (m *Session) AuthGrantType() authn.GrantType {
return authn.Grant(m.GrantType)
}
// SetProvider updates the session's authentication provider.
func (m *Session) SetProvider(provider authn.ProviderType) *Session {
if provider == "" {
// SetGrantType sets the session's grant type if no type has been set yet.
func (m *Session) SetGrantType(t authn.GrantType) *Session {
if t.IsUndefined() || m.GrantType != "" {
return m
}
m.AuthProvider = provider.String()
return m
}
// Method returns the authentication method.
func (m *Session) Method() authn.MethodType {
return authn.Method(m.AuthMethod)
}
// SetMethod sets a custom authentication method.
func (m *Session) SetMethod(method authn.MethodType) *Session {
if method == "" {
return m
}
m.AuthMethod = method.String()
m.GrantType = t.String()
return m
}

View File

@@ -20,8 +20,8 @@ import (
// Auth checks if the credentials are valid and returns the user and authentication provider.
var Auth = func(f form.Login, m *Session, c *gin.Context) (user *User, provider authn.ProviderType, method authn.MethodType, err error) {
// Get username from login form.
nameName := f.Username()
// Get sanitized username from login form.
nameName := f.CleanUsername()
// Find registered user account.
user = FindUserByName(nameName)
@@ -77,16 +77,16 @@ func AuthLocal(user *User, f form.Login, m *Session, c *gin.Context) (provider a
// Get client IP from request context.
clientIp := header.ClientIP(c)
// Get username from login form.
userName := f.Username()
// Get sanitized username from login form.
username := f.CleanUsername()
// Check if user account exists.
if user == nil {
message := authn.ErrAccountNotFound.Error()
if m != nil {
event.AuditWarn([]string{clientIp, "session %s", "login as %s", message}, m.RefID, clean.LogQuote(userName))
event.LoginError(clientIp, "api", userName, m.UserAgent, message)
event.AuditWarn([]string{clientIp, "session %s", "login as %s", message}, m.RefID, clean.LogQuote(username))
event.LoginError(clientIp, "api", username, m.UserAgent, message)
m.Status = http.StatusUnauthorized
}
@@ -98,8 +98,8 @@ func AuthLocal(user *User, f form.Login, m *Session, c *gin.Context) (provider a
message := fmt.Sprintf("%s authentication disabled", authn.ProviderLocal.String())
if m != nil {
event.AuditWarn([]string{clientIp, "session %s", "login as %s", message}, m.RefID, clean.LogQuote(userName))
event.LoginError(clientIp, "api", userName, m.UserAgent, message)
event.AuditWarn([]string{clientIp, "session %s", "login as %s", message}, m.RefID, clean.LogQuote(username))
event.LoginError(clientIp, "api", username, m.UserAgent, message)
m.Status = http.StatusUnauthorized
}
@@ -108,8 +108,8 @@ func AuthLocal(user *User, f form.Login, m *Session, c *gin.Context) (provider a
message := authn.ErrAccountDisabled.Error()
if m != nil {
event.AuditWarn([]string{clientIp, "session %s", "login as %s", message}, m.RefID, clean.LogQuote(userName))
event.LoginError(clientIp, "api", userName, m.UserAgent, message)
event.AuditWarn([]string{clientIp, "session %s", "login as %s", message}, m.RefID, clean.LogQuote(username))
event.LoginError(clientIp, "api", username, m.UserAgent, message)
m.Status = http.StatusUnauthorized
}
@@ -120,8 +120,8 @@ func AuthLocal(user *User, f form.Login, m *Session, c *gin.Context) (provider a
if authSess, authUser, authErr := AuthSession(f, c); authSess != nil && authUser != nil && authErr == nil {
if !authUser.IsRegistered() || authUser.UserUID != user.UserUID {
message := authn.ErrInvalidUsername.Error()
event.AuditErr([]string{clientIp, "session %s", "login as %s with app password", message}, m.RefID, clean.LogQuote(userName))
event.LoginError(clientIp, "api", userName, m.UserAgent, message)
event.AuditErr([]string{clientIp, "session %s", "login as %s with app password", message}, m.RefID, clean.LogQuote(username))
event.LoginError(clientIp, "api", username, m.UserAgent, message)
m.Status = http.StatusUnauthorized
return provider, method, i18n.Error(i18n.ErrInvalidCredentials)
} else if insufficientScope := authSess.InsufficientScope(acl.ResourceSessions, acl.Permissions{acl.ActionCreate}); insufficientScope || !authSess.IsClient() {
@@ -131,8 +131,8 @@ func AuthLocal(user *User, f form.Login, m *Session, c *gin.Context) (provider a
} else {
message = authn.ErrUnauthorized.Error()
}
event.AuditErr([]string{clientIp, "session %s", "login as %s with app password", message}, m.RefID, clean.LogQuote(userName))
event.LoginError(clientIp, "api", userName, m.UserAgent, message)
event.AuditErr([]string{clientIp, "session %s", "login as %s with app password", message}, m.RefID, clean.LogQuote(username))
event.LoginError(clientIp, "api", username, m.UserAgent, message)
m.Status = http.StatusUnauthorized
return provider, method, i18n.Error(i18n.ErrInvalidCredentials)
} else {
@@ -142,8 +142,8 @@ func AuthLocal(user *User, f form.Login, m *Session, c *gin.Context) (provider a
m.ClientName = authSess.ClientName
m.SetScope(authSess.Scope())
m.SetMethod(authn.MethodSession)
event.AuditInfo([]string{clientIp, "session %s", "login as %s with app password", authn.Succeeded}, m.RefID, clean.LogQuote(userName))
event.LoginInfo(clientIp, "api", userName, m.UserAgent)
event.AuditInfo([]string{clientIp, "session %s", "login as %s with app password", authn.Succeeded}, m.RefID, clean.LogQuote(username))
event.LoginInfo(clientIp, "api", username, m.UserAgent)
return provider, method, authErr
}
}
@@ -153,8 +153,8 @@ func AuthLocal(user *User, f form.Login, m *Session, c *gin.Context) (provider a
message := authn.ErrInvalidPassword.Error()
if m != nil {
event.AuditErr([]string{clientIp, "session %s", "login as %s", message}, m.RefID, clean.LogQuote(userName))
event.LoginError(clientIp, "api", userName, m.UserAgent, message)
event.AuditErr([]string{clientIp, "session %s", "login as %s", message}, m.RefID, clean.LogQuote(username))
event.LoginError(clientIp, "api", username, m.UserAgent, message)
m.Status = http.StatusUnauthorized
}
@@ -165,7 +165,7 @@ func AuthLocal(user *User, f form.Login, m *Session, c *gin.Context) (provider a
// Check two-factor authentication, if enabled.
if method = user.Method(); method.Is(authn.Method2FA) {
if valid, _, passcodeErr := user.VerifyPasscode(f.Passcode); passcodeErr != nil {
if valid, _, passcodeErr := user.VerifyPasscode(f.Passcode()); passcodeErr != nil {
return provider, method, passcodeErr
} else if !valid {
return provider, method, authn.ErrInvalidPasscode
@@ -175,8 +175,8 @@ func AuthLocal(user *User, f form.Login, m *Session, c *gin.Context) (provider a
}
if m != nil {
event.AuditInfo([]string{clientIp, "session %s", "login as %s", authn.Succeeded}, m.RefID, clean.LogQuote(userName))
event.LoginInfo(clientIp, "api", userName, m.UserAgent)
event.AuditInfo([]string{clientIp, "session %s", "login as %s", authn.Succeeded}, m.RefID, clean.LogQuote(username))
event.LoginInfo(clientIp, "api", username, m.UserAgent)
}
return provider, method, nil
@@ -211,6 +211,7 @@ func (m *Session) LogIn(f form.Login, c *gin.Context) (err error) {
}
m.SetUser(user)
m.SetGrantType(authn.GrantPassword)
}
// Try to redeem link share token, if provided.
@@ -219,18 +220,18 @@ func (m *Session) LogIn(f form.Login, c *gin.Context) (err error) {
// Redeem token.
if user.IsRegistered() {
if shares := user.RedeemToken(f.ShareToken); shares == 0 {
if shares := user.RedeemToken(f.Token); shares == 0 {
message := authn.ErrInvalidShareToken.Error()
event.AuditWarn([]string{m.IP(), "session %s", message}, m.RefID)
m.Status = http.StatusNotFound
return i18n.Error(i18n.ErrInvalidLink)
} else {
event.AuditInfo([]string{m.IP(), "session %s", "token redeemed for %d shares"}, m.RefID, user.RedeemToken(f.ShareToken))
event.AuditInfo([]string{m.IP(), "session %s", "token redeemed for %d shares"}, m.RefID, user.RedeemToken(f.Token))
}
} else if data := m.Data(); data == nil {
m.Status = http.StatusInternalServerError
return i18n.Error(i18n.ErrUnexpected)
} else if shares := data.RedeemToken(f.ShareToken); shares == 0 {
} else if shares := data.RedeemToken(f.Token); shares == 0 {
message := authn.ErrInvalidShareToken.Error()
event.AuditWarn([]string{m.IP(), "session %s", message}, m.RefID)
event.LoginError(m.IP(), "api", "", m.UserAgent, message)
@@ -239,6 +240,7 @@ func (m *Session) LogIn(f form.Login, c *gin.Context) (err error) {
} else {
m.SetData(data)
m.SetProvider(authn.ProviderLink)
m.SetGrantType(authn.GrantShareToken)
event.AuditInfo([]string{m.IP(), "session %s", "token redeemed for %d shares"}, m.RefID, shares, data)
}

View File

@@ -19,7 +19,7 @@ func TestAuthSession(t *testing.T) {
t.Run("RandomAppPassword", func(t *testing.T) {
// Create test request form.
f := form.Login{
UserName: "alice",
Username: "alice",
Password: rnd.AppPassword(),
}
@@ -38,7 +38,7 @@ func TestAuthSession(t *testing.T) {
t.Run("RandomAuthToken", func(t *testing.T) {
// Create test request form.
f := form.Login{
UserName: "alice",
Username: "alice",
Password: rnd.AuthToken(),
}
@@ -59,7 +59,7 @@ func TestAuthSession(t *testing.T) {
// Create test request form.
f := form.Login{
UserName: "alice",
Username: "alice",
Password: s.AuthToken(),
}
@@ -81,7 +81,7 @@ func TestAuthSession(t *testing.T) {
// Create test request form.
f := form.Login{
UserName: "alice",
Username: "alice",
Password: s.AuthToken(),
}
@@ -119,7 +119,7 @@ func TestAuthSession(t *testing.T) {
// Create test request form.
f := form.Login{
UserName: "alice",
Username: "alice",
Password: s.AuthToken(),
}
@@ -154,7 +154,7 @@ func TestAuthSession(t *testing.T) {
t.Run("EmptyPassword", func(t *testing.T) {
// Create test request form.
f := form.Login{
UserName: "alice",
Username: "alice",
Password: "",
}
@@ -179,7 +179,7 @@ func TestAuthLocal(t *testing.T) {
// Create test request form.
frm := form.Login{
UserName: "alice",
Username: "alice",
Password: "Alice123!",
}
@@ -202,7 +202,7 @@ func TestAuthLocal(t *testing.T) {
// Create test request form.
frm := form.Login{
UserName: "alice",
Username: "alice",
Password: "photoprism",
}
@@ -227,7 +227,7 @@ func TestAuthLocal(t *testing.T) {
// Create test request form.
frm := form.Login{
UserName: "friend",
Username: "friend",
Password: "!Friend321",
}
@@ -254,7 +254,7 @@ func TestAuthLocal(t *testing.T) {
// Create test request form.
frm := form.Login{
UserName: "friend",
Username: "friend",
Password: "!Friend321",
}
@@ -285,7 +285,7 @@ func TestSessionLogIn(t *testing.T) {
// Create login form.
frm := form.Login{
UserName: "admin",
Username: "admin",
Password: "photoprism",
}
@@ -310,9 +310,9 @@ func TestSessionLogIn(t *testing.T) {
// Create login form.
frm := form.Login{
UserName: "jane",
Passcode: passcode,
Username: "jane",
Password: "Jane123!",
Code: passcode,
}
// Create test request context.
@@ -331,9 +331,9 @@ func TestSessionLogIn(t *testing.T) {
// Create login form.
frm := form.Login{
UserName: "jane",
Passcode: "xxxxxx",
Username: "jane",
Password: "Jane123!",
Code: "xxxxxx",
}
// Create test request context.
@@ -352,7 +352,7 @@ func TestSessionLogIn(t *testing.T) {
// Create login form.
frm := form.Login{
UserName: "jane",
Username: "jane",
Password: "Jane123!",
}
@@ -372,7 +372,7 @@ func TestSessionLogIn(t *testing.T) {
// Create login form.
frm := form.Login{
UserName: "admin",
Username: "admin",
Password: "wrong",
}
@@ -392,7 +392,7 @@ func TestSessionLogIn(t *testing.T) {
// Create login form.
frm := form.Login{
UserName: "foo",
Username: "foo",
Password: "password",
}
@@ -412,7 +412,7 @@ func TestSessionLogIn(t *testing.T) {
// Create login form.
frm := form.Login{
ShareToken: "1jxf3jfn2k",
Token: "1jxf3jfn2k",
}
// Create test request context.
@@ -432,7 +432,7 @@ func TestSessionLogIn(t *testing.T) {
// Create login form.
frm := form.Login{
ShareToken: "1jxf3jfxxx",
Token: "1jxf3jfxxx",
}
// Create test request context.
@@ -470,7 +470,7 @@ func TestSessionLogIn(t *testing.T) {
// Create login form.
frm := form.Login{
ShareToken: "1jxf3jfn2k",
Token: "1jxf3jfn2k",
}
// Create test request context.
@@ -490,7 +490,7 @@ func TestSessionLogIn(t *testing.T) {
// Create login form.
frm := form.Login{
ShareToken: "1jxf3jfxxx",
Token: "1jxf3jfxxx",
}
// Create test request context.
@@ -514,9 +514,9 @@ func TestSessionLogIn(t *testing.T) {
// Create login form.
frm := form.Login{
UserName: "jane",
Passcode: passcode,
Username: "jane",
Password: "Jane123!",
Code: passcode,
}
// Create test request context.

View File

@@ -1,17 +1,17 @@
package entity
import (
"github.com/gin-gonic/gin"
"net/http"
"testing"
"time"
"github.com/photoprism/photoprism/pkg/report"
"github.com/stretchr/testify/assert"
"github.com/photoprism/photoprism/internal/acl"
"github.com/photoprism/photoprism/pkg/authn"
"github.com/photoprism/photoprism/pkg/header"
"github.com/photoprism/photoprism/pkg/report"
"github.com/photoprism/photoprism/pkg/rnd"
"github.com/photoprism/photoprism/pkg/unix"
)
@@ -544,6 +544,80 @@ func TestSession_SetAuthID(t *testing.T) {
})
}
func TestSession_SetMethod(t *testing.T) {
t.Run("Empty", func(t *testing.T) {
s := &Session{
UserName: "test",
RefID: "sessxkkcxxxz",
AuthProvider: authn.ProviderAccessToken.String(),
AuthMethod: authn.MethodPersonal.String(),
}
m := s.SetMethod("")
assert.Equal(t, authn.ProviderAccessToken, m.Provider())
assert.Equal(t, authn.MethodPersonal, m.Method())
})
t.Run("Test", func(t *testing.T) {
s := &Session{
UserName: "test",
RefID: "sessxkkcxxxz",
AuthProvider: authn.ProviderAccessToken.String(),
AuthMethod: authn.MethodPersonal.String(),
}
m := s.SetMethod("Test")
assert.Equal(t, authn.ProviderAccessToken, m.Provider())
assert.Equal(t, authn.Method("Test"), m.Method())
})
t.Run("Test", func(t *testing.T) {
s := &Session{
UserName: "test",
RefID: "sessxkkcxxxz",
AuthProvider: authn.ProviderAccessToken.String(),
AuthMethod: authn.MethodPersonal.String(),
}
m := s.SetMethod(authn.MethodSession)
assert.Equal(t, authn.ProviderAccessToken, m.Provider())
assert.Equal(t, authn.MethodSession, m.Method())
})
}
func TestSession_SetProvider(t *testing.T) {
m := FindSessionByRefID("sessxkkcabce")
assert.Equal(t, authn.ProviderDefault, m.Provider())
m.SetProvider("")
assert.Equal(t, authn.ProviderDefault, m.Provider())
m.SetProvider(authn.ProviderLink)
assert.Equal(t, authn.ProviderLink, m.Provider())
m.SetProvider(authn.ProviderDefault)
assert.Equal(t, authn.ProviderDefault, m.Provider())
}
func TestSession_ChangePassword(t *testing.T) {
m := FindSessionByRefID("sessxkkcabce")
assert.Empty(t, m.PreviewToken)
assert.Empty(t, m.DownloadToken)
err := m.ChangePassword("photoprism123")
if err != nil {
t.Fatal(err)
}
assert.NotEmpty(t, m.PreviewToken)
assert.NotEmpty(t, m.DownloadToken)
err2 := m.ChangePassword("Bobbob123!")
if err2 != nil {
t.Fatal(err2)
}
}
func TestSession_ValidateScope(t *testing.T) {
t.Run("AnyScope", func(t *testing.T) {
s := &Session{
@@ -659,78 +733,37 @@ func TestSession_SetScope(t *testing.T) {
})
}
func TestSession_SetMethod(t *testing.T) {
t.Run("Empty", func(t *testing.T) {
s := &Session{
UserName: "test",
RefID: "sessxkkcxxxz",
AuthProvider: authn.ProviderAccessToken.String(),
AuthMethod: authn.MethodPersonal.String(),
func TestSession_SetGrantType(t *testing.T) {
t.Run("Password", func(t *testing.T) {
m := &Session{
UserName: "test",
RefID: "sessxkkcxxxz",
AuthScope: "*",
}
m := s.SetMethod("")
expected := "password"
assert.Equal(t, authn.ProviderAccessToken, m.Provider())
assert.Equal(t, authn.MethodPersonal, m.Method())
m.SetGrantType(authn.GrantPassword)
assert.Equal(t, expected, m.GrantType)
m.SetGrantType(authn.GrantClientCredentials)
assert.Equal(t, expected, m.GrantType)
m.SetGrantType(authn.GrantUndefined)
assert.Equal(t, expected, m.GrantType)
assert.Equal(t, authn.GrantPassword, m.AuthGrantType())
})
t.Run("Test", func(t *testing.T) {
s := &Session{
UserName: "test",
RefID: "sessxkkcxxxz",
AuthProvider: authn.ProviderAccessToken.String(),
AuthMethod: authn.MethodPersonal.String(),
}
t.Run("ClientCredentials", func(t *testing.T) {
client := ClientFixtures.Pointer("alice")
m := client.NewSession(&gin.Context{}, authn.GrantClientCredentials)
m := s.SetMethod("Test")
expected := "client_credentials"
assert.Equal(t, authn.ProviderAccessToken, m.Provider())
assert.Equal(t, authn.Method("Test"), m.Method())
assert.Equal(t, expected, m.GrantType)
m.SetGrantType(authn.GrantPassword)
assert.Equal(t, expected, m.GrantType)
m.SetGrantType(authn.GrantUndefined)
assert.Equal(t, expected, m.GrantType)
assert.Equal(t, authn.GrantClientCredentials, m.AuthGrantType())
})
t.Run("Test", func(t *testing.T) {
s := &Session{
UserName: "test",
RefID: "sessxkkcxxxz",
AuthProvider: authn.ProviderAccessToken.String(),
AuthMethod: authn.MethodPersonal.String(),
}
m := s.SetMethod(authn.MethodSession)
assert.Equal(t, authn.ProviderAccessToken, m.Provider())
assert.Equal(t, authn.MethodSession, m.Method())
})
}
func TestSession_SetProvider(t *testing.T) {
m := FindSessionByRefID("sessxkkcabce")
assert.Equal(t, authn.ProviderDefault, m.Provider())
m.SetProvider("")
assert.Equal(t, authn.ProviderDefault, m.Provider())
m.SetProvider(authn.ProviderLink)
assert.Equal(t, authn.ProviderLink, m.Provider())
m.SetProvider(authn.ProviderDefault)
assert.Equal(t, authn.ProviderDefault, m.Provider())
}
func TestSession_ChangePassword(t *testing.T) {
m := FindSessionByRefID("sessxkkcabce")
assert.Empty(t, m.PreviewToken)
assert.Empty(t, m.DownloadToken)
err := m.ChangePassword("photoprism123")
if err != nil {
t.Fatal(err)
}
assert.NotEmpty(t, m.PreviewToken)
assert.NotEmpty(t, m.DownloadToken)
err2 := m.ChangePassword("Bobbob123!")
if err2 != nil {
t.Fatal(err2)
}
}
func TestSession_SetPreviewToken(t *testing.T) {

View File

@@ -13,7 +13,7 @@ func LoginData(level logrus.Level, ip, realm, name, browser, message string) Dat
"level": level.String(),
"ip": txt.Clip(ip, txt.ClipIP),
"realm": txt.Clip(realm, txt.ClipRealm),
"name": txt.Clip(name, txt.ClipUserName),
"name": txt.Clip(name, txt.ClipUsername),
"browser": txt.Clip(browser, txt.ClipLog),
"message": txt.Clip(message, txt.ClipLog),
}

View File

@@ -1,39 +0,0 @@
package form
import (
"fmt"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/rnd"
)
// ClientCredentials represents client authentication request credentials.
type ClientCredentials struct {
ClientID string `form:"client_id" json:"client_id,omitempty"`
ClientSecret string `form:"client_secret" json:" client_secret,omitempty"`
AuthScope string `form:"scope" json:"scope,omitempty"`
}
// Validate checks the grant type and credentials.
func (f ClientCredentials) Validate() error {
// Check client ID.
if f.ClientID == "" {
return fmt.Errorf("missing client id")
} else if rnd.InvalidUID(f.ClientID, 'c') {
return fmt.Errorf("invalid client id")
}
// Check client secret.
if f.ClientSecret == "" {
return fmt.Errorf("missing client secret")
} else if !rnd.IsAlnum(f.ClientSecret) {
return fmt.Errorf("invalid client secret")
}
return nil
}
// Scope returns the client scopes as sanitized string.
func (f ClientCredentials) Scope() string {
return clean.Scope(f.AuthScope)
}

View File

@@ -1,56 +0,0 @@
package form
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestClientCredentials_Validate(t *testing.T) {
t.Run("Valid", func(t *testing.T) {
m := ClientCredentials{
ClientID: "cs5gfen1bgxz7s9i",
ClientSecret: "abc",
AuthScope: "*",
}
assert.NoError(t, m.Validate())
assert.Equal(t, "*", m.Scope())
})
t.Run("NoClientID", func(t *testing.T) {
m := ClientCredentials{
ClientID: "",
ClientSecret: "Alice123!",
AuthScope: "*",
}
assert.Error(t, m.Validate())
})
t.Run("InvalidClientID", func(t *testing.T) {
m := ClientCredentials{
ClientID: "s5gfen1bgxz7s9i",
ClientSecret: "Alice123!",
AuthScope: "*",
}
assert.Error(t, m.Validate())
})
t.Run("NoSecret", func(t *testing.T) {
m := ClientCredentials{
ClientID: "cs5gfen1bgxz7s9i",
ClientSecret: "",
AuthScope: "*",
}
assert.Error(t, m.Validate())
})
t.Run("InvalidSecret", func(t *testing.T) {
m := ClientCredentials{
ClientID: "cs5gfen1bgxz7s9i",
ClientSecret: "abc 123",
AuthScope: "*",
}
assert.Error(t, m.Validate())
})
}

View File

@@ -2,51 +2,57 @@ package form
import (
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/txt"
)
// Login represents a login form.
type Login struct {
UserName string `json:"username,omitempty"`
UserEmail string `json:"email,omitempty"`
Password string `json:"password,omitempty"`
Passcode string `json:"passcode,omitempty"`
ShareToken string `json:"token,omitempty"`
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
Code string `json:"code,omitempty"`
Token string `json:"token,omitempty"`
Email string `json:"email,omitempty"`
}
// Username returns the sanitized username in lowercase.
func (f Login) Username() string {
return clean.Username(f.UserName)
// CleanUsername returns the sanitized and normalized username.
func (f Login) CleanUsername() string {
return clean.Username(f.Username)
}
// Email returns the sanitized email in lowercase.
func (f Login) Email() string {
return clean.Email(f.UserEmail)
}
// HasUsername checks if a username is set.
func (f Login) HasUsername() bool {
if l := len(f.Username()); l == 0 || l > 255 {
return false
}
return true
}
// HasPasscode checks if a passcode is set.
func (f Login) HasPasscode() bool {
return f.Passcode != "" && len(f.Passcode) <= 255
}
// HasPassword checks if a password is set.
func (f Login) HasPassword() bool {
return f.Password != "" && len(f.Password) <= 255
}
// HasShareToken checks if a link share token has been provided.
func (f Login) HasShareToken() bool {
return f.ShareToken != ""
// CleanEmail returns the sanitized and normalized email.
func (f Login) CleanEmail() string {
return clean.Email(f.Email)
}
// HasCredentials checks if all credentials is set.
func (f Login) HasCredentials() bool {
return f.HasUsername() && f.HasPassword()
}
// HasUsername checks if a username is set.
func (f Login) HasUsername() bool {
if l := len(f.CleanUsername()); l == 0 || l > txt.ClipUsername {
return false
}
return true
}
// HasPassword checks if a password is set.
func (f Login) HasPassword() bool {
return f.Password != "" && len(f.Password) <= txt.ClipPassword
}
// HasPasscode checks if a verification passcode has been provided.
func (f Login) HasPasscode() bool {
return clean.Passcode(f.Code) != ""
}
// Passcode returns the sanitized verification passcode.
func (f Login) Passcode() string {
return clean.Passcode(f.Code)
}
// HasShareToken checks if a link share token has been provided.
func (f Login) HasShareToken() bool {
return f.Token != ""
}

View File

@@ -6,68 +6,94 @@ import (
"github.com/stretchr/testify/assert"
)
func TestLogin_Email(t *testing.T) {
func TestLogin_CleanEmail(t *testing.T) {
t.Run("Empty", func(t *testing.T) {
form := &Login{UserEmail: "", Password: "passwd", ShareToken: ""}
assert.Equal(t, "", form.Email())
form := &Login{Email: "", Password: "passwd", Token: ""}
assert.Equal(t, "", form.CleanEmail())
})
t.Run("valid", func(t *testing.T) {
form := &Login{UserEmail: "test@test.com", UserName: "John", Password: "passwd", ShareToken: "123"}
assert.Equal(t, "test@test.com", form.Email())
form := &Login{Email: "test@test.com", Username: "John", Password: "passwd", Token: "123"}
assert.Equal(t, "test@test.com", form.CleanEmail())
})
}
func TestLogin_HasToken(t *testing.T) {
t.Run("false", func(t *testing.T) {
form := &Login{UserEmail: "test@test.com", UserName: "John", Password: "passwd", ShareToken: ""}
form := &Login{Email: "test@test.com", Username: "John", Password: "passwd", Token: ""}
assert.Equal(t, false, form.HasShareToken())
})
t.Run("true", func(t *testing.T) {
form := &Login{UserEmail: "test@test.com", UserName: "John", Password: "passwd", ShareToken: "123"}
form := &Login{Email: "test@test.com", Username: "John", Password: "passwd", Token: "123"}
assert.Equal(t, true, form.HasShareToken())
})
}
func TestLogin_HasName(t *testing.T) {
func TestLogin_HasUsername(t *testing.T) {
t.Run("false", func(t *testing.T) {
form := &Login{UserEmail: "test@test.com", Password: "passwd", ShareToken: ""}
form := &Login{Email: "test@test.com", Password: "passwd", Token: ""}
assert.Equal(t, false, form.HasUsername())
})
t.Run("true", func(t *testing.T) {
form := &Login{UserEmail: "test@test.com", UserName: "John", Password: "passwd", ShareToken: "123"}
form := &Login{Email: "test@test.com", Username: "John", Password: "passwd", Token: "123"}
assert.Equal(t, true, form.HasUsername())
})
}
func TestLogin_HasPasscode(t *testing.T) {
func TestLogin_CleanUsername(t *testing.T) {
t.Run("false", func(t *testing.T) {
form := &Login{UserEmail: "test@test.com", Passcode: "", ShareToken: ""}
assert.Equal(t, false, form.HasPasscode())
form := &Login{Email: "test@test.com", Password: "passwd", Token: ""}
assert.Equal(t, "", form.CleanUsername())
})
t.Run("true", func(t *testing.T) {
form := &Login{UserEmail: "test@test.com", UserName: "John", Passcode: "123456", ShareToken: "123"}
form := &Login{Email: "test@test.com", Username: " John", Password: "passwd", Token: "123"}
assert.Equal(t, "john", form.CleanUsername())
})
}
func TestLogin_HasPasscode(t *testing.T) {
t.Run("False", func(t *testing.T) {
form := &Login{Email: "test@test.com", Code: "", Token: ""}
assert.Equal(t, false, form.HasPasscode())
})
t.Run("True", func(t *testing.T) {
form := &Login{Email: "test@test.com", Username: "John", Code: "123456", Token: "123"}
assert.Equal(t, true, form.HasPasscode())
})
}
func TestLogin_Passcode(t *testing.T) {
t.Run("Empty", func(t *testing.T) {
form := &Login{Username: "admin", Password: "passwd1234", Code: "", Token: ""}
assert.Equal(t, "", form.Passcode())
})
t.Run("Recovery", func(t *testing.T) {
form := &Login{Username: "admin", Password: "passwd1234", Code: " A23 456 H7l pwf"}
assert.Equal(t, "a23456h7lpwf", form.Passcode())
})
t.Run("Valid", func(t *testing.T) {
form := &Login{Username: "admin", Password: "passwd1234", Code: "123456", Token: "123"}
assert.Equal(t, "123456", form.Passcode())
})
}
func TestLogin_HasPassword(t *testing.T) {
t.Run("false", func(t *testing.T) {
form := &Login{UserEmail: "test@test.com", Password: "", ShareToken: ""}
form := &Login{Email: "test@test.com", Password: "", Token: ""}
assert.Equal(t, false, form.HasPassword())
})
t.Run("true", func(t *testing.T) {
form := &Login{UserEmail: "test@test.com", UserName: "John", Password: "passwd", ShareToken: "123"}
form := &Login{Email: "test@test.com", Username: "John", Password: "passwd", Token: "123"}
assert.Equal(t, true, form.HasPassword())
})
}
func TestLogin_HasCredentials(t *testing.T) {
t.Run("false", func(t *testing.T) {
form := &Login{UserEmail: "test@test.com", Password: "passwd123", ShareToken: ""}
form := &Login{Email: "test@test.com", Password: "passwd123", Token: ""}
assert.Equal(t, false, form.HasCredentials())
})
t.Run("true", func(t *testing.T) {
form := &Login{UserEmail: "test@test.com", UserName: "John", Password: "passwd", ShareToken: "123"}
form := &Login{Email: "test@test.com", Username: "John", Password: "passwd", Token: "123"}
assert.Equal(t, true, form.HasCredentials())
})
}

View File

@@ -0,0 +1,70 @@
package form
import (
"github.com/photoprism/photoprism/pkg/authn"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/rnd"
"github.com/photoprism/photoprism/pkg/txt"
)
// OAuthCreateToken represents a create token request form.
type OAuthCreateToken struct {
GrantType authn.GrantType `form:"grant_type" json:"grant_type,omitempty"`
ClientID string `form:"client_id" json:"client_id,omitempty"`
ClientSecret string `form:"client_secret" json:" client_secret,omitempty"`
Username string `form:"username" json:"username,omitempty"`
Password string `form:"password" json:"password,omitempty"`
RefreshToken string `form:"refresh_token" json:"refresh_token,omitempty"`
Code string `form:"code" json:"code,omitempty"`
CodeVerifier string `form:"code_verifier" json:"code_verifier,omitempty"`
RedirectURI string `form:"redirect_uri" json:"redirect_uri,omitempty"`
Assertion string `form:"assertion" json:"assertion,omitempty"`
Name string `form:"name" json:"name,omitempty"`
Scope string `form:"scope" json:"scope,omitempty"`
Expires int `form:"expires" json:"expires,omitempty"`
}
// Validate verifies the request parameters depending on the grant type.
func (f OAuthCreateToken) Validate() error {
switch f.GrantType {
case authn.GrantClientCredentials, authn.GrantUndefined:
// Validate client id.
if f.ClientID == "" {
return authn.ErrClientIDRequired
} else if rnd.InvalidUID(f.ClientID, 'c') {
return authn.ErrInvalidCredentials
}
// Validate client secret.
if f.ClientSecret == "" {
return authn.ErrClientSecretRequired
} else if !rnd.IsAlnum(f.ClientSecret) {
return authn.ErrInvalidCredentials
}
case authn.GrantPassword:
// Validate request credentials.
if f.Username == "" {
return authn.ErrUsernameRequired
} else if len(f.Username) > txt.ClipUsername {
return authn.ErrInvalidCredentials
} else if f.Password == "" {
return authn.ErrPasswordRequired
} else if len(f.Password) > txt.ClipPassword {
return authn.ErrInvalidCredentials
} else if f.Name == "" {
return authn.ErrNameRequired
} else if f.Scope == "" {
return authn.ErrScopeRequired
}
default:
// Reject requests with unsupported grant types.
return authn.ErrInvalidGrantType
}
return nil
}
// CleanScope returns the client scopes as sanitized string.
func (f OAuthCreateToken) CleanScope() string {
return clean.Scope(f.Scope)
}

View File

@@ -0,0 +1,91 @@
package form
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/photoprism/photoprism/pkg/authn"
)
func TestOAuthCreateToken_Validate(t *testing.T) {
t.Run("Valid", func(t *testing.T) {
m := OAuthCreateToken{
ClientID: "cs5gfen1bgxz7s9i",
ClientSecret: "abc",
Scope: "*",
}
assert.NoError(t, m.Validate())
assert.Equal(t, "*", m.CleanScope())
})
t.Run("GrantType", func(t *testing.T) {
m := OAuthCreateToken{
GrantType: authn.GrantClientCredentials,
ClientID: "cs5gfen1bgxz7s9i",
ClientSecret: "abc",
Scope: "*",
}
assert.NoError(t, m.Validate())
assert.Equal(t, "*", m.CleanScope())
})
t.Run("NoClientID", func(t *testing.T) {
m := OAuthCreateToken{
ClientID: "",
ClientSecret: "Alice123!",
Scope: "*",
}
assert.Error(t, m.Validate())
})
t.Run("InvalidClientID", func(t *testing.T) {
m := OAuthCreateToken{
ClientID: "s5gfen1bgxz7s9i",
ClientSecret: "Alice123!",
Scope: "*",
}
assert.Error(t, m.Validate())
})
t.Run("NoSecret", func(t *testing.T) {
m := OAuthCreateToken{
ClientID: "cs5gfen1bgxz7s9i",
ClientSecret: "",
Scope: "*",
}
assert.Error(t, m.Validate())
})
t.Run("InvalidSecret", func(t *testing.T) {
m := OAuthCreateToken{
ClientID: "cs5gfen1bgxz7s9i",
ClientSecret: "abc 123",
Scope: "*",
}
assert.Error(t, m.Validate())
})
t.Run("Password", func(t *testing.T) {
m := OAuthCreateToken{
GrantType: authn.GrantPassword,
Username: "admin",
Password: "cs5gfen1bgxz7s9i",
Name: "test",
Scope: "*",
}
assert.NoError(t, m.Validate())
})
t.Run("PasswordRequired", func(t *testing.T) {
m := OAuthCreateToken{
GrantType: authn.GrantPassword,
Username: "admin",
Password: "",
Name: "test",
Scope: "*",
}
assert.Error(t, m.Validate())
})
}

View File

@@ -10,14 +10,14 @@ const (
ClientAccessToken = "access_token"
)
// ClientToken represents a client authentication token.
type ClientToken struct {
// OAuthRevokeToken represents a token revokation form.
type OAuthRevokeToken struct {
AuthToken string `form:"token" binding:"required" json:"token,omitempty"`
TypeHint string `form:"token_type_hint" json:" token_type_hint,omitempty"`
}
// Empty checks if all form values are unset.
func (f ClientToken) Empty() bool {
func (f OAuthRevokeToken) Empty() bool {
switch {
case f.AuthToken != "":
return false
@@ -29,7 +29,7 @@ func (f ClientToken) Empty() bool {
}
// Validate checks the token and token type.
func (f ClientToken) Validate() error {
func (f OAuthRevokeToken) Validate() error {
// Check auth token.
if f.AuthToken == "" {
return fmt.Errorf("missing token")

View File

@@ -6,23 +6,23 @@ import (
"github.com/stretchr/testify/assert"
)
func TestClientToken_Empty(t *testing.T) {
func TestOAuthRevokeToken_Empty(t *testing.T) {
t.Run("AuthTokenAndTypeHintEmpty", func(t *testing.T) {
m := ClientToken{
m := OAuthRevokeToken{
AuthToken: "",
TypeHint: "",
}
assert.True(t, m.Empty())
})
t.Run("AuthTokenNotEmpty", func(t *testing.T) {
m := ClientToken{
m := OAuthRevokeToken{
AuthToken: "abc",
TypeHint: "",
}
assert.False(t, m.Empty())
})
t.Run("TypeHintNotEmpty", func(t *testing.T) {
m := ClientToken{
m := OAuthRevokeToken{
AuthToken: "",
TypeHint: "test",
}
@@ -30,30 +30,30 @@ func TestClientToken_Empty(t *testing.T) {
})
}
func TestClientToken_Validate(t *testing.T) {
func TestOAuthRevokeToken_Validate(t *testing.T) {
t.Run("AuthTokenEmpty", func(t *testing.T) {
m := ClientToken{
m := OAuthRevokeToken{
AuthToken: "",
TypeHint: "test",
}
assert.Error(t, m.Validate())
})
t.Run("AuthTokenInvalid", func(t *testing.T) {
m := ClientToken{
m := OAuthRevokeToken{
AuthToken: "abc 234",
TypeHint: "test",
}
assert.Error(t, m.Validate())
})
t.Run("UnsupportedToken", func(t *testing.T) {
m := ClientToken{
m := OAuthRevokeToken{
AuthToken: "abc234",
TypeHint: "test",
}
assert.Error(t, m.Validate())
})
t.Run("Valid", func(t *testing.T) {
m := ClientToken{
m := OAuthRevokeToken{
AuthToken: "abc234",
TypeHint: "access_token",
}

28
internal/form/passcode.go Normal file
View File

@@ -0,0 +1,28 @@
package form
import (
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/txt"
)
// Passcode represents a multi-factor authentication key setup form.
type Passcode struct {
Type string `form:"type" json:"type,omitempty"`
Password string `form:"password" json:"password,omitempty"`
Code string `form:"code" json:"code,omitempty"`
}
// HasPassword checks if a password has been provided.
func (f Passcode) HasPassword() bool {
return f.Password != "" && len(f.Password) <= txt.ClipPassword
}
// HasPasscode checks if a verification code has been provided.
func (f Passcode) HasPasscode() bool {
return clean.Passcode(f.Code) != ""
}
// Passcode returns the sanitized verification code.
func (f Passcode) Passcode() string {
return clean.Passcode(f.Code)
}

View File

@@ -0,0 +1,44 @@
package form
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestPasscode_HasPassword(t *testing.T) {
t.Run("True", func(t *testing.T) {
form := &Passcode{Type: "totp", Password: "passwd1234", Code: "123456"}
assert.Equal(t, true, form.HasPassword())
})
t.Run("False", func(t *testing.T) {
form := &Passcode{Type: "totp", Password: "", Code: "123456"}
assert.Equal(t, false, form.HasPassword())
})
}
func TestPasscode_HasPasscode(t *testing.T) {
t.Run("True", func(t *testing.T) {
form := &Passcode{Type: "totp", Password: "passwd1234", Code: "123456"}
assert.Equal(t, true, form.HasPasscode())
})
t.Run("False", func(t *testing.T) {
form := &Passcode{Type: "totp", Password: "passwd1234", Code: ""}
assert.Equal(t, false, form.HasPasscode())
})
}
func TestPasscode_Passcode(t *testing.T) {
t.Run("Valid", func(t *testing.T) {
form := &Passcode{Type: "totp", Password: "passwd1234", Code: "123456"}
assert.Equal(t, "123456", form.Passcode())
})
t.Run("Recovery", func(t *testing.T) {
form := &Passcode{Type: "totp", Password: "passwd1234", Code: " A23 456 H7l pwf"}
assert.Equal(t, "a23456h7lpwf", form.Passcode())
})
t.Run("Empty", func(t *testing.T) {
form := &Passcode{Type: "totp", Password: "passwd1234", Code: ""}
assert.Equal(t, "", form.Passcode())
})
}

View File

@@ -1,13 +0,0 @@
package form
// UserPasscode represents a multi-factor authentication key setup form.
type UserPasscode struct {
Type string `form:"type" json:"type,omitempty"`
Passcode string `form:"passcode" json:"passcode,omitempty"`
Password string `form:"password" json:"password,omitempty"`
}
// HasPassword checks if a password is set.
func (f UserPasscode) HasPassword() bool {
return f.Password != "" && len(f.Password) <= 255
}

View File

@@ -1,18 +0,0 @@
package form
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestUserPasscode_HasPassword(t *testing.T) {
t.Run("false", func(t *testing.T) {
form := &UserPasscode{Passcode: "123456", Password: ""}
assert.Equal(t, false, form.HasPassword())
})
t.Run("true", func(t *testing.T) {
form := &UserPasscode{Passcode: "123456", Password: "passwd1234"}
assert.Equal(t, true, form.HasPassword())
})
}

View File

@@ -169,7 +169,7 @@ func WebDAVAuth(conf *config.Config) gin.HandlerFunc {
// User credentials.
f := form.Login{
UserName: username,
Username: username,
Password: password,
}

View File

@@ -16,14 +16,19 @@ var (
ErrInvalidCredentials = errors.New("invalid credentials")
ErrInvalidShareToken = errors.New("invalid share token")
ErrInsufficientScope = errors.New("insufficient scope")
ErrNameRequired = errors.New("name required")
ErrScopeRequired = errors.New("scope required")
ErrDisabledInPublicMode = errors.New("disabled in public mode")
ErrAuthenticationDisabled = errors.New("authentication disabled")
)
// OAuth2-related error messages:
var (
ErrInvalidClientID = errors.New("invalid client id")
ErrInvalidClientSecret = errors.New("invalid client secret")
ErrInvalidGrantType = errors.New("invalid grant type")
ErrInvalidClientID = errors.New("invalid client id")
ErrClientIDRequired = errors.New("client id required")
ErrInvalidClientSecret = errors.New("invalid client secret")
ErrClientSecretRequired = errors.New("client secret required")
)
// Username-related error messages:
@@ -35,7 +40,7 @@ var (
// Passcode-related error messages:
var (
ErrPasscodeRequired = errors.New("passcode required")
ErrPasscodeNotSetUp = errors.New("passcode required, but not set up")
ErrPasscodeNotSetUp = errors.New("passcode required, but not configured")
ErrPasscodeNotVerified = errors.New("passcode not verified")
ErrPasscodeAlreadyActivated = errors.New("passcode already activated")
ErrPasscodeNotSupported = errors.New("passcode not supported")

103
pkg/authn/grants.go Normal file
View File

@@ -0,0 +1,103 @@
package authn
import (
"strings"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/txt"
)
// GrantType represents an authentication grant type.
type GrantType string
// Standard authentication grant types.
const (
GrantUndefined GrantType = ""
GrantClientCredentials GrantType = "client_credentials"
GrantPassword GrantType = "password"
GrantShareToken GrantType = "share_token"
GrantRefreshToken GrantType = "refresh_token"
GrantAuthorizationCode GrantType = "authorization_code"
GrantJwtBearer GrantType = "urn:ietf:params:oauth:grant-type:jwt-bearer"
GrantSamlBearer GrantType = "urn:ietf:params:oauth:grant-type:saml2-bearer"
GrantTokenExchange GrantType = "urn:ietf:params:oauth:grant-type:token-exchange"
)
// String returns the provider identifier as a string.
func (t GrantType) String() string {
return clean.TypeLowerUnderscore(string(t))
}
// Is compares the method with another type.
func (t GrantType) Is(method GrantType) bool {
return t == method
}
// IsNot checks if the method is not the specified type.
func (t GrantType) IsNot(method GrantType) bool {
return t != method
}
// IsUndefined checks if the method is undefined.
func (t GrantType) IsUndefined() bool {
return t == ""
}
// Equal checks if the type matches.
func (t GrantType) Equal(s string) bool {
return strings.EqualFold(s, t.String())
}
// NotEqual checks if the type is different.
func (t GrantType) NotEqual(s string) bool {
return !t.Equal(s)
}
// Pretty returns the provider identifier in an easy-to-read format.
func (t GrantType) Pretty() string {
switch t {
case GrantShareToken:
return "Share Token"
case GrantRefreshToken:
return "Refresh Token"
case GrantClientCredentials:
return "Client Credentials"
case GrantAuthorizationCode:
return "Authorization Code"
case GrantJwtBearer:
return "JWT Bearer Assertion"
case GrantSamlBearer:
return "SAML2 Bearer Assertion"
case GrantTokenExchange:
return "Token Exchange"
default:
return txt.UpperFirst(t.String())
}
}
// Grant casts a string to a normalized grant type.
func Grant(s string) GrantType {
s = clean.TypeLowerUnderscore(s)
switch s {
case "", "-", "null", "nil", "0", "false":
return GrantUndefined
case "client_credentials", "client":
return GrantClientCredentials
case "password", "passwd", "pass", "user", "username":
return GrantPassword
case "share_token", "share":
return GrantClientCredentials
case "refresh_token", "refresh":
return GrantRefreshToken
case "authorization_code", "auth_code":
return GrantAuthorizationCode
case "jwt-bearer", "jwt", "jwt_bearer":
return GrantJwtBearer
case "saml2-bearer", "saml2_bearer", "saml2", "saml":
return GrantSamlBearer
case "token-exchange", "token_exchange":
return GrantTokenExchange
default:
return GrantType(s)
}
}

93
pkg/authn/grants_test.go Normal file
View File

@@ -0,0 +1,93 @@
package authn
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestGrantType_String(t *testing.T) {
assert.Equal(t, "", GrantUndefined.String())
assert.Equal(t, "client_credentials", GrantClientCredentials.String())
assert.Equal(t, "password", GrantPassword.String())
assert.Equal(t, "refresh_token", GrantRefreshToken.String())
assert.Equal(t, "authorization_code", GrantAuthorizationCode.String())
assert.Equal(t, "authorization_code", GrantType("Authorization Code ").String())
assert.Equal(t, GrantAuthorizationCode.String(), GrantType("Authorization Code ").String())
assert.Equal(t, "urn:ietf:params:oauth:grant-type:jwt-bearer", GrantJwtBearer.String())
}
func TestGrantType_Is(t *testing.T) {
assert.Equal(t, true, GrantUndefined.Is(GrantUndefined))
assert.Equal(t, true, GrantClientCredentials.Is(GrantClientCredentials))
assert.Equal(t, true, GrantPassword.Is(GrantPassword))
assert.Equal(t, false, GrantClientCredentials.Is(GrantPassword))
assert.Equal(t, false, GrantClientCredentials.Is(GrantRefreshToken))
assert.Equal(t, false, GrantClientCredentials.Is(GrantAuthorizationCode))
assert.Equal(t, false, GrantClientCredentials.Is(GrantJwtBearer))
assert.Equal(t, false, GrantClientCredentials.Is(GrantSamlBearer))
assert.Equal(t, false, GrantClientCredentials.Is(GrantTokenExchange))
assert.Equal(t, false, GrantClientCredentials.Is(GrantUndefined))
}
func TestGrantType_IsNot(t *testing.T) {
assert.Equal(t, false, GrantUndefined.IsNot(GrantUndefined))
assert.Equal(t, false, GrantClientCredentials.IsNot(GrantClientCredentials))
assert.Equal(t, false, GrantPassword.IsNot(GrantPassword))
assert.Equal(t, true, GrantClientCredentials.IsNot(GrantPassword))
assert.Equal(t, true, GrantClientCredentials.IsNot(GrantRefreshToken))
assert.Equal(t, true, GrantClientCredentials.IsNot(GrantAuthorizationCode))
assert.Equal(t, true, GrantClientCredentials.IsNot(GrantJwtBearer))
assert.Equal(t, true, GrantClientCredentials.IsNot(GrantSamlBearer))
assert.Equal(t, true, GrantClientCredentials.IsNot(GrantTokenExchange))
assert.Equal(t, true, GrantClientCredentials.IsNot(GrantUndefined))
}
func TestGrantType_IsUndefined(t *testing.T) {
assert.Equal(t, true, GrantUndefined.IsUndefined())
assert.Equal(t, false, GrantClientCredentials.IsUndefined())
assert.Equal(t, false, GrantPassword.IsUndefined())
}
func TestGrantType_Pretty(t *testing.T) {
assert.Equal(t, "", GrantUndefined.Pretty())
assert.Equal(t, "Client Credentials", GrantClientCredentials.Pretty())
assert.Equal(t, "Password", GrantPassword.Pretty())
assert.Equal(t, "Refresh Token", GrantRefreshToken.Pretty())
assert.Equal(t, "Authorization Code", GrantAuthorizationCode.Pretty())
assert.Equal(t, "JWT Bearer Assertion", GrantJwtBearer.Pretty())
assert.Equal(t, "SAML2 Bearer Assertion", GrantSamlBearer.Pretty())
}
func TestGrantType_Equal(t *testing.T) {
assert.True(t, GrantClientCredentials.Equal("Client_Credentials"))
assert.False(t, GrantClientCredentials.Equal("Client Credentials"))
assert.True(t, GrantClientCredentials.Equal("client_credentials"))
assert.False(t, GrantClientCredentials.Equal("client"))
assert.True(t, GrantUndefined.Equal(""))
assert.True(t, GrantPassword.Equal("Password"))
assert.True(t, GrantPassword.Equal("password"))
assert.False(t, GrantPassword.Equal("pass"))
}
func TestGrantType_NotEqual(t *testing.T) {
assert.False(t, GrantClientCredentials.NotEqual("Client_Credentials"))
assert.True(t, GrantClientCredentials.NotEqual("Client Credentials"))
assert.False(t, GrantClientCredentials.NotEqual("client_credentials"))
assert.True(t, GrantClientCredentials.NotEqual("client"))
assert.False(t, GrantUndefined.NotEqual(""))
assert.False(t, GrantPassword.NotEqual("Password"))
assert.False(t, GrantPassword.NotEqual("password"))
assert.True(t, GrantPassword.NotEqual("pass"))
}
func TestGrant(t *testing.T) {
assert.Equal(t, GrantUndefined, Grant(""))
assert.Equal(t, GrantClientCredentials, Grant("client credentials"))
assert.Equal(t, GrantPassword, Grant("pass"))
assert.Equal(t, GrantRefreshToken, Grant("refresh_token"))
assert.Equal(t, GrantAuthorizationCode, Grant("auth_code"))
assert.Equal(t, GrantJwtBearer, Grant("jwt-bearer"))
assert.Equal(t, GrantSamlBearer, Grant("saml"))
assert.Equal(t, GrantTokenExchange, Grant("token-exchange"))
}

View File

@@ -40,6 +40,11 @@ func TestMethodType_IsNot(t *testing.T) {
assert.Equal(t, true, MethodUndefined.IsNot(MethodDefault))
}
func TestMethodType_IsUndefined(t *testing.T) {
assert.True(t, MethodUndefined.IsUndefined())
assert.False(t, Method2FA.IsUndefined())
}
func TestMethodType_IsDefault(t *testing.T) {
assert.Equal(t, true, MethodDefault.IsDefault())
assert.Equal(t, false, MethodPersonal.IsDefault())
@@ -81,11 +86,6 @@ func TestMethod(t *testing.T) {
assert.Equal(t, Method2FA, Method("2FA"))
}
func TestMethodType_IsUnknown(t *testing.T) {
assert.True(t, MethodUndefined.IsUndefined())
assert.False(t, Method2FA.IsUndefined())
}
func TestMethodType_IsSession(t *testing.T) {
assert.True(t, MethodSession.IsSession())
assert.False(t, Method2FA.IsSession())

View File

@@ -41,6 +41,11 @@ func TestProviderType_IsNot(t *testing.T) {
assert.False(t, ProviderUndefined.IsNot(ProviderUndefined))
}
func TestProviderType_IsUndefined(t *testing.T) {
assert.True(t, ProviderUndefined.IsUndefined())
assert.False(t, ProviderLocal.IsUndefined())
}
func TestProviderType_IsRemote(t *testing.T) {
assert.False(t, ProviderLocal.IsRemote())
assert.True(t, ProviderLDAP.IsRemote())
@@ -124,11 +129,6 @@ func TestProvider(t *testing.T) {
assert.Equal(t, ProviderClientCredentials, Provider("oauth2"))
}
func TestProviderType_IsUnknown(t *testing.T) {
assert.True(t, ProviderUndefined.IsUndefined())
assert.False(t, ProviderLocal.IsUndefined())
}
func TestProviderType_IsApplication(t *testing.T) {
assert.True(t, ProviderApplication.IsApplication())
assert.False(t, ProviderLocal.IsApplication())

View File

@@ -35,7 +35,7 @@ func Handle(s string) string {
}, s)
// Empty or too long?
if s == "" || reject(s, txt.ClipUserName) {
if s == "" || reject(s, txt.ClipUsername) {
return ""
}
@@ -113,3 +113,23 @@ func Attr(s string) string {
func Password(s string) string {
return strings.TrimSpace(s)
}
// Passcode sanitizes a passcode and returns it in lowercase with all whitespace removed.
func Passcode(s string) string {
if s == "" || reject(s, txt.ClipPasscode) {
return ""
} else if s = strings.ToLower(strings.TrimSpace(s)); s == "" {
return ""
}
// Remove unwanted characters.
s = strings.Map(func(r rune) rune {
if (r < '0' || r > '9') && (r < 'a' || r > 'z') {
return -1
}
return r
}, s)
return s
}

View File

@@ -120,3 +120,15 @@ func TestPassword(t *testing.T) {
assert.Equal(t, "!#$T#)$%I#J$I", Password("!#$T#)$%I#J$I"))
})
}
func TestPasscode(t *testing.T) {
t.Run("Alnum", func(t *testing.T) {
assert.Equal(t, "fgdg5yw4y", Passcode("fgdg5yw4y "))
})
t.Run("Upper", func(t *testing.T) {
assert.Equal(t, "aabdf24245vgfrg", Passcode(" AABDF24245vgfrg "))
})
t.Run("Special", func(t *testing.T) {
assert.Equal(t, "tiji", Passcode("!#$T#)$%I#J$I"))
})
}

View File

@@ -14,6 +14,11 @@ func TypeLower(s string) string {
return Type(strings.ToLower(s))
}
// TypeLowerUnderscore converts a string to a lowercase type string and replaces spaces with underscores.
func TypeLowerUnderscore(s string) string {
return strings.ReplaceAll(TypeLower(s), " ", "_")
}
// ShortType omits invalid runes, ensures a maximum length of 8 characters, and returns the result.
func ShortType(s string) string {
return Clip(ASCII(s), ClipShortType)
@@ -23,3 +28,8 @@ func ShortType(s string) string {
func ShortTypeLower(s string) string {
return ShortType(strings.ToLower(s))
}
// ShortTypeLowerUnderscore converts a string to a short lowercase type string and replaces spaces with underscores.
func ShortTypeLowerUnderscore(s string) string {
return strings.ReplaceAll(ShortTypeLower(s), " ", "_")
}

View File

@@ -52,6 +52,21 @@ func TestTypeLower(t *testing.T) {
assert.Equal(t, ClipType, len(result))
}
func TestTypeLowerUnderscore(t *testing.T) {
t.Run("Undefined", func(t *testing.T) {
assert.Equal(t, "", TypeLowerUnderscore(" \t "))
})
t.Run("ClientCredentials", func(t *testing.T) {
assert.Equal(t, "client_credentials", TypeLowerUnderscore(" Client Credentials幸"))
})
t.Run("Clip", func(t *testing.T) {
assert.Equal(
t,
"hanzi_are_logograms_developed_for_the_writing_of_chinese!_expres",
TypeLowerUnderscore(" 幸福 Hanzi are logograms developed for the writing of Chinese! Expressions in an index may not ...!"))
})
}
func TestShortType(t *testing.T) {
result := ShortType(" 幸福 Hanzi are logograms developed for the writing of Chinese! Expressions in an index may not ...!")
assert.Equal(t, "Hanzi ar", result)
@@ -63,3 +78,17 @@ func TestShortTypeLower(t *testing.T) {
assert.Equal(t, "hanzi ar", result)
assert.Equal(t, ClipShortType, len(result))
}
func TestShortTypeLowerUnderscore(t *testing.T) {
t.Run("Undefined", func(t *testing.T) {
assert.Equal(t, "", ShortTypeLowerUnderscore(" \t "))
})
t.Run("ClientCredentials", func(t *testing.T) {
assert.Equal(t, "client_c", ShortTypeLowerUnderscore(" Client Credentials幸"))
})
t.Run("Clip", func(t *testing.T) {
assert.Equal(t,
"hanzi_ar",
ShortTypeLowerUnderscore(" 幸福 Hanzi are logograms developed for the writing of Chinese! Expressions in an index may not ...!"))
})
}

View File

@@ -8,10 +8,11 @@ const (
Ellipsis = "…"
ClipCountry = 2
ClipRole = 32
ClipPasscode = 36
ClipKeyword = 40
ClipIP = 48
ClipRealm = 64
ClipUserName = 64
ClipUsername = 64
ClipPassword = 72
ClipSlug = 80
ClipCategory = 100