diff --git a/frontend/src/common/session.js b/frontend/src/common/session.js index 097160fc7..f90def3c4 100644 --- a/frontend/src/common/session.js +++ b/frontend/src/common/session.js @@ -403,13 +403,13 @@ export default class Session { // Use a static auth token in public mode, as no additional authentication is required. this.setAuthToken(PublicAuthToken); this.setId(PublicSessionID); - return Api.get("session/" + this.getId()).then((resp) => { + return Api.get("session").then((resp) => { this.setResp(resp); return Promise.resolve(); }); } else if (this.isAuthenticated()) { // Check the auth token by fetching the client session data from the API. - return Api.get("session/" + this.getId()) + return Api.get("session") .then((resp) => { this.setResp(resp); return Promise.resolve(); @@ -452,7 +452,7 @@ export default class Session { logout(noRedirect) { if (this.isAuthenticated()) { - return Api.delete("session/" + this.getId()) + return Api.delete("session") .then(() => { return this.onLogout(noRedirect); }) diff --git a/frontend/src/locales/af.po b/frontend/src/locales/af.po index 3d501de61..280cfcd40 100644 --- a/frontend/src/locales/af.po +++ b/frontend/src/locales/af.po @@ -658,8 +658,8 @@ msgstr "Ontfout logs" #: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100 #: src/model/session.js:61 src/options/admin.js:44 src/options/admin.js:45 -#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:73 -#: src/options/admin.js:92 src/options/options.js:313 +#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:74 +#: src/options/admin.js:93 src/options/options.js:313 #: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142 msgid "Default" msgstr "Verstek" @@ -1343,7 +1343,7 @@ msgstr "Laaste sinkronisering" msgid "Latitude" msgstr "Breedtegraad" -#: src/options/admin.js:49 src/options/admin.js:81 +#: src/options/admin.js:49 src/options/admin.js:82 msgid "LDAP/AD" msgstr "LDAP/AD" @@ -1408,8 +1408,8 @@ msgstr "Leef" msgid "Live Photos" msgstr "Regstreekse Foto's" -#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77 -#: src/options/admin.js:96 +#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78 +#: src/options/admin.js:97 msgid "Local" msgstr "Plaaslik" @@ -1655,7 +1655,7 @@ msgstr "Geen waarskuwings of foute wat hierdie sleutelwoord bevat nie. Let daaro msgid "Non-photographic and low-quality images require a review before they appear in search results." msgstr "Nie-fotografiese en lae kwaliteit prente vereis 'n hersiening voordat dit in soekresultate verskyn." -#: src/options/admin.js:52 src/options/admin.js:85 src/options/admin.js:100 +#: src/options/admin.js:52 src/options/admin.js:86 src/options/admin.js:101 #: src/options/options.js:293 src/options/options.js:389 msgid "None" msgstr "Geen" @@ -2157,7 +2157,7 @@ msgstr "Diens-URL" msgid "Services" msgstr "Dienste" -#: src/model/session.js:98 +#: src/model/session.js:98 src/options/admin.js:62 msgid "Session" msgstr "Sessie" diff --git a/frontend/src/locales/ar.po b/frontend/src/locales/ar.po index 563d2d5df..564ce30fe 100644 --- a/frontend/src/locales/ar.po +++ b/frontend/src/locales/ar.po @@ -661,8 +661,8 @@ msgstr "سجلات التصحيح" #: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100 #: src/model/session.js:61 src/options/admin.js:44 src/options/admin.js:45 -#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:73 -#: src/options/admin.js:92 src/options/options.js:313 +#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:74 +#: src/options/admin.js:93 src/options/options.js:313 #: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142 msgid "Default" msgstr "تقصير" @@ -1346,7 +1346,7 @@ msgstr "آخر مزامنة" msgid "Latitude" msgstr "خط العرض" -#: src/options/admin.js:49 src/options/admin.js:81 +#: src/options/admin.js:49 src/options/admin.js:82 msgid "LDAP/AD" msgstr "LDAP / AD" @@ -1411,8 +1411,8 @@ msgstr "يعيش" msgid "Live Photos" msgstr "Live Photos" -#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77 -#: src/options/admin.js:96 +#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78 +#: src/options/admin.js:97 msgid "Local" msgstr "محلي" @@ -1658,7 +1658,7 @@ msgstr "لا تحذيرات أو خطأ يحتوي على هذه الكلمة ا msgid "Non-photographic and low-quality images require a review before they appear in search results." msgstr "تتطلب الصور غير الفوتوغرافية وذات الجودة المنخفضة المراجعة قبل ظهورها في نتائج البحث." -#: src/options/admin.js:52 src/options/admin.js:85 src/options/admin.js:100 +#: src/options/admin.js:52 src/options/admin.js:86 src/options/admin.js:101 #: src/options/options.js:293 src/options/options.js:389 msgid "None" msgstr "لا أحد" @@ -2160,7 +2160,7 @@ msgstr "URL الخدمة" msgid "Services" msgstr "خدمات" -#: src/model/session.js:98 +#: src/model/session.js:98 src/options/admin.js:62 msgid "Session" msgstr "حصة" diff --git a/frontend/src/locales/be.po b/frontend/src/locales/be.po index 1d5c60ab2..2e13287bd 100644 --- a/frontend/src/locales/be.po +++ b/frontend/src/locales/be.po @@ -658,8 +658,8 @@ msgstr "Журналы адладкі" #: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100 #: src/model/session.js:61 src/options/admin.js:44 src/options/admin.js:45 -#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:73 -#: src/options/admin.js:92 src/options/options.js:313 +#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:74 +#: src/options/admin.js:93 src/options/options.js:313 #: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142 msgid "Default" msgstr "Па змаўчанні" @@ -1343,7 +1343,7 @@ msgstr "Апошняя сінхранізацыя" msgid "Latitude" msgstr "Шырата" -#: src/options/admin.js:49 src/options/admin.js:81 +#: src/options/admin.js:49 src/options/admin.js:82 msgid "LDAP/AD" msgstr "LDAP/AD" @@ -1408,8 +1408,8 @@ msgstr "жыць" msgid "Live Photos" msgstr "Жывыя фатаграфіі" -#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77 -#: src/options/admin.js:96 +#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78 +#: src/options/admin.js:97 msgid "Local" msgstr "Мясцовы" @@ -1655,7 +1655,7 @@ msgstr "Няма папярэджанняў або памылак з гэтым msgid "Non-photographic and low-quality images require a review before they appear in search results." msgstr "Нефатаграфічныя і нізкаякасныя выявы патрабуюць праверкі, перш чым яны з'явяцца ў выніках пошуку." -#: src/options/admin.js:52 src/options/admin.js:85 src/options/admin.js:100 +#: src/options/admin.js:52 src/options/admin.js:86 src/options/admin.js:101 #: src/options/options.js:293 src/options/options.js:389 msgid "None" msgstr "Няма" @@ -2157,7 +2157,7 @@ msgstr "URL службы" msgid "Services" msgstr "Паслугі" -#: src/model/session.js:98 +#: src/model/session.js:98 src/options/admin.js:62 msgid "Session" msgstr "сесія" diff --git a/frontend/src/locales/bg.po b/frontend/src/locales/bg.po index b595d1450..aada956bb 100644 --- a/frontend/src/locales/bg.po +++ b/frontend/src/locales/bg.po @@ -661,8 +661,8 @@ msgstr "Протоколи за отработване" #: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100 #: src/model/session.js:61 src/options/admin.js:44 src/options/admin.js:45 -#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:73 -#: src/options/admin.js:92 src/options/options.js:313 +#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:74 +#: src/options/admin.js:93 src/options/options.js:313 #: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142 msgid "Default" msgstr "По подразбиране" @@ -1346,7 +1346,7 @@ msgstr "Синхронизиране" msgid "Latitude" msgstr "Географска ширина" -#: src/options/admin.js:49 src/options/admin.js:81 +#: src/options/admin.js:49 src/options/admin.js:82 msgid "LDAP/AD" msgstr "LDAP/AD" @@ -1411,8 +1411,8 @@ msgstr "На живо" msgid "Live Photos" msgstr "Снимки" -#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77 -#: src/options/admin.js:96 +#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78 +#: src/options/admin.js:97 msgid "Local" msgstr "Местни" @@ -1658,7 +1658,7 @@ msgstr "Няма предупреждения или грешки, съдърж msgid "Non-photographic and low-quality images require a review before they appear in search results." msgstr "Нефотографските изображения и изображенията с ниско качество изискват преглед, преди да се появят в резултатите от търсенето." -#: src/options/admin.js:52 src/options/admin.js:85 src/options/admin.js:100 +#: src/options/admin.js:52 src/options/admin.js:86 src/options/admin.js:101 #: src/options/options.js:293 src/options/options.js:389 msgid "None" msgstr "Няма" @@ -2160,7 +2160,7 @@ msgstr "URL адрес на услугата" msgid "Services" msgstr "URL адрес на услугата" -#: src/model/session.js:98 +#: src/model/session.js:98 src/options/admin.js:62 msgid "Session" msgstr "Сесия" diff --git a/frontend/src/locales/ca.po b/frontend/src/locales/ca.po index b14145cab..c7dd06d10 100644 --- a/frontend/src/locales/ca.po +++ b/frontend/src/locales/ca.po @@ -661,8 +661,8 @@ msgstr "Registres de depuració" #: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100 #: src/model/session.js:61 src/options/admin.js:44 src/options/admin.js:45 -#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:73 -#: src/options/admin.js:92 src/options/options.js:313 +#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:74 +#: src/options/admin.js:93 src/options/options.js:313 #: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142 msgid "Default" msgstr "Per defecte" @@ -1346,7 +1346,7 @@ msgstr "Última sincronització" msgid "Latitude" msgstr "Latitud" -#: src/options/admin.js:49 src/options/admin.js:81 +#: src/options/admin.js:49 src/options/admin.js:82 msgid "LDAP/AD" msgstr "LDAP/AD" @@ -1411,8 +1411,8 @@ msgstr "En viu" msgid "Live Photos" msgstr "Fotos en directe" -#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77 -#: src/options/admin.js:96 +#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78 +#: src/options/admin.js:97 msgid "Local" msgstr "Local" @@ -1658,7 +1658,7 @@ msgstr "No hi ha cap advertiment ni error que contingui aquesta paraula clau. Ti msgid "Non-photographic and low-quality images require a review before they appear in search results." msgstr "Les imatges no fotogràfiques i de baixa qualitat requereixen una revisió abans que apareguin als resultats de la cerca." -#: src/options/admin.js:52 src/options/admin.js:85 src/options/admin.js:100 +#: src/options/admin.js:52 src/options/admin.js:86 src/options/admin.js:101 #: src/options/options.js:293 src/options/options.js:389 msgid "None" msgstr "Cap" @@ -2160,7 +2160,7 @@ msgstr "URL del servei" msgid "Services" msgstr "Serveis" -#: src/model/session.js:98 +#: src/model/session.js:98 src/options/admin.js:62 msgid "Session" msgstr "Sessió" diff --git a/frontend/src/locales/cs.po b/frontend/src/locales/cs.po index 35071caa3..24c755b0f 100644 --- a/frontend/src/locales/cs.po +++ b/frontend/src/locales/cs.po @@ -661,8 +661,8 @@ msgstr "Protokoly ladění" #: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100 #: src/model/session.js:61 src/options/admin.js:44 src/options/admin.js:45 -#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:73 -#: src/options/admin.js:92 src/options/options.js:313 +#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:74 +#: src/options/admin.js:93 src/options/options.js:313 #: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142 msgid "Default" msgstr "Výchozí" @@ -1346,7 +1346,7 @@ msgstr "Poslední synchronizace" msgid "Latitude" msgstr "Zeměpisná šířka" -#: src/options/admin.js:49 src/options/admin.js:81 +#: src/options/admin.js:49 src/options/admin.js:82 msgid "LDAP/AD" msgstr "LDAP/AD" @@ -1411,8 +1411,8 @@ msgstr "Živé" msgid "Live Photos" msgstr "Živé fotografie" -#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77 -#: src/options/admin.js:96 +#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78 +#: src/options/admin.js:97 msgid "Local" msgstr "Místní" @@ -1658,7 +1658,7 @@ msgstr "Žádná varování nebo chyba obsahující toto klíčové slovo. Mějt msgid "Non-photographic and low-quality images require a review before they appear in search results." msgstr "Nefotografické obrázky a snímky nízké kvality vyžadují kontrolu, než se objeví ve výsledcích vyhledávání." -#: src/options/admin.js:52 src/options/admin.js:85 src/options/admin.js:100 +#: src/options/admin.js:52 src/options/admin.js:86 src/options/admin.js:101 #: src/options/options.js:293 src/options/options.js:389 msgid "None" msgstr "Žádné" @@ -2160,7 +2160,7 @@ msgstr "URL služby" msgid "Services" msgstr "Služby" -#: src/model/session.js:98 +#: src/model/session.js:98 src/options/admin.js:62 msgid "Session" msgstr "Relace" diff --git a/frontend/src/locales/da.po b/frontend/src/locales/da.po index 720ac7d75..c92d12e66 100644 --- a/frontend/src/locales/da.po +++ b/frontend/src/locales/da.po @@ -661,8 +661,8 @@ msgstr "Fejlfindingslog" #: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100 #: src/model/session.js:61 src/options/admin.js:44 src/options/admin.js:45 -#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:73 -#: src/options/admin.js:92 src/options/options.js:313 +#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:74 +#: src/options/admin.js:93 src/options/options.js:313 #: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142 msgid "Default" msgstr "Standard" @@ -1346,7 +1346,7 @@ msgstr "Seneste synkronisering" msgid "Latitude" msgstr "Breddegrad" -#: src/options/admin.js:49 src/options/admin.js:81 +#: src/options/admin.js:49 src/options/admin.js:82 msgid "LDAP/AD" msgstr "LDAP/AD" @@ -1411,8 +1411,8 @@ msgstr "Direkte" msgid "Live Photos" msgstr "Live-fotos" -#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77 -#: src/options/admin.js:96 +#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78 +#: src/options/admin.js:97 msgid "Local" msgstr "Lokal" @@ -1658,7 +1658,7 @@ msgstr "Ingen advarsler eller fejl, der indeholder dette nøgleord. Bemærk, at msgid "Non-photographic and low-quality images require a review before they appear in search results." msgstr "Ikke-fotografiske billeder af lav kvalitet kræver en gennemgang, før de vises i søgeresultaterne." -#: src/options/admin.js:52 src/options/admin.js:85 src/options/admin.js:100 +#: src/options/admin.js:52 src/options/admin.js:86 src/options/admin.js:101 #: src/options/options.js:293 src/options/options.js:389 msgid "None" msgstr "Ingen" @@ -2160,7 +2160,7 @@ msgstr "Service-URL" msgid "Services" msgstr "Tjenester" -#: src/model/session.js:98 +#: src/model/session.js:98 src/options/admin.js:62 msgid "Session" msgstr "Session" diff --git a/frontend/src/locales/de.po b/frontend/src/locales/de.po index acabc7535..7937aed4e 100644 --- a/frontend/src/locales/de.po +++ b/frontend/src/locales/de.po @@ -661,8 +661,8 @@ msgstr "Debug Logs" #: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100 #: src/model/session.js:61 src/options/admin.js:44 src/options/admin.js:45 -#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:73 -#: src/options/admin.js:92 src/options/options.js:313 +#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:74 +#: src/options/admin.js:93 src/options/options.js:313 #: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142 msgid "Default" msgstr "Standard" @@ -1346,7 +1346,7 @@ msgstr "Letzte Synchronisation" msgid "Latitude" msgstr "Breitengrad" -#: src/options/admin.js:49 src/options/admin.js:81 +#: src/options/admin.js:49 src/options/admin.js:82 msgid "LDAP/AD" msgstr "LDAP/AD" @@ -1411,8 +1411,8 @@ msgstr "Live" msgid "Live Photos" msgstr "Live Photos" -#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77 -#: src/options/admin.js:96 +#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78 +#: src/options/admin.js:97 msgid "Local" msgstr "Lokal" @@ -1658,7 +1658,7 @@ msgstr "Keine Warnungen oder Fehler mit diesem Suchbegriff. Bei der Suche wird z msgid "Non-photographic and low-quality images require a review before they appear in search results." msgstr "Nicht-fotografische Inhalte oder Bilder mit geringer Qualität werden erst nach einer Bestätigung in der Suche angezeigt." -#: src/options/admin.js:52 src/options/admin.js:85 src/options/admin.js:100 +#: src/options/admin.js:52 src/options/admin.js:86 src/options/admin.js:101 #: src/options/options.js:293 src/options/options.js:389 msgid "None" msgstr "Keine" @@ -2160,7 +2160,7 @@ msgstr "Dienst-URL" msgid "Services" msgstr "Dienste" -#: src/model/session.js:98 +#: src/model/session.js:98 src/options/admin.js:62 msgid "Session" msgstr "Session" diff --git a/frontend/src/locales/el.po b/frontend/src/locales/el.po index 9b4e8dfd1..070e1e0de 100644 --- a/frontend/src/locales/el.po +++ b/frontend/src/locales/el.po @@ -658,8 +658,8 @@ msgstr "Αρχεία καταγραφής σφαλμάτων" #: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100 #: src/model/session.js:61 src/options/admin.js:44 src/options/admin.js:45 -#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:73 -#: src/options/admin.js:92 src/options/options.js:313 +#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:74 +#: src/options/admin.js:93 src/options/options.js:313 #: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142 msgid "Default" msgstr "Προεπιλογή" @@ -1343,7 +1343,7 @@ msgstr "Τελευταίος συγχρονισμός" msgid "Latitude" msgstr "Γεωγραφικό πλάτος" -#: src/options/admin.js:49 src/options/admin.js:81 +#: src/options/admin.js:49 src/options/admin.js:82 msgid "LDAP/AD" msgstr "LDAP/AD" @@ -1408,8 +1408,8 @@ msgstr "Ζωντανό" msgid "Live Photos" msgstr "Φωτογραφίες" -#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77 -#: src/options/admin.js:96 +#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78 +#: src/options/admin.js:97 msgid "Local" msgstr "Τοπικό" @@ -1655,7 +1655,7 @@ msgstr "Δεν υπάρχουν προειδοποιήσεις ή σφάλματ msgid "Non-photographic and low-quality images require a review before they appear in search results." msgstr "Οι μη φωτογραφικές εικόνες και οι εικόνες χαμηλής ποιότητας απαιτούν επανεξέταση προτού εμφανιστούν στα αποτελέσματα αναζήτησης." -#: src/options/admin.js:52 src/options/admin.js:85 src/options/admin.js:100 +#: src/options/admin.js:52 src/options/admin.js:86 src/options/admin.js:101 #: src/options/options.js:293 src/options/options.js:389 msgid "None" msgstr "Κανένα" @@ -2157,7 +2157,7 @@ msgstr "URL υπηρεσίας" msgid "Services" msgstr "URL υπηρεσίας" -#: src/model/session.js:98 +#: src/model/session.js:98 src/options/admin.js:62 msgid "Session" msgstr "Σύνοδος" diff --git a/frontend/src/locales/en.po b/frontend/src/locales/en.po index c7896a06e..8c50a3843 100644 --- a/frontend/src/locales/en.po +++ b/frontend/src/locales/en.po @@ -660,8 +660,8 @@ msgstr "" #: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100 #: src/model/session.js:61 src/options/admin.js:44 src/options/admin.js:45 -#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:73 -#: src/options/admin.js:92 src/options/options.js:313 +#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:74 +#: src/options/admin.js:93 src/options/options.js:313 #: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142 msgid "Default" msgstr "" @@ -1345,7 +1345,7 @@ msgstr "" msgid "Latitude" msgstr "" -#: src/options/admin.js:49 src/options/admin.js:81 +#: src/options/admin.js:49 src/options/admin.js:82 msgid "LDAP/AD" msgstr "" @@ -1410,8 +1410,8 @@ msgstr "" msgid "Live Photos" msgstr "" -#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77 -#: src/options/admin.js:96 +#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78 +#: src/options/admin.js:97 msgid "Local" msgstr "" @@ -1657,7 +1657,7 @@ msgstr "" msgid "Non-photographic and low-quality images require a review before they appear in search results." msgstr "" -#: src/options/admin.js:52 src/options/admin.js:85 src/options/admin.js:100 +#: src/options/admin.js:52 src/options/admin.js:86 src/options/admin.js:101 #: src/options/options.js:293 src/options/options.js:389 msgid "None" msgstr "" @@ -2159,7 +2159,7 @@ msgstr "" msgid "Services" msgstr "" -#: src/model/session.js:98 +#: src/model/session.js:98 src/options/admin.js:62 msgid "Session" msgstr "" diff --git a/frontend/src/locales/es.po b/frontend/src/locales/es.po index cb88dcbb3..5d0c5dcdb 100644 --- a/frontend/src/locales/es.po +++ b/frontend/src/locales/es.po @@ -661,8 +661,8 @@ msgstr "Registros de depuración" #: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100 #: src/model/session.js:61 src/options/admin.js:44 src/options/admin.js:45 -#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:73 -#: src/options/admin.js:92 src/options/options.js:313 +#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:74 +#: src/options/admin.js:93 src/options/options.js:313 #: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142 msgid "Default" msgstr "Por defecto" @@ -1346,7 +1346,7 @@ msgstr "Última sincronización" msgid "Latitude" msgstr "Latitud" -#: src/options/admin.js:49 src/options/admin.js:81 +#: src/options/admin.js:49 src/options/admin.js:82 msgid "LDAP/AD" msgstr "LDAP/AD" @@ -1412,8 +1412,8 @@ msgstr "En vivo" msgid "Live Photos" msgstr "Fotos en vivo" -#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77 -#: src/options/admin.js:96 +#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78 +#: src/options/admin.js:97 msgid "Local" msgstr "Local" @@ -1659,7 +1659,7 @@ msgstr "No hay advertencias ni errores que contengan esta palabra clave. Tenga e msgid "Non-photographic and low-quality images require a review before they appear in search results." msgstr "Las imágenes no fotográficas y de baja calidad requieren una revisión antes que aparezcan en los resultados de la búsqueda." -#: src/options/admin.js:52 src/options/admin.js:85 src/options/admin.js:100 +#: src/options/admin.js:52 src/options/admin.js:86 src/options/admin.js:101 #: src/options/options.js:293 src/options/options.js:389 msgid "None" msgstr "Ninguno" @@ -2161,7 +2161,7 @@ msgstr "URL del servicio" msgid "Services" msgstr "Servicios" -#: src/model/session.js:98 +#: src/model/session.js:98 src/options/admin.js:62 msgid "Session" msgstr "Sesión" diff --git a/frontend/src/locales/et.po b/frontend/src/locales/et.po index 5ba78dd32..6cede4d2d 100644 --- a/frontend/src/locales/et.po +++ b/frontend/src/locales/et.po @@ -658,8 +658,8 @@ msgstr "Tõrkeotsingu logid" #: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100 #: src/model/session.js:61 src/options/admin.js:44 src/options/admin.js:45 -#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:73 -#: src/options/admin.js:92 src/options/options.js:313 +#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:74 +#: src/options/admin.js:93 src/options/options.js:313 #: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142 msgid "Default" msgstr "Vaikimisi" @@ -1343,7 +1343,7 @@ msgstr "Viimane sünkroonimine" msgid "Latitude" msgstr "Laiuskraad" -#: src/options/admin.js:49 src/options/admin.js:81 +#: src/options/admin.js:49 src/options/admin.js:82 msgid "LDAP/AD" msgstr "LDAP/AD" @@ -1408,8 +1408,8 @@ msgstr "Live" msgid "Live Photos" msgstr "Liikuvad fotod" -#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77 -#: src/options/admin.js:96 +#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78 +#: src/options/admin.js:97 msgid "Local" msgstr "Kohalik" @@ -1655,7 +1655,7 @@ msgstr "Seda märksõna sisaldavaid hoiatusi või vigu ei ole. Pane tähele, et msgid "Non-photographic and low-quality images require a review before they appear in search results." msgstr "Mittefotograafilised ja madala kvaliteediga pildid tuleb üle vaadata, enne kui nad otsingutulemustes ilmuvad." -#: src/options/admin.js:52 src/options/admin.js:85 src/options/admin.js:100 +#: src/options/admin.js:52 src/options/admin.js:86 src/options/admin.js:101 #: src/options/options.js:293 src/options/options.js:389 msgid "None" msgstr "Puudub" @@ -2157,7 +2157,7 @@ msgstr "Teenuse URL" msgid "Services" msgstr "Teenused" -#: src/model/session.js:98 +#: src/model/session.js:98 src/options/admin.js:62 msgid "Session" msgstr "Sessioon" diff --git a/frontend/src/locales/eu.po b/frontend/src/locales/eu.po index 3bd07623c..4ca9f9e01 100644 --- a/frontend/src/locales/eu.po +++ b/frontend/src/locales/eu.po @@ -658,8 +658,8 @@ msgstr "Arazte-erregistroak" #: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100 #: src/model/session.js:61 src/options/admin.js:44 src/options/admin.js:45 -#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:73 -#: src/options/admin.js:92 src/options/options.js:313 +#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:74 +#: src/options/admin.js:93 src/options/options.js:313 #: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142 msgid "Default" msgstr "Lehenetsia" @@ -1343,7 +1343,7 @@ msgstr "Azken sinkronizazioa" msgid "Latitude" msgstr "Latitudea" -#: src/options/admin.js:49 src/options/admin.js:81 +#: src/options/admin.js:49 src/options/admin.js:82 msgid "LDAP/AD" msgstr "LDAP/AD" @@ -1408,8 +1408,8 @@ msgstr "Zuzenean" msgid "Live Photos" msgstr "Zuzeneko Argazkiak" -#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77 -#: src/options/admin.js:96 +#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78 +#: src/options/admin.js:97 msgid "Local" msgstr "Tokikoa" @@ -1655,7 +1655,7 @@ msgstr "Ez dago gako-hitz hau duen abisurik edo errorerik. Kontuan izan bilaketa msgid "Non-photographic and low-quality images require a review before they appear in search results." msgstr "Argazkiak ez diren eta kalitate baxuko irudiak berrikusi behar dira bilaketa-emaitzetan agertu aurretik." -#: src/options/admin.js:52 src/options/admin.js:85 src/options/admin.js:100 +#: src/options/admin.js:52 src/options/admin.js:86 src/options/admin.js:101 #: src/options/options.js:293 src/options/options.js:389 msgid "None" msgstr "Bat ere ez" @@ -2157,7 +2157,7 @@ msgstr "Zerbitzuaren URLa" msgid "Services" msgstr "Zerbitzuak" -#: src/model/session.js:98 +#: src/model/session.js:98 src/options/admin.js:62 msgid "Session" msgstr "Saioa" diff --git a/frontend/src/locales/fa.po b/frontend/src/locales/fa.po index c39f8884d..b86bbf875 100644 --- a/frontend/src/locales/fa.po +++ b/frontend/src/locales/fa.po @@ -661,8 +661,8 @@ msgstr "گزارش‌های اشکال زدایی" #: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100 #: src/model/session.js:61 src/options/admin.js:44 src/options/admin.js:45 -#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:73 -#: src/options/admin.js:92 src/options/options.js:313 +#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:74 +#: src/options/admin.js:93 src/options/options.js:313 #: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142 msgid "Default" msgstr "پیشفرض" @@ -1346,7 +1346,7 @@ msgstr "آخرین همگام سازی" msgid "Latitude" msgstr "عرض جغرافیایی" -#: src/options/admin.js:49 src/options/admin.js:81 +#: src/options/admin.js:49 src/options/admin.js:82 msgid "LDAP/AD" msgstr "LDAP/AD" @@ -1411,8 +1411,8 @@ msgstr "زنده" msgid "Live Photos" msgstr "تصاویر" -#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77 -#: src/options/admin.js:96 +#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78 +#: src/options/admin.js:97 msgid "Local" msgstr "محلی" @@ -1658,7 +1658,7 @@ msgstr "هیچ هشدار یا خطایی حاوی این کلمه کلیدی ن msgid "Non-photographic and low-quality images require a review before they appear in search results." msgstr "تصاویر غیرعکاسی و با کیفیت پایین قبل از اینکه در نتایج جستجو ظاهر شوند نیاز به بررسی دارند." -#: src/options/admin.js:52 src/options/admin.js:85 src/options/admin.js:100 +#: src/options/admin.js:52 src/options/admin.js:86 src/options/admin.js:101 #: src/options/options.js:293 src/options/options.js:389 msgid "None" msgstr "هیچ یک" @@ -2160,7 +2160,7 @@ msgstr "URL سرویس" msgid "Services" msgstr "URL سرویس" -#: src/model/session.js:98 +#: src/model/session.js:98 src/options/admin.js:62 msgid "Session" msgstr "جلسه" diff --git a/frontend/src/locales/fi.po b/frontend/src/locales/fi.po index c840a0e44..f123f1c5f 100644 --- a/frontend/src/locales/fi.po +++ b/frontend/src/locales/fi.po @@ -658,8 +658,8 @@ msgstr "Vianmäärityslokit" #: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100 #: src/model/session.js:61 src/options/admin.js:44 src/options/admin.js:45 -#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:73 -#: src/options/admin.js:92 src/options/options.js:313 +#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:74 +#: src/options/admin.js:93 src/options/options.js:313 #: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142 msgid "Default" msgstr "Oletus" @@ -1343,7 +1343,7 @@ msgstr "Viimeisin synkronointi" msgid "Latitude" msgstr "Leveysaste" -#: src/options/admin.js:49 src/options/admin.js:81 +#: src/options/admin.js:49 src/options/admin.js:82 msgid "LDAP/AD" msgstr "LDAP/AD" @@ -1408,8 +1408,8 @@ msgstr "Live Photo -kuva" msgid "Live Photos" msgstr "Kuvat" -#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77 -#: src/options/admin.js:96 +#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78 +#: src/options/admin.js:97 msgid "Local" msgstr "Paikallinen" @@ -1655,7 +1655,7 @@ msgstr "Ei varoituksia tai virheitä, jotka sisältävät tämän avainsanan. Hu msgid "Non-photographic and low-quality images require a review before they appear in search results." msgstr "Muut kuin valokuvat ja heikkolaatuiset kuvat edellyttävät tarkistusta, ennen kuin ne näkyvät hakutuloksissa." -#: src/options/admin.js:52 src/options/admin.js:85 src/options/admin.js:100 +#: src/options/admin.js:52 src/options/admin.js:86 src/options/admin.js:101 #: src/options/options.js:293 src/options/options.js:389 msgid "None" msgstr "Ei mitään" @@ -2157,7 +2157,7 @@ msgstr "Palvelun URL-osoite" msgid "Services" msgstr "Palvelun URL-osoite" -#: src/model/session.js:98 +#: src/model/session.js:98 src/options/admin.js:62 msgid "Session" msgstr "Istunto" diff --git a/frontend/src/locales/fr.po b/frontend/src/locales/fr.po index faf5c21f0..18b358e01 100644 --- a/frontend/src/locales/fr.po +++ b/frontend/src/locales/fr.po @@ -661,8 +661,8 @@ msgstr "Journaux de débogage" #: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100 #: src/model/session.js:61 src/options/admin.js:44 src/options/admin.js:45 -#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:73 -#: src/options/admin.js:92 src/options/options.js:313 +#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:74 +#: src/options/admin.js:93 src/options/options.js:313 #: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142 msgid "Default" msgstr "Valeur par défaut" @@ -1346,7 +1346,7 @@ msgstr "Dernière synchro" msgid "Latitude" msgstr "Latitude" -#: src/options/admin.js:49 src/options/admin.js:81 +#: src/options/admin.js:49 src/options/admin.js:82 msgid "LDAP/AD" msgstr "LDAP/AD" @@ -1411,8 +1411,8 @@ msgstr "Live" msgid "Live Photos" msgstr "Photos en direct" -#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77 -#: src/options/admin.js:96 +#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78 +#: src/options/admin.js:97 msgid "Local" msgstr "Locale" @@ -1658,7 +1658,7 @@ msgstr "Aucun avertissement ou erreur contenant ce mot-clé. Notez que la recher msgid "Non-photographic and low-quality images require a review before they appear in search results." msgstr "Les images non photographiques ou de mauvaise qualité doivent faire l'objet d'un examen avant d'apparaître dans les résultats de recherche." -#: src/options/admin.js:52 src/options/admin.js:85 src/options/admin.js:100 +#: src/options/admin.js:52 src/options/admin.js:86 src/options/admin.js:101 #: src/options/options.js:293 src/options/options.js:389 msgid "None" msgstr "Aucun" @@ -2160,7 +2160,7 @@ msgstr "URL du service" msgid "Services" msgstr "Services" -#: src/model/session.js:98 +#: src/model/session.js:98 src/options/admin.js:62 msgid "Session" msgstr "Session" diff --git a/frontend/src/locales/he.po b/frontend/src/locales/he.po index 5c8516926..4c2445125 100644 --- a/frontend/src/locales/he.po +++ b/frontend/src/locales/he.po @@ -661,8 +661,8 @@ msgstr "Debug Logs" #: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100 #: src/model/session.js:61 src/options/admin.js:44 src/options/admin.js:45 -#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:73 -#: src/options/admin.js:92 src/options/options.js:313 +#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:74 +#: src/options/admin.js:93 src/options/options.js:313 #: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142 msgid "Default" msgstr "ברירת מחדל" @@ -1346,7 +1346,7 @@ msgstr "סנכרון אחרון" msgid "Latitude" msgstr "קו רוחב" -#: src/options/admin.js:49 src/options/admin.js:81 +#: src/options/admin.js:49 src/options/admin.js:82 msgid "LDAP/AD" msgstr "LDAP/AD" @@ -1411,8 +1411,8 @@ msgstr "חי" msgid "Live Photos" msgstr "תמונות חיות" -#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77 -#: src/options/admin.js:96 +#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78 +#: src/options/admin.js:97 msgid "Local" msgstr "מְקוֹמִי" @@ -1658,7 +1658,7 @@ msgstr "אין אזהרות או שגיאות המכילות מילת מפתח msgid "Non-photographic and low-quality images require a review before they appear in search results." msgstr "תמונות שאינן נראות צילום או באיכות נמוכה דורשות בדיקה לפני שהן מופיעות בתוצאות החיפוש." -#: src/options/admin.js:52 src/options/admin.js:85 src/options/admin.js:100 +#: src/options/admin.js:52 src/options/admin.js:86 src/options/admin.js:101 #: src/options/options.js:293 src/options/options.js:389 msgid "None" msgstr "ללא" @@ -2160,7 +2160,7 @@ msgstr "נתיב השרות" msgid "Services" msgstr "שירותים" -#: src/model/session.js:98 +#: src/model/session.js:98 src/options/admin.js:62 msgid "Session" msgstr "מוֹשָׁב" diff --git a/frontend/src/locales/hi.po b/frontend/src/locales/hi.po index 79acb8131..83e06cfab 100644 --- a/frontend/src/locales/hi.po +++ b/frontend/src/locales/hi.po @@ -661,8 +661,8 @@ msgstr "दोषमार्जन लॉग" #: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100 #: src/model/session.js:61 src/options/admin.js:44 src/options/admin.js:45 -#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:73 -#: src/options/admin.js:92 src/options/options.js:313 +#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:74 +#: src/options/admin.js:93 src/options/options.js:313 #: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142 msgid "Default" msgstr "चूक" @@ -1346,7 +1346,7 @@ msgstr "अंतिम सिंक" msgid "Latitude" msgstr "अक्षांश" -#: src/options/admin.js:49 src/options/admin.js:81 +#: src/options/admin.js:49 src/options/admin.js:82 msgid "LDAP/AD" msgstr "एलडीएपी/एडी" @@ -1411,8 +1411,8 @@ msgstr "लाइव" msgid "Live Photos" msgstr "लाइव तस्वीरें" -#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77 -#: src/options/admin.js:96 +#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78 +#: src/options/admin.js:97 msgid "Local" msgstr "स्थानीय" @@ -1658,7 +1658,7 @@ msgstr "इस कीवर्ड से कोई चेतावनी या msgid "Non-photographic and low-quality images require a review before they appear in search results." msgstr "गैर-फोटोग्राफिक और निम्न-गुणवत्ता वाली छवियों को खोज परिणामों में प्रदर्शित होने से पहले समीक्षा की आवश्यकता होती है।" -#: src/options/admin.js:52 src/options/admin.js:85 src/options/admin.js:100 +#: src/options/admin.js:52 src/options/admin.js:86 src/options/admin.js:101 #: src/options/options.js:293 src/options/options.js:389 msgid "None" msgstr "कोई नहीं" @@ -2160,7 +2160,7 @@ msgstr "सेवा URL" msgid "Services" msgstr "सेवाएं" -#: src/model/session.js:98 +#: src/model/session.js:98 src/options/admin.js:62 msgid "Session" msgstr "सत्र" diff --git a/frontend/src/locales/hr.po b/frontend/src/locales/hr.po index ebe57c2f0..aaf579fcd 100644 --- a/frontend/src/locales/hr.po +++ b/frontend/src/locales/hr.po @@ -661,8 +661,8 @@ msgstr "Zapisnici otklanjanja pogrešaka" #: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100 #: src/model/session.js:61 src/options/admin.js:44 src/options/admin.js:45 -#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:73 -#: src/options/admin.js:92 src/options/options.js:313 +#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:74 +#: src/options/admin.js:93 src/options/options.js:313 #: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142 msgid "Default" msgstr "Zadano" @@ -1346,7 +1346,7 @@ msgstr "Zadnja sinkronizacija" msgid "Latitude" msgstr "Zemljopisna širina" -#: src/options/admin.js:49 src/options/admin.js:81 +#: src/options/admin.js:49 src/options/admin.js:82 msgid "LDAP/AD" msgstr "LDAP/AD" @@ -1411,8 +1411,8 @@ msgstr "Uživo" msgid "Live Photos" msgstr "Slike" -#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77 -#: src/options/admin.js:96 +#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78 +#: src/options/admin.js:97 msgid "Local" msgstr "Lokalni" @@ -1658,7 +1658,7 @@ msgstr "Nema upozorenja ili pogreške koje sadrže ovu ključnu riječ. Imajte n msgid "Non-photographic and low-quality images require a review before they appear in search results." msgstr "Nefotografske slike i slike niske kvalitete zahtijevaju pregled prije nego što se pojave u rezultatima pretraživanja." -#: src/options/admin.js:52 src/options/admin.js:85 src/options/admin.js:100 +#: src/options/admin.js:52 src/options/admin.js:86 src/options/admin.js:101 #: src/options/options.js:293 src/options/options.js:389 msgid "None" msgstr "Nijedan" @@ -2160,7 +2160,7 @@ msgstr "URL usluge" msgid "Services" msgstr "URL usluge" -#: src/model/session.js:98 +#: src/model/session.js:98 src/options/admin.js:62 msgid "Session" msgstr "Sjednica" diff --git a/frontend/src/locales/hu.po b/frontend/src/locales/hu.po index b2492fc6e..e6d60204d 100644 --- a/frontend/src/locales/hu.po +++ b/frontend/src/locales/hu.po @@ -660,8 +660,8 @@ msgstr "Hibakeresési naplók" #: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100 #: src/model/session.js:61 src/options/admin.js:44 src/options/admin.js:45 -#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:73 -#: src/options/admin.js:92 src/options/options.js:313 +#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:74 +#: src/options/admin.js:93 src/options/options.js:313 #: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142 msgid "Default" msgstr "Alapértelmezett" @@ -1345,7 +1345,7 @@ msgstr "Utolsó szinkronizálás" msgid "Latitude" msgstr "Szélességi kör" -#: src/options/admin.js:49 src/options/admin.js:81 +#: src/options/admin.js:49 src/options/admin.js:82 msgid "LDAP/AD" msgstr "LDAP/AD" @@ -1410,8 +1410,8 @@ msgstr "Élő" msgid "Live Photos" msgstr "Fényképek" -#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77 -#: src/options/admin.js:96 +#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78 +#: src/options/admin.js:97 msgid "Local" msgstr "Helyi" @@ -1657,7 +1657,7 @@ msgstr "Nincsenek figyelmeztetések vagy hibák, amelyek ezt a kulcsszót tartal msgid "Non-photographic and low-quality images require a review before they appear in search results." msgstr "A rossz minőségű képek ellenörzésre kerülnek, mielőtt megjelennének a keresési eredmények között." -#: src/options/admin.js:52 src/options/admin.js:85 src/options/admin.js:100 +#: src/options/admin.js:52 src/options/admin.js:86 src/options/admin.js:101 #: src/options/options.js:293 src/options/options.js:389 msgid "None" msgstr "Egyik sem" @@ -2159,7 +2159,7 @@ msgstr "Szolgáltatás URL-je" msgid "Services" msgstr "Szolgáltatások" -#: src/model/session.js:98 +#: src/model/session.js:98 src/options/admin.js:62 msgid "Session" msgstr "Ülés" diff --git a/frontend/src/locales/id.po b/frontend/src/locales/id.po index 188eaeb3f..78679248a 100644 --- a/frontend/src/locales/id.po +++ b/frontend/src/locales/id.po @@ -661,8 +661,8 @@ msgstr "Log Debug" #: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100 #: src/model/session.js:61 src/options/admin.js:44 src/options/admin.js:45 -#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:73 -#: src/options/admin.js:92 src/options/options.js:313 +#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:74 +#: src/options/admin.js:93 src/options/options.js:313 #: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142 msgid "Default" msgstr "Bawaan" @@ -1346,7 +1346,7 @@ msgstr "Sinkronisasi Terakhir" msgid "Latitude" msgstr "Lintang" -#: src/options/admin.js:49 src/options/admin.js:81 +#: src/options/admin.js:49 src/options/admin.js:82 msgid "LDAP/AD" msgstr "LDAP/AD" @@ -1411,8 +1411,8 @@ msgstr "Langsung" msgid "Live Photos" msgstr "Foto" -#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77 -#: src/options/admin.js:96 +#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78 +#: src/options/admin.js:97 msgid "Local" msgstr "Lokal" @@ -1658,7 +1658,7 @@ msgstr "Tidak ada peringatan atau kesalahan yang mengandung kata kunci ini. Perh msgid "Non-photographic and low-quality images require a review before they appear in search results." msgstr "Gambar non-fotografis dan berkualitas rendah memerlukan peninjauan sebelum muncul di hasil pencarian." -#: src/options/admin.js:52 src/options/admin.js:85 src/options/admin.js:100 +#: src/options/admin.js:52 src/options/admin.js:86 src/options/admin.js:101 #: src/options/options.js:293 src/options/options.js:389 msgid "None" msgstr "Tidak ada" @@ -2160,7 +2160,7 @@ msgstr "URL Layanan" msgid "Services" msgstr "URL Layanan" -#: src/model/session.js:98 +#: src/model/session.js:98 src/options/admin.js:62 msgid "Session" msgstr "Sesi" diff --git a/frontend/src/locales/it.po b/frontend/src/locales/it.po index dce472fbc..299344414 100644 --- a/frontend/src/locales/it.po +++ b/frontend/src/locales/it.po @@ -661,8 +661,8 @@ msgstr "Registri di debug" #: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100 #: src/model/session.js:61 src/options/admin.js:44 src/options/admin.js:45 -#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:73 -#: src/options/admin.js:92 src/options/options.js:313 +#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:74 +#: src/options/admin.js:93 src/options/options.js:313 #: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142 msgid "Default" msgstr "Predefinito" @@ -1346,7 +1346,7 @@ msgstr "Ultima sincronizzazione" msgid "Latitude" msgstr "Latitudine" -#: src/options/admin.js:49 src/options/admin.js:81 +#: src/options/admin.js:49 src/options/admin.js:82 msgid "LDAP/AD" msgstr "LDAP/AD" @@ -1411,8 +1411,8 @@ msgstr "Live" msgid "Live Photos" msgstr "Foto dal vivo" -#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77 -#: src/options/admin.js:96 +#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78 +#: src/options/admin.js:97 msgid "Local" msgstr "Locale" @@ -1658,7 +1658,7 @@ msgstr "Nessun warning o errore contiene questa parola chiave. Tieni presente ch msgid "Non-photographic and low-quality images require a review before they appear in search results." msgstr "Le immagini non fotografiche e di bassa qualità richiedono una revisione prima di essere visualizzate nei risultati di ricerca." -#: src/options/admin.js:52 src/options/admin.js:85 src/options/admin.js:100 +#: src/options/admin.js:52 src/options/admin.js:86 src/options/admin.js:101 #: src/options/options.js:293 src/options/options.js:389 msgid "None" msgstr "Nessuno" @@ -2160,7 +2160,7 @@ msgstr "URL Servizio" msgid "Services" msgstr "Servizi" -#: src/model/session.js:98 +#: src/model/session.js:98 src/options/admin.js:62 msgid "Session" msgstr "Sessione" diff --git a/frontend/src/locales/ja.po b/frontend/src/locales/ja.po index 8b4a78c0c..4f09b8329 100644 --- a/frontend/src/locales/ja.po +++ b/frontend/src/locales/ja.po @@ -661,8 +661,8 @@ msgstr "デバッグログ" #: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100 #: src/model/session.js:61 src/options/admin.js:44 src/options/admin.js:45 -#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:73 -#: src/options/admin.js:92 src/options/options.js:313 +#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:74 +#: src/options/admin.js:93 src/options/options.js:313 #: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142 msgid "Default" msgstr "既定" @@ -1346,7 +1346,7 @@ msgstr "最終同期" msgid "Latitude" msgstr "緯度" -#: src/options/admin.js:49 src/options/admin.js:81 +#: src/options/admin.js:49 src/options/admin.js:82 msgid "LDAP/AD" msgstr "LDAP/AD" @@ -1411,8 +1411,8 @@ msgstr "ライブ" msgid "Live Photos" msgstr "ライブ写真" -#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77 -#: src/options/admin.js:96 +#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78 +#: src/options/admin.js:97 msgid "Local" msgstr "ローカル" @@ -1658,7 +1658,7 @@ msgstr "このキーワードを含む警告やエラーは1つも見つかり msgid "Non-photographic and low-quality images require a review before they appear in search results." msgstr "写真ではないものや、低品質な画像は検索結果に現れる前にレビューが必要です。" -#: src/options/admin.js:52 src/options/admin.js:85 src/options/admin.js:100 +#: src/options/admin.js:52 src/options/admin.js:86 src/options/admin.js:101 #: src/options/options.js:293 src/options/options.js:389 msgid "None" msgstr "なし" @@ -2160,7 +2160,7 @@ msgstr "サービス URL" msgid "Services" msgstr "サービス" -#: src/model/session.js:98 +#: src/model/session.js:98 src/options/admin.js:62 msgid "Session" msgstr "セッション" diff --git a/frontend/src/locales/ko.po b/frontend/src/locales/ko.po index 32a1c3138..6ad2ceb5f 100644 --- a/frontend/src/locales/ko.po +++ b/frontend/src/locales/ko.po @@ -661,8 +661,8 @@ msgstr "디버그 로그" #: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100 #: src/model/session.js:61 src/options/admin.js:44 src/options/admin.js:45 -#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:73 -#: src/options/admin.js:92 src/options/options.js:313 +#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:74 +#: src/options/admin.js:93 src/options/options.js:313 #: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142 msgid "Default" msgstr "기본값" @@ -1346,7 +1346,7 @@ msgstr "마지막 동기화" msgid "Latitude" msgstr "위도" -#: src/options/admin.js:49 src/options/admin.js:81 +#: src/options/admin.js:49 src/options/admin.js:82 msgid "LDAP/AD" msgstr "LDAP/AD" @@ -1411,8 +1411,8 @@ msgstr "라이브" msgid "Live Photos" msgstr "라이브 포토" -#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77 -#: src/options/admin.js:96 +#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78 +#: src/options/admin.js:97 msgid "Local" msgstr "로컬" @@ -1658,7 +1658,7 @@ msgstr "이 키워드를 포함하는 경고 또는 오류가 없습니다. 검 msgid "Non-photographic and low-quality images require a review before they appear in search results." msgstr "사진이 아닌 저품질 이미지는 검색 결과에 표시되기 전에 검토가 필요합니다." -#: src/options/admin.js:52 src/options/admin.js:85 src/options/admin.js:100 +#: src/options/admin.js:52 src/options/admin.js:86 src/options/admin.js:101 #: src/options/options.js:293 src/options/options.js:389 msgid "None" msgstr "없음" @@ -2160,7 +2160,7 @@ msgstr "서비스 URL" msgid "Services" msgstr "서비스" -#: src/model/session.js:98 +#: src/model/session.js:98 src/options/admin.js:62 msgid "Session" msgstr "세션" diff --git a/frontend/src/locales/ku.po b/frontend/src/locales/ku.po index 510ddd87a..14cb02235 100644 --- a/frontend/src/locales/ku.po +++ b/frontend/src/locales/ku.po @@ -661,8 +661,8 @@ msgstr "تۆماری هەڵەکان" #: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100 #: src/model/session.js:61 src/options/admin.js:44 src/options/admin.js:45 -#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:73 -#: src/options/admin.js:92 src/options/options.js:313 +#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:74 +#: src/options/admin.js:93 src/options/options.js:313 #: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142 msgid "Default" msgstr "بنه‌ڕه‌ت" @@ -1346,7 +1346,7 @@ msgstr "هاوکاتگەری" msgid "Latitude" msgstr "هێڵی پانیی" -#: src/options/admin.js:49 src/options/admin.js:81 +#: src/options/admin.js:49 src/options/admin.js:82 msgid "LDAP/AD" msgstr "LDAP/AD" @@ -1411,8 +1411,8 @@ msgstr "زیندوو" msgid "Live Photos" msgstr "وێنەکان" -#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77 -#: src/options/admin.js:96 +#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78 +#: src/options/admin.js:97 msgid "Local" msgstr "Herêmî" @@ -1658,7 +1658,7 @@ msgstr "هیچ ئاگادارییەک یان هەڵەیەک نیە کە ئەم msgid "Non-photographic and low-quality images require a review before they appear in search results." msgstr "وێنە نافۆتۆگرافی و کوالێتی نزمەکان پێویستی بە پێداچونەوە هەیە پێش ئەوەی لە ئەنجامی گەڕاندا دەرکەون." -#: src/options/admin.js:52 src/options/admin.js:85 src/options/admin.js:100 +#: src/options/admin.js:52 src/options/admin.js:86 src/options/admin.js:101 #: src/options/options.js:293 src/options/options.js:389 msgid "None" msgstr "هیچ" @@ -2160,7 +2160,7 @@ msgstr "بەستەری خزمەتگوزاری" msgid "Services" msgstr "بەستەری خزمەتگوزاری" -#: src/model/session.js:98 +#: src/model/session.js:98 src/options/admin.js:62 msgid "Session" msgstr "Rûniştinî" diff --git a/frontend/src/locales/lt.po b/frontend/src/locales/lt.po index b8c9a753a..252cc96aa 100644 --- a/frontend/src/locales/lt.po +++ b/frontend/src/locales/lt.po @@ -661,8 +661,8 @@ msgstr "Derinimo žurnalai" #: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100 #: src/model/session.js:61 src/options/admin.js:44 src/options/admin.js:45 -#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:73 -#: src/options/admin.js:92 src/options/options.js:313 +#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:74 +#: src/options/admin.js:93 src/options/options.js:313 #: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142 msgid "Default" msgstr "Numatytoji" @@ -1346,7 +1346,7 @@ msgstr "Sinchronizavimas" msgid "Latitude" msgstr "Platuma" -#: src/options/admin.js:49 src/options/admin.js:81 +#: src/options/admin.js:49 src/options/admin.js:82 msgid "LDAP/AD" msgstr "LDAP/AD" @@ -1411,8 +1411,8 @@ msgstr "Gyvai" msgid "Live Photos" msgstr "Nuotraukos" -#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77 -#: src/options/admin.js:96 +#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78 +#: src/options/admin.js:97 msgid "Local" msgstr "Vietinis" @@ -1658,7 +1658,7 @@ msgstr "Jokių įspėjimų ar klaidų su šiuo raktažodžiu nėra. Atkreipkite msgid "Non-photographic and low-quality images require a review before they appear in search results." msgstr "Prieš rodant nefotografuotus ir prastos kokybės vaizdus paieškos rezultatuose, juos reikia peržiūrėti." -#: src/options/admin.js:52 src/options/admin.js:85 src/options/admin.js:100 +#: src/options/admin.js:52 src/options/admin.js:86 src/options/admin.js:101 #: src/options/options.js:293 src/options/options.js:389 msgid "None" msgstr "Nėra" @@ -2160,7 +2160,7 @@ msgstr "Paslaugos URL" msgid "Services" msgstr "Paslaugos URL" -#: src/model/session.js:98 +#: src/model/session.js:98 src/options/admin.js:62 msgid "Session" msgstr "Sesija" diff --git a/frontend/src/locales/ms.po b/frontend/src/locales/ms.po index 4abd202bf..7e7850421 100644 --- a/frontend/src/locales/ms.po +++ b/frontend/src/locales/ms.po @@ -661,8 +661,8 @@ msgstr "Log Nyahpepijat" #: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100 #: src/model/session.js:61 src/options/admin.js:44 src/options/admin.js:45 -#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:73 -#: src/options/admin.js:92 src/options/options.js:313 +#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:74 +#: src/options/admin.js:93 src/options/options.js:313 #: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142 msgid "Default" msgstr "Lalai" @@ -1346,7 +1346,7 @@ msgstr "Penyegerakan Terakhir" msgid "Latitude" msgstr "Latitud" -#: src/options/admin.js:49 src/options/admin.js:81 +#: src/options/admin.js:49 src/options/admin.js:82 msgid "LDAP/AD" msgstr "LDAP/AD" @@ -1411,8 +1411,8 @@ msgstr "Langsung" msgid "Live Photos" msgstr "Foto" -#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77 -#: src/options/admin.js:96 +#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78 +#: src/options/admin.js:97 msgid "Local" msgstr "Tempatan" @@ -1658,7 +1658,7 @@ msgstr "Tiada amaran atau ralat yang mengandungi kata kunci ini. Ambil perhatian msgid "Non-photographic and low-quality images require a review before they appear in search results." msgstr "Imej bukan fotografi dan berkualiti rendah memerlukan semakan sebelum ia muncul dalam hasil carian." -#: src/options/admin.js:52 src/options/admin.js:85 src/options/admin.js:100 +#: src/options/admin.js:52 src/options/admin.js:86 src/options/admin.js:101 #: src/options/options.js:293 src/options/options.js:389 msgid "None" msgstr "Tiada" @@ -2160,7 +2160,7 @@ msgstr "URL perkhidmatan" msgid "Services" msgstr "URL perkhidmatan" -#: src/model/session.js:98 +#: src/model/session.js:98 src/options/admin.js:62 msgid "Session" msgstr "Sesi" diff --git a/frontend/src/locales/nb.po b/frontend/src/locales/nb.po index 4ca31e73e..1206c5e08 100644 --- a/frontend/src/locales/nb.po +++ b/frontend/src/locales/nb.po @@ -661,8 +661,8 @@ msgstr "Feilsøkingslogger" #: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100 #: src/model/session.js:61 src/options/admin.js:44 src/options/admin.js:45 -#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:73 -#: src/options/admin.js:92 src/options/options.js:313 +#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:74 +#: src/options/admin.js:93 src/options/options.js:313 #: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142 msgid "Default" msgstr "Standard" @@ -1346,7 +1346,7 @@ msgstr "Siste synkronisering" msgid "Latitude" msgstr "Breddegrad" -#: src/options/admin.js:49 src/options/admin.js:81 +#: src/options/admin.js:49 src/options/admin.js:82 msgid "LDAP/AD" msgstr "LDAP/AD" @@ -1411,8 +1411,8 @@ msgstr "Direkte" msgid "Live Photos" msgstr "Fotoer" -#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77 -#: src/options/admin.js:96 +#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78 +#: src/options/admin.js:97 msgid "Local" msgstr "Lokalt" @@ -1658,7 +1658,7 @@ msgstr "Ingen advarsler eller feilmeldinger inneholder dette nøkkelordet. Merk msgid "Non-photographic and low-quality images require a review before they appear in search results." msgstr "Bilder som ikke er fotografiske eller har lav kvalitet må gjennomgås før de kommer i søkeresultater." -#: src/options/admin.js:52 src/options/admin.js:85 src/options/admin.js:100 +#: src/options/admin.js:52 src/options/admin.js:86 src/options/admin.js:101 #: src/options/options.js:293 src/options/options.js:389 msgid "None" msgstr "Ingen" @@ -2160,7 +2160,7 @@ msgstr "Tjeneste-URL" msgid "Services" msgstr "Tjenester" -#: src/model/session.js:98 +#: src/model/session.js:98 src/options/admin.js:62 msgid "Session" msgstr "Sesjon" diff --git a/frontend/src/locales/nl.po b/frontend/src/locales/nl.po index 97fbfb252..aec37c4a2 100644 --- a/frontend/src/locales/nl.po +++ b/frontend/src/locales/nl.po @@ -661,8 +661,8 @@ msgstr "Debug-logs" #: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100 #: src/model/session.js:61 src/options/admin.js:44 src/options/admin.js:45 -#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:73 -#: src/options/admin.js:92 src/options/options.js:313 +#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:74 +#: src/options/admin.js:93 src/options/options.js:313 #: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142 msgid "Default" msgstr "Standaard" @@ -1346,7 +1346,7 @@ msgstr "Laatste synchronisatie" msgid "Latitude" msgstr "Breedtegraad" -#: src/options/admin.js:49 src/options/admin.js:81 +#: src/options/admin.js:49 src/options/admin.js:82 msgid "LDAP/AD" msgstr "LDAP/AD" @@ -1411,8 +1411,8 @@ msgstr "Live" msgid "Live Photos" msgstr "Live foto's" -#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77 -#: src/options/admin.js:96 +#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78 +#: src/options/admin.js:97 msgid "Local" msgstr "Lokaal" @@ -1658,7 +1658,7 @@ msgstr "Geen waarschuwingen of fouten met dit trefwoord. Let op: zoeken is hoofd msgid "Non-photographic and low-quality images require a review before they appear in search results." msgstr "Niet-fotografische beelden en beelden van lage kwaliteit moeten worden beoordeeld voordat ze in de zoekresultaten verschijnen." -#: src/options/admin.js:52 src/options/admin.js:85 src/options/admin.js:100 +#: src/options/admin.js:52 src/options/admin.js:86 src/options/admin.js:101 #: src/options/options.js:293 src/options/options.js:389 msgid "None" msgstr "Geen" @@ -2160,7 +2160,7 @@ msgstr "Service URL" msgid "Services" msgstr "Diensten" -#: src/model/session.js:98 +#: src/model/session.js:98 src/options/admin.js:62 msgid "Session" msgstr "Sessie" diff --git a/frontend/src/locales/pl.po b/frontend/src/locales/pl.po index 1c0bb27b7..e6215279d 100644 --- a/frontend/src/locales/pl.po +++ b/frontend/src/locales/pl.po @@ -661,8 +661,8 @@ msgstr "Logi debugowania" #: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100 #: src/model/session.js:61 src/options/admin.js:44 src/options/admin.js:45 -#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:73 -#: src/options/admin.js:92 src/options/options.js:313 +#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:74 +#: src/options/admin.js:93 src/options/options.js:313 #: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142 msgid "Default" msgstr "Domyślny" @@ -1346,7 +1346,7 @@ msgstr "Ostatnia synchronizacja" msgid "Latitude" msgstr "Szerokość geograficzna" -#: src/options/admin.js:49 src/options/admin.js:81 +#: src/options/admin.js:49 src/options/admin.js:82 msgid "LDAP/AD" msgstr "LDAP/AD" @@ -1411,8 +1411,8 @@ msgstr "Live" msgid "Live Photos" msgstr "Zdjęcia na żywo" -#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77 -#: src/options/admin.js:96 +#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78 +#: src/options/admin.js:97 msgid "Local" msgstr "Lokalnie" @@ -1658,7 +1658,7 @@ msgstr "Brak ostrzeżeń lub błędów zawierających to słowo kluczowe. Zwró msgid "Non-photographic and low-quality images require a review before they appear in search results." msgstr "Obrazy niebędące fotografiami lub posiadające niską jakość wymagają zatwierdzenia, zanim pojawią się w wynikach wyszukiwania." -#: src/options/admin.js:52 src/options/admin.js:85 src/options/admin.js:100 +#: src/options/admin.js:52 src/options/admin.js:86 src/options/admin.js:101 #: src/options/options.js:293 src/options/options.js:389 msgid "None" msgstr "Brak" @@ -2160,7 +2160,7 @@ msgstr "Adres URL do usługi" msgid "Services" msgstr "Usługi" -#: src/model/session.js:98 +#: src/model/session.js:98 src/options/admin.js:62 msgid "Session" msgstr "Sesja" diff --git a/frontend/src/locales/pt.po b/frontend/src/locales/pt.po index eb64266a2..50fcca325 100644 --- a/frontend/src/locales/pt.po +++ b/frontend/src/locales/pt.po @@ -661,8 +661,8 @@ msgstr "Registros de depuração" #: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100 #: src/model/session.js:61 src/options/admin.js:44 src/options/admin.js:45 -#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:73 -#: src/options/admin.js:92 src/options/options.js:313 +#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:74 +#: src/options/admin.js:93 src/options/options.js:313 #: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142 msgid "Default" msgstr "Padrão" @@ -1346,7 +1346,7 @@ msgstr "Última Sincronia" msgid "Latitude" msgstr "Latitude" -#: src/options/admin.js:49 src/options/admin.js:81 +#: src/options/admin.js:49 src/options/admin.js:82 msgid "LDAP/AD" msgstr "LDAP/AD" @@ -1411,8 +1411,8 @@ msgstr "Ao vivo" msgid "Live Photos" msgstr "Fotos ao vivo" -#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77 -#: src/options/admin.js:96 +#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78 +#: src/options/admin.js:97 msgid "Local" msgstr "Local" @@ -1658,7 +1658,7 @@ msgstr "Nenhum alerta ou erro contendo esta palavra-chave. Note que a pesquisa d msgid "Non-photographic and low-quality images require a review before they appear in search results." msgstr "Imagens de baixa qualidade ou não-fotográficas necessitam de revisão antes de aparecerem nos resultados da pesquisa." -#: src/options/admin.js:52 src/options/admin.js:85 src/options/admin.js:100 +#: src/options/admin.js:52 src/options/admin.js:86 src/options/admin.js:101 #: src/options/options.js:293 src/options/options.js:389 msgid "None" msgstr "Nenhum" @@ -2160,7 +2160,7 @@ msgstr "URL do serviço" msgid "Services" msgstr "Serviços" -#: src/model/session.js:98 +#: src/model/session.js:98 src/options/admin.js:62 msgid "Session" msgstr "Sessão" diff --git a/frontend/src/locales/pt_BR.po b/frontend/src/locales/pt_BR.po index 920c69494..bcba4fa1c 100644 --- a/frontend/src/locales/pt_BR.po +++ b/frontend/src/locales/pt_BR.po @@ -661,8 +661,8 @@ msgstr "Registros de depuração" #: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100 #: src/model/session.js:61 src/options/admin.js:44 src/options/admin.js:45 -#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:73 -#: src/options/admin.js:92 src/options/options.js:313 +#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:74 +#: src/options/admin.js:93 src/options/options.js:313 #: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142 msgid "Default" msgstr "Padrão" @@ -1346,7 +1346,7 @@ msgstr "Última Sincronia" msgid "Latitude" msgstr "Latitude" -#: src/options/admin.js:49 src/options/admin.js:81 +#: src/options/admin.js:49 src/options/admin.js:82 msgid "LDAP/AD" msgstr "LDAP/AD" @@ -1411,8 +1411,8 @@ msgstr "Ao vivo" msgid "Live Photos" msgstr "Fotos ao vivo" -#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77 -#: src/options/admin.js:96 +#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78 +#: src/options/admin.js:97 msgid "Local" msgstr "Local" @@ -1658,7 +1658,7 @@ msgstr "Nenhum alerta ou erro contento esta palavra-chave. Note que a busca dife msgid "Non-photographic and low-quality images require a review before they appear in search results." msgstr "Imagens de baixa qualidade ou não-fotográficas necessitam de revisão antes de aparecerem nos resultados de busca." -#: src/options/admin.js:52 src/options/admin.js:85 src/options/admin.js:100 +#: src/options/admin.js:52 src/options/admin.js:86 src/options/admin.js:101 #: src/options/options.js:293 src/options/options.js:389 msgid "None" msgstr "Nenhum" @@ -2160,7 +2160,7 @@ msgstr "URL do serviço" msgid "Services" msgstr "Serviços" -#: src/model/session.js:98 +#: src/model/session.js:98 src/options/admin.js:62 msgid "Session" msgstr "Sessão" diff --git a/frontend/src/locales/ro.po b/frontend/src/locales/ro.po index 0f3bb3bad..3de5a2d0d 100644 --- a/frontend/src/locales/ro.po +++ b/frontend/src/locales/ro.po @@ -661,8 +661,8 @@ msgstr "Jurnalele de depanare" #: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100 #: src/model/session.js:61 src/options/admin.js:44 src/options/admin.js:45 -#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:73 -#: src/options/admin.js:92 src/options/options.js:313 +#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:74 +#: src/options/admin.js:93 src/options/options.js:313 #: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142 msgid "Default" msgstr "Implicit" @@ -1346,7 +1346,7 @@ msgstr "Ultima sincronizare" msgid "Latitude" msgstr "Latitudine" -#: src/options/admin.js:49 src/options/admin.js:81 +#: src/options/admin.js:49 src/options/admin.js:82 msgid "LDAP/AD" msgstr "LDAP/AD" @@ -1411,8 +1411,8 @@ msgstr "Live" msgid "Live Photos" msgstr "Fotografii în direct" -#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77 -#: src/options/admin.js:96 +#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78 +#: src/options/admin.js:97 msgid "Local" msgstr "Local" @@ -1658,7 +1658,7 @@ msgstr "Nu există avertismente sau erori care să conțină acest cuvânt cheie msgid "Non-photographic and low-quality images require a review before they appear in search results." msgstr "Imaginile nefotografice și de slabă calitate necesită o revizuire înainte de a apărea în rezultatele căutării." -#: src/options/admin.js:52 src/options/admin.js:85 src/options/admin.js:100 +#: src/options/admin.js:52 src/options/admin.js:86 src/options/admin.js:101 #: src/options/options.js:293 src/options/options.js:389 msgid "None" msgstr "Nici unul" @@ -2160,7 +2160,7 @@ msgstr "URL de serviciu" msgid "Services" msgstr "Servicii" -#: src/model/session.js:98 +#: src/model/session.js:98 src/options/admin.js:62 msgid "Session" msgstr "Sesiunea" diff --git a/frontend/src/locales/ru.po b/frontend/src/locales/ru.po index 0c775696e..ee899026a 100644 --- a/frontend/src/locales/ru.po +++ b/frontend/src/locales/ru.po @@ -661,8 +661,8 @@ msgstr "Отладочные Логи" #: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100 #: src/model/session.js:61 src/options/admin.js:44 src/options/admin.js:45 -#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:73 -#: src/options/admin.js:92 src/options/options.js:313 +#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:74 +#: src/options/admin.js:93 src/options/options.js:313 #: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142 msgid "Default" msgstr "По умолчанию" @@ -1346,7 +1346,7 @@ msgstr "Последняя синхронизация" msgid "Latitude" msgstr "Широта" -#: src/options/admin.js:49 src/options/admin.js:81 +#: src/options/admin.js:49 src/options/admin.js:82 msgid "LDAP/AD" msgstr "LDAP/AD" @@ -1411,8 +1411,8 @@ msgstr "Прямой эфир" msgid "Live Photos" msgstr "Живые фотографии" -#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77 -#: src/options/admin.js:96 +#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78 +#: src/options/admin.js:97 msgid "Local" msgstr "Местный" @@ -1658,7 +1658,7 @@ msgstr "Нет предупреждений или ошибок содержащ msgid "Non-photographic and low-quality images require a review before they appear in search results." msgstr "Файлы, не являющиеся фотографиями, или изображения низкого качества нужно одобрить, чтобы они появились в результатах поиска." -#: src/options/admin.js:52 src/options/admin.js:85 src/options/admin.js:100 +#: src/options/admin.js:52 src/options/admin.js:86 src/options/admin.js:101 #: src/options/options.js:293 src/options/options.js:389 msgid "None" msgstr "Ничего" @@ -2160,7 +2160,7 @@ msgstr "URL сервиса" msgid "Services" msgstr "Сервисы" -#: src/model/session.js:98 +#: src/model/session.js:98 src/options/admin.js:62 msgid "Session" msgstr "Сессия" diff --git a/frontend/src/locales/sk.po b/frontend/src/locales/sk.po index d2bb90e5a..20303aa6c 100644 --- a/frontend/src/locales/sk.po +++ b/frontend/src/locales/sk.po @@ -661,8 +661,8 @@ msgstr "Denníky ladenia" #: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100 #: src/model/session.js:61 src/options/admin.js:44 src/options/admin.js:45 -#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:73 -#: src/options/admin.js:92 src/options/options.js:313 +#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:74 +#: src/options/admin.js:93 src/options/options.js:313 #: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142 msgid "Default" msgstr "Predvolená" @@ -1346,7 +1346,7 @@ msgstr "Posledná synchronizácia" msgid "Latitude" msgstr "Šírka" -#: src/options/admin.js:49 src/options/admin.js:81 +#: src/options/admin.js:49 src/options/admin.js:82 msgid "LDAP/AD" msgstr "LDAP/AD" @@ -1411,8 +1411,8 @@ msgstr "Živé" msgid "Live Photos" msgstr "Živé fotografie" -#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77 -#: src/options/admin.js:96 +#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78 +#: src/options/admin.js:97 msgid "Local" msgstr "Miestne stránky" @@ -1658,7 +1658,7 @@ msgstr "Nenašli sa žiadne upozornenia ani chyby ktoré by obsahovali toto kľ msgid "Non-photographic and low-quality images require a review before they appear in search results." msgstr "Nefotografické a fotografie nízkej kvality vyžadujú skontrolovanie pred tým než sa zobrazia vo výsledkoch vyhľadávania." -#: src/options/admin.js:52 src/options/admin.js:85 src/options/admin.js:100 +#: src/options/admin.js:52 src/options/admin.js:86 src/options/admin.js:101 #: src/options/options.js:293 src/options/options.js:389 msgid "None" msgstr "Žiadne" @@ -2160,7 +2160,7 @@ msgstr "URL Služby" msgid "Services" msgstr "Služby" -#: src/model/session.js:98 +#: src/model/session.js:98 src/options/admin.js:62 msgid "Session" msgstr "Zasadnutie" diff --git a/frontend/src/locales/sl.po b/frontend/src/locales/sl.po index 6ae85d7d4..7dc85d370 100644 --- a/frontend/src/locales/sl.po +++ b/frontend/src/locales/sl.po @@ -658,8 +658,8 @@ msgstr "Dnevniki za odpravljanje napak" #: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100 #: src/model/session.js:61 src/options/admin.js:44 src/options/admin.js:45 -#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:73 -#: src/options/admin.js:92 src/options/options.js:313 +#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:74 +#: src/options/admin.js:93 src/options/options.js:313 #: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142 msgid "Default" msgstr "Privzeto" @@ -1343,7 +1343,7 @@ msgstr "Zadnja sinhronizacija" msgid "Latitude" msgstr "Zemljepisna širina" -#: src/options/admin.js:49 src/options/admin.js:81 +#: src/options/admin.js:49 src/options/admin.js:82 msgid "LDAP/AD" msgstr "LDAP/AD" @@ -1408,8 +1408,8 @@ msgstr "V živo" msgid "Live Photos" msgstr "Fotografije" -#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77 -#: src/options/admin.js:96 +#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78 +#: src/options/admin.js:97 msgid "Local" msgstr "Lokalni" @@ -1655,7 +1655,7 @@ msgstr "Ni opozoril ali napak, ki bi vsebovale to ključno besedo. Upoštevajte, msgid "Non-photographic and low-quality images require a review before they appear in search results." msgstr "Nefotografske slike in slike nizke kakovosti je treba pred prikazom v rezultatih iskanja pregledati." -#: src/options/admin.js:52 src/options/admin.js:85 src/options/admin.js:100 +#: src/options/admin.js:52 src/options/admin.js:86 src/options/admin.js:101 #: src/options/options.js:293 src/options/options.js:389 msgid "None" msgstr "Ni" @@ -2157,7 +2157,7 @@ msgstr "URL storitve" msgid "Services" msgstr "URL storitve" -#: src/model/session.js:98 +#: src/model/session.js:98 src/options/admin.js:62 msgid "Session" msgstr "Seja" diff --git a/frontend/src/locales/sv.po b/frontend/src/locales/sv.po index 9d1683c51..d04d890c5 100644 --- a/frontend/src/locales/sv.po +++ b/frontend/src/locales/sv.po @@ -661,8 +661,8 @@ msgstr "Felsökningsloggar" #: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100 #: src/model/session.js:61 src/options/admin.js:44 src/options/admin.js:45 -#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:73 -#: src/options/admin.js:92 src/options/options.js:313 +#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:74 +#: src/options/admin.js:93 src/options/options.js:313 #: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142 msgid "Default" msgstr "Standard" @@ -1346,7 +1346,7 @@ msgstr "Senaste synkronisering" msgid "Latitude" msgstr "Latitud" -#: src/options/admin.js:49 src/options/admin.js:81 +#: src/options/admin.js:49 src/options/admin.js:82 msgid "LDAP/AD" msgstr "LDAP/AD" @@ -1411,8 +1411,8 @@ msgstr "Live" msgid "Live Photos" msgstr "Foton" -#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77 -#: src/options/admin.js:96 +#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78 +#: src/options/admin.js:97 msgid "Local" msgstr "Lokal" @@ -1658,7 +1658,7 @@ msgstr "Inga varningar eller fel som innehåller detta nyckelord. Observera att msgid "Non-photographic and low-quality images require a review before they appear in search results." msgstr "Bilder som inte är fotografiska eller av låg kvalitet måste granskas innan de visas i sökresultaten." -#: src/options/admin.js:52 src/options/admin.js:85 src/options/admin.js:100 +#: src/options/admin.js:52 src/options/admin.js:86 src/options/admin.js:101 #: src/options/options.js:293 src/options/options.js:389 msgid "None" msgstr "Ingen" @@ -2160,7 +2160,7 @@ msgstr "Tjänstens URL" msgid "Services" msgstr "Tjänstens URL" -#: src/model/session.js:98 +#: src/model/session.js:98 src/options/admin.js:62 msgid "Session" msgstr "Sammanträde" diff --git a/frontend/src/locales/th.po b/frontend/src/locales/th.po index 129939bd8..5c7c4bbc5 100644 --- a/frontend/src/locales/th.po +++ b/frontend/src/locales/th.po @@ -661,8 +661,8 @@ msgstr "บันทึกการดีบัก" #: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100 #: src/model/session.js:61 src/options/admin.js:44 src/options/admin.js:45 -#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:73 -#: src/options/admin.js:92 src/options/options.js:313 +#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:74 +#: src/options/admin.js:93 src/options/options.js:313 #: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142 msgid "Default" msgstr "ค่าเริ่มต้น" @@ -1346,7 +1346,7 @@ msgstr "ซิงค์ล่าสุด" msgid "Latitude" msgstr "ละติจูด" -#: src/options/admin.js:49 src/options/admin.js:81 +#: src/options/admin.js:49 src/options/admin.js:82 msgid "LDAP/AD" msgstr "แอลดีเอพี/ค.ศ" @@ -1411,8 +1411,8 @@ msgstr "สด" msgid "Live Photos" msgstr "ภาพถ่าย" -#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77 -#: src/options/admin.js:96 +#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78 +#: src/options/admin.js:97 msgid "Local" msgstr "ท้องถิ่น" @@ -1658,7 +1658,7 @@ msgstr "ไม่มีคำเตือนหรือข้อผิดพล msgid "Non-photographic and low-quality images require a review before they appear in search results." msgstr "รูปภาพที่ไม่ใช่ภาพถ่ายและคุณภาพต่ำต้องได้รับการตรวจสอบก่อนที่จะปรากฏในผลการค้นหา" -#: src/options/admin.js:52 src/options/admin.js:85 src/options/admin.js:100 +#: src/options/admin.js:52 src/options/admin.js:86 src/options/admin.js:101 #: src/options/options.js:293 src/options/options.js:389 msgid "None" msgstr "ไม่มี" @@ -2160,7 +2160,7 @@ msgstr "URL บริการ" msgid "Services" msgstr "URL บริการ" -#: src/model/session.js:98 +#: src/model/session.js:98 src/options/admin.js:62 msgid "Session" msgstr "การประชุม" diff --git a/frontend/src/locales/tr.po b/frontend/src/locales/tr.po index 3e1024421..cd02ede33 100644 --- a/frontend/src/locales/tr.po +++ b/frontend/src/locales/tr.po @@ -661,8 +661,8 @@ msgstr "Hata Kayıtları" #: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100 #: src/model/session.js:61 src/options/admin.js:44 src/options/admin.js:45 -#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:73 -#: src/options/admin.js:92 src/options/options.js:313 +#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:74 +#: src/options/admin.js:93 src/options/options.js:313 #: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142 msgid "Default" msgstr "Varsayılan" @@ -1346,7 +1346,7 @@ msgstr "Son Senkronizasyon" msgid "Latitude" msgstr "Enlem" -#: src/options/admin.js:49 src/options/admin.js:81 +#: src/options/admin.js:49 src/options/admin.js:82 msgid "LDAP/AD" msgstr "LDAP/AD" @@ -1411,8 +1411,8 @@ msgstr "Canlı" msgid "Live Photos" msgstr "Canlı Fotoğraflar" -#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77 -#: src/options/admin.js:96 +#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78 +#: src/options/admin.js:97 msgid "Local" msgstr "Yerel" @@ -1658,7 +1658,7 @@ msgstr "Bu anahtar kelimeyi içeren uyarı veya hata yok. Aramanın büyük/kü msgid "Non-photographic and low-quality images require a review before they appear in search results." msgstr "Fotoğrafik olmayan ve düşük kaliteli görseller, arama sonuçlarında görünmeden önce bir inceleme gerektirir." -#: src/options/admin.js:52 src/options/admin.js:85 src/options/admin.js:100 +#: src/options/admin.js:52 src/options/admin.js:86 src/options/admin.js:101 #: src/options/options.js:293 src/options/options.js:389 msgid "None" msgstr "Hiçbiri" @@ -2160,7 +2160,7 @@ msgstr "Hizmet URL'si" msgid "Services" msgstr "Hizmetler" -#: src/model/session.js:98 +#: src/model/session.js:98 src/options/admin.js:62 msgid "Session" msgstr "Oturum" diff --git a/frontend/src/locales/translations.pot b/frontend/src/locales/translations.pot index 61f3ca2e3..73ae817e3 100644 --- a/frontend/src/locales/translations.pot +++ b/frontend/src/locales/translations.pot @@ -773,8 +773,8 @@ msgstr "" #: src/options/admin.js:45 #: src/options/admin.js:59 #: src/options/admin.js:60 -#: src/options/admin.js:73 -#: src/options/admin.js:92 +#: src/options/admin.js:74 +#: src/options/admin.js:93 #: src/options/options.js:313 #: src/options/options.js:377 #: src/options/themes.js:492 @@ -1579,7 +1579,7 @@ msgid "Latitude" msgstr "" #: src/options/admin.js:49 -#: src/options/admin.js:81 +#: src/options/admin.js:82 msgid "LDAP/AD" msgstr "" @@ -1664,8 +1664,8 @@ msgstr "" #: src/options/admin.js:46 #: src/options/admin.js:48 -#: src/options/admin.js:77 -#: src/options/admin.js:96 +#: src/options/admin.js:78 +#: src/options/admin.js:97 msgid "Local" msgstr "" @@ -1977,8 +1977,8 @@ msgid "Non-photographic and low-quality images require a review before they appe msgstr "" #: src/options/admin.js:52 -#: src/options/admin.js:85 -#: src/options/admin.js:100 +#: src/options/admin.js:86 +#: src/options/admin.js:101 #: src/options/options.js:293 #: src/options/options.js:389 msgid "None" @@ -2564,6 +2564,7 @@ msgid "Services" msgstr "" #: src/model/session.js:98 +#: src/options/admin.js:62 msgid "Session" msgstr "" diff --git a/frontend/src/locales/uk.po b/frontend/src/locales/uk.po index dbb934098..9e5f18f46 100644 --- a/frontend/src/locales/uk.po +++ b/frontend/src/locales/uk.po @@ -661,8 +661,8 @@ msgstr "Журнали налагодження" #: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100 #: src/model/session.js:61 src/options/admin.js:44 src/options/admin.js:45 -#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:73 -#: src/options/admin.js:92 src/options/options.js:313 +#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:74 +#: src/options/admin.js:93 src/options/options.js:313 #: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142 msgid "Default" msgstr "За замовчуванням" @@ -1346,7 +1346,7 @@ msgstr "Остання синхронізація" msgid "Latitude" msgstr "Широта" -#: src/options/admin.js:49 src/options/admin.js:81 +#: src/options/admin.js:49 src/options/admin.js:82 msgid "LDAP/AD" msgstr "LDAP/AD" @@ -1411,8 +1411,8 @@ msgstr "Live фото" msgid "Live Photos" msgstr "Живі фото" -#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77 -#: src/options/admin.js:96 +#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78 +#: src/options/admin.js:97 msgid "Local" msgstr "Місцевий" @@ -1658,7 +1658,7 @@ msgstr "Немає попереджень або помилок із цим кл msgid "Non-photographic and low-quality images require a review before they appear in search results." msgstr "Нефотографічні та низькоякісні зображення потребують перевірки, перш ніж з’являться в результатах пошуку." -#: src/options/admin.js:52 src/options/admin.js:85 src/options/admin.js:100 +#: src/options/admin.js:52 src/options/admin.js:86 src/options/admin.js:101 #: src/options/options.js:293 src/options/options.js:389 msgid "None" msgstr "Жодного" @@ -2160,7 +2160,7 @@ msgstr "URL служби" msgid "Services" msgstr "Послуги" -#: src/model/session.js:98 +#: src/model/session.js:98 src/options/admin.js:62 msgid "Session" msgstr "Сесія" diff --git a/frontend/src/locales/zh.po b/frontend/src/locales/zh.po index 7252e6e6c..371a5b2eb 100644 --- a/frontend/src/locales/zh.po +++ b/frontend/src/locales/zh.po @@ -661,8 +661,8 @@ msgstr "调试日志" #: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100 #: src/model/session.js:61 src/options/admin.js:44 src/options/admin.js:45 -#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:73 -#: src/options/admin.js:92 src/options/options.js:313 +#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:74 +#: src/options/admin.js:93 src/options/options.js:313 #: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142 msgid "Default" msgstr "默认" @@ -1346,7 +1346,7 @@ msgstr "上次同步" msgid "Latitude" msgstr "纬度" -#: src/options/admin.js:49 src/options/admin.js:81 +#: src/options/admin.js:49 src/options/admin.js:82 msgid "LDAP/AD" msgstr "LDAP/AD" @@ -1411,8 +1411,8 @@ msgstr "实况" msgid "Live Photos" msgstr "现场照片" -#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77 -#: src/options/admin.js:96 +#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78 +#: src/options/admin.js:97 msgid "Local" msgstr "当地" @@ -1658,7 +1658,7 @@ msgstr "没有包含此关键字的警告或错误,请注意,搜索区分大 msgid "Non-photographic and low-quality images require a review before they appear in search results." msgstr "非照片和低质量图像出现在搜索结果中前需要进行审查。" -#: src/options/admin.js:52 src/options/admin.js:85 src/options/admin.js:100 +#: src/options/admin.js:52 src/options/admin.js:86 src/options/admin.js:101 #: src/options/options.js:293 src/options/options.js:389 msgid "None" msgstr "无" @@ -2162,7 +2162,7 @@ msgstr "服务 URL" msgid "Services" msgstr "服务" -#: src/model/session.js:98 +#: src/model/session.js:98 src/options/admin.js:62 msgid "Session" msgstr "会议" diff --git a/frontend/src/locales/zh_TW.po b/frontend/src/locales/zh_TW.po index 00eb61f1f..88b13175a 100644 --- a/frontend/src/locales/zh_TW.po +++ b/frontend/src/locales/zh_TW.po @@ -661,8 +661,8 @@ msgstr "除錯紀錄" #: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100 #: src/model/session.js:61 src/options/admin.js:44 src/options/admin.js:45 -#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:73 -#: src/options/admin.js:92 src/options/options.js:313 +#: src/options/admin.js:59 src/options/admin.js:60 src/options/admin.js:74 +#: src/options/admin.js:93 src/options/options.js:313 #: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142 msgid "Default" msgstr "預設" @@ -1346,7 +1346,7 @@ msgstr "上次同步" msgid "Latitude" msgstr "緯度" -#: src/options/admin.js:49 src/options/admin.js:81 +#: src/options/admin.js:49 src/options/admin.js:82 msgid "LDAP/AD" msgstr "LDAP/AD" @@ -1411,8 +1411,8 @@ msgstr "即時" msgid "Live Photos" msgstr "原況照片" -#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77 -#: src/options/admin.js:96 +#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78 +#: src/options/admin.js:97 msgid "Local" msgstr "当地" @@ -1658,7 +1658,7 @@ msgstr "沒有包含此關鍵字的警告或錯誤。請注意,搜尋區分大 msgid "Non-photographic and low-quality images require a review before they appear in search results." msgstr "非照片和低品質圖像需要進行手動確認,才會出現在搜尋結果中。" -#: src/options/admin.js:52 src/options/admin.js:85 src/options/admin.js:100 +#: src/options/admin.js:52 src/options/admin.js:86 src/options/admin.js:101 #: src/options/options.js:293 src/options/options.js:389 msgid "None" msgstr "無" @@ -2160,7 +2160,7 @@ msgstr "服務 URL" msgid "Services" msgstr "服務" -#: src/model/session.js:98 +#: src/model/session.js:98 src/options/admin.js:62 msgid "Session" msgstr "工作階段" diff --git a/frontend/src/model/session.js b/frontend/src/model/session.js index 387be9e60..21a95460b 100644 --- a/frontend/src/model/session.js +++ b/frontend/src/model/session.js @@ -91,7 +91,7 @@ export class Session extends RestModel { } static getCollectionResource() { - return "session"; + return "sessions"; } static getModelName() { diff --git a/frontend/src/options/admin.js b/frontend/src/options/admin.js index feae98bae..dfbdb48ef 100644 --- a/frontend/src/options/admin.js +++ b/frontend/src/options/admin.js @@ -59,6 +59,7 @@ export const AuthMethods = () => { "": $gettext("Default"), default: $gettext("Default"), access_token: $gettext("Access Token"), + session: $gettext("Session"), "2fa": "2FA", oauth2: "OAuth2", oidc: "OIDC", diff --git a/internal/acl/acl_test.go b/internal/acl/acl_test.go index 2ba1cf004..902c3fdcf 100644 --- a/internal/acl/acl_test.go +++ b/internal/acl/acl_test.go @@ -7,6 +7,14 @@ import ( ) func TestACL_Allow(t *testing.T) { + t.Run("ResourceSessions", func(t *testing.T) { + assert.True(t, Resources.Allow(ResourceSessions, RoleAdmin, AccessAll)) + assert.True(t, Resources.Allow(ResourceSessions, RoleAdmin, AccessOwn)) + assert.False(t, Resources.Allow(ResourceSessions, RoleVisitor, AccessAll)) + assert.True(t, Resources.Allow(ResourceSessions, RoleVisitor, AccessOwn)) + assert.False(t, Resources.Allow(ResourceSessions, RoleClient, AccessAll)) + assert.True(t, Resources.Allow(ResourceSessions, RoleClient, AccessOwn)) + }) t.Run("ResourcePhotosRoleAdminActionModify", func(t *testing.T) { assert.True(t, Resources.Allow(ResourcePhotos, RoleAdmin, ActionUpdate)) }) @@ -124,6 +132,6 @@ func TestACL_DenyAll(t *testing.T) { func TestACL_Resources(t *testing.T) { t.Run("Resources", func(t *testing.T) { result := Resources.Resources() - assert.Len(t, result, 21) + assert.Len(t, result, 22) }) } diff --git a/internal/acl/resource.go b/internal/acl/resource.go index 745f4d6f7..998d082e8 100644 --- a/internal/acl/resource.go +++ b/internal/acl/resource.go @@ -21,6 +21,7 @@ const ( ResourcePassword Resource = "password" ResourceServices Resource = "services" ResourceUsers Resource = "users" + ResourceSessions Resource = "sessions" ResourceLogs Resource = "logs" ResourceWebDAV Resource = "webdav" ResourceMetrics Resource = "metrics" diff --git a/internal/acl/resources.go b/internal/acl/resources.go index f6b639154..13e1325cc 100644 --- a/internal/acl/resources.go +++ b/internal/acl/resources.go @@ -61,7 +61,12 @@ var Resources = ACL{ RoleAdmin: GrantFullAccess, }, ResourceUsers: Roles{ - RoleAdmin: Grant{AccessAll: true, AccessOwn: true, ActionView: true, ActionCreate: true, ActionUpdate: true, ActionDelete: true, ActionSubscribe: true}, + RoleAdmin: Grant{AccessAll: true, AccessOwn: true, ActionView: true, ActionCreate: true, ActionUpdate: true, ActionDelete: true, ActionSubscribe: true}, + RoleClient: Grant{AccessOwn: true, ActionView: true}, + }, + ResourceSessions: Roles{ + RoleAdmin: GrantFullAccess, + RoleDefault: Grant{AccessOwn: true, ActionView: true, ActionCreate: true, ActionUpdate: true, ActionDelete: true, ActionSubscribe: true}, }, ResourceLogs: Roles{ RoleAdmin: GrantFullAccess, diff --git a/internal/api/api_auth.go b/internal/api/api_auth.go index 419f3393c..911bd02f8 100644 --- a/internal/api/api_auth.go +++ b/internal/api/api_auth.go @@ -18,16 +18,16 @@ func Auth(c *gin.Context, resource acl.Resource, grant acl.Permission) *entity.S // AuthAny checks if the user is authorized to access a resource with any of the specified permissions // and returns the session or nil otherwise. func AuthAny(c *gin.Context, resource acl.Resource, grants acl.Permissions) (s *entity.Session) { - // Get the client IP and session ID from the request headers. - ip := ClientIP(c) + // Get client IP and auth token from the request headers. + clientIp := ClientIP(c) authToken := AuthToken(c) // Find active session to perform authorization check or deny if no session was found. - if s = Session(authToken); s == nil { - event.AuditWarn([]string{ip, "unauthenticated", "%s %s", "denied"}, grants.String(), string(resource)) + if s = Session(clientIp, authToken); s == nil { + event.AuditWarn([]string{clientIp, "unauthenticated", "%s %s", "denied"}, grants.String(), string(resource)) return entity.SessionStatusUnauthorized() } else { - s.SetClientIP(ip) + s.SetClientIP(clientIp) } // If the request is from a client application, check its authorization based @@ -35,31 +35,31 @@ func AuthAny(c *gin.Context, resource acl.Resource, grants acl.Permissions) (s * if s.IsClient() { // Check ACL resource name against the permitted scope. if !s.HasScope(resource.String()) { - event.AuditErr([]string{ip, "client %s", "session %s", "access %s", "denied"}, s.AuthID, s.RefID, string(resource)) + event.AuditErr([]string{clientIp, "client %s", "session %s", "access %s", "denied"}, s.AuthID, s.RefID, string(resource)) return s } // Perform an authorization check based on the ACL defaults for client applications. if acl.Resources.DenyAll(resource, acl.RoleClient, grants) { - event.AuditErr([]string{ip, "client %s", "session %s", "%s %s", "denied"}, s.AuthID, s.RefID, grants.String(), string(resource)) + event.AuditErr([]string{clientIp, "client %s", "session %s", "%s %s", "denied"}, s.AuthID, s.RefID, grants.String(), string(resource)) return entity.SessionStatusForbidden() } // Additionally check the user authorization if the client belongs to a user account. if s.NoUser() { // Allow access based on the ACL defaults for client applications. - event.AuditInfo([]string{ip, "client %s", "session %s", "%s %s", "granted"}, s.AuthID, s.RefID, grants.String(), string(resource)) + event.AuditInfo([]string{clientIp, "client %s", "session %s", "%s %s", "granted"}, s.AuthID, s.RefID, grants.String(), string(resource)) } else if u := s.User(); !u.IsDisabled() && !u.IsUnknown() && u.IsRegistered() { if acl.Resources.DenyAll(resource, u.AclRole(), grants) { - event.AuditErr([]string{ip, "client %s", "session %s", "%s %s as %s", "denied"}, s.AuthID, s.RefID, grants.String(), string(resource), u.String()) + event.AuditErr([]string{clientIp, "client %s", "session %s", "%s %s as %s", "denied"}, s.AuthID, s.RefID, grants.String(), string(resource), u.String()) return entity.SessionStatusForbidden() } // Allow access based on the user role. - event.AuditInfo([]string{ip, "client %s", "session %s", "%s %s as %s", "granted"}, s.AuthID, s.RefID, grants.String(), string(resource), u.String()) + event.AuditInfo([]string{clientIp, "client %s", "session %s", "%s %s as %s", "granted"}, s.AuthID, s.RefID, grants.String(), string(resource), u.String()) } else { // Deny access if it is not a regular user account or the account has been disabled. - event.AuditErr([]string{ip, "client %s", "session %s", "%s %s as unauthorized user", "denied"}, s.AuthID, s.RefID, grants.String(), string(resource)) + event.AuditErr([]string{clientIp, "client %s", "session %s", "%s %s as unauthorized user", "denied"}, s.AuthID, s.RefID, grants.String(), string(resource)) return entity.SessionStatusForbidden() } @@ -68,13 +68,13 @@ func AuthAny(c *gin.Context, resource acl.Resource, grants acl.Permissions) (s * // Otherwise, perform a regular ACL authorization check based on the user role. if u := s.User(); u.IsUnknown() || u.IsDisabled() { - event.AuditWarn([]string{ip, "session %s", "%s %s as unauthorized user", "denied"}, s.RefID, grants.String(), string(resource)) + event.AuditWarn([]string{clientIp, "session %s", "%s %s as unauthorized user", "denied"}, s.RefID, grants.String(), string(resource)) return entity.SessionStatusUnauthorized() } else if acl.Resources.DenyAll(resource, u.AclRole(), grants) { - event.AuditErr([]string{ip, "session %s", "%s %s as %s", "denied"}, s.RefID, grants.String(), string(resource), u.AclRole().String()) + event.AuditErr([]string{clientIp, "session %s", "%s %s as %s", "denied"}, s.RefID, grants.String(), string(resource), u.AclRole().String()) return entity.SessionStatusForbidden() } else { - event.AuditInfo([]string{ip, "session %s", "%s %s as %s", "granted"}, s.RefID, grants.String(), string(resource), u.AclRole().String()) + event.AuditInfo([]string{clientIp, "session %s", "%s %s as %s", "granted"}, s.RefID, grants.String(), string(resource), u.AclRole().String()) return s } } diff --git a/internal/api/api_client_config.go b/internal/api/api_client_config.go index 47469b0ce..b5c07a0dc 100644 --- a/internal/api/api_client_config.go +++ b/internal/api/api_client_config.go @@ -19,13 +19,13 @@ func UpdateClientConfig() { // GET /api/v1/config func GetClientConfig(router *gin.RouterGroup) { router.GET("/config", func(c *gin.Context) { - s := Session(AuthToken(c)) + sess := Session(ClientIP(c), AuthToken(c)) conf := get.Config() - if s == nil { + if sess == nil { c.JSON(http.StatusOK, conf.ClientPublic()) } else { - c.JSON(http.StatusOK, conf.ClientSession(s)) + c.JSON(http.StatusOK, conf.ClientSession(sess)) } }) } diff --git a/internal/api/api_response_headers.go b/internal/api/api_response_headers.go index 5727af529..8c036d1ef 100644 --- a/internal/api/api_response_headers.go +++ b/internal/api/api_response_headers.go @@ -31,9 +31,9 @@ func AddDownloadHeader(c *gin.Context, fileName string) { c.Header(header.ContentDisposition, fmt.Sprintf("attachment; filename=%s", fileName)) } -// AddSessionHeader adds a session id header to the response. -func AddSessionHeader(c *gin.Context, id string) { - c.Header(header.XSessionID, id) +// AddAuthTokenHeader adds an X-Auth-Token header to the response. +func AddAuthTokenHeader(c *gin.Context, authToken string) { + c.Header(header.XAuthToken, authToken) } // AddContentTypeHeader adds a content type header to the response. diff --git a/internal/api/api_test.go b/internal/api/api_test.go index 250cef0da..b0879a57e 100644 --- a/internal/api/api_test.go +++ b/internal/api/api_test.go @@ -109,7 +109,7 @@ func AuthenticateUser(app *gin.Engine, router *gin.RouterGroup, name string, pas Password: password, })) - authToken = r.Header().Get(header.XSessionID) + authToken = r.Header().Get(header.XAuthToken) return } diff --git a/internal/api/session.go b/internal/api/session.go index 9918a03df..471f1a59c 100644 --- a/internal/api/session.go +++ b/internal/api/session.go @@ -3,22 +3,33 @@ package api import ( "github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/get" + "github.com/photoprism/photoprism/internal/server/limiter" "github.com/photoprism/photoprism/pkg/rnd" ) -// Session finds the client session for the specified -// auth token, or returns nil if not found. -func Session(authToken string) *entity.Session { +// Session finds the client session for the specified auth token, or returns nil if not found. +func Session(clientIp, authToken string) *entity.Session { // Skip authentication when running in public mode. if get.Config().Public() { return get.Session().Public() - } else if !rnd.IsAuthAny(authToken) { + } + + // Fail if the auth token does not have a supported format. + if !rnd.IsAuthAny(authToken) { return nil } - // Find the session based on the hashed auth - // token used as id, or return nil otherwise. - if s, err := get.Session().Get(rnd.SessionID(authToken)); err != nil { + // Fail if authentication error rate limit is exceeded. + if clientIp != "" && limiter.Auth.Reject(clientIp) { + return nil + } + + // Find the session based on the hashed auth token, or return nil otherwise. + if s, err := entity.FindSession(rnd.SessionID(authToken)); err != nil { + if clientIp != "" { + limiter.Auth.Reserve(clientIp) + } + return nil } else { return s diff --git a/internal/api/session_create.go b/internal/api/session_create.go index 01e18431b..3039a2167 100644 --- a/internal/api/session_create.go +++ b/internal/api/session_create.go @@ -11,17 +11,24 @@ import ( "github.com/photoprism/photoprism/internal/get" "github.com/photoprism/photoprism/internal/i18n" "github.com/photoprism/photoprism/internal/server/limiter" + "github.com/photoprism/photoprism/pkg/header" ) // CreateSession creates a new client session and returns it as JSON if authentication was successful. // // POST /api/v1/session +// POST /api/v1/sessions func CreateSession(router *gin.RouterGroup) { - router.POST("/session", func(c *gin.Context) { + createSessionHandler := func(c *gin.Context) { + // Disable caching of responses. + c.Header(header.CacheControl, header.CacheControlNoStore) + var f form.Login + clientIp := ClientIP(c) + if err := c.BindJSON(&f); err != nil { - event.AuditWarn([]string{ClientIP(c), "create session", "invalid request", "%s"}, err) + event.AuditWarn([]string{clientIp, "create session", "invalid request", "%s"}, err) AbortBadRequest(c) return } @@ -40,8 +47,8 @@ func CreateSession(router *gin.RouterGroup) { return } - // Check limit for failed auth requests (max. 10 per minute). - if limiter.Login.Reject(ClientIP(c)) { + // Fail if authentication error rate limit is exceeded. + if clientIp != "" && (limiter.Login.Reject(clientIp) || limiter.Auth.Reject(clientIp)) { limiter.AbortJSON(c) return } @@ -50,7 +57,7 @@ func CreateSession(router *gin.RouterGroup) { var isNew bool // Find existing session, if any. - if s := Session(AuthToken(c)); s != nil { + if s := Session(clientIp, AuthToken(c)); s != nil { // Update existing session. sess = s } else { @@ -64,25 +71,28 @@ func CreateSession(router *gin.RouterGroup) { c.AbortWithStatusJSON(sess.HttpStatus(), gin.H{"error": i18n.Msg(i18n.ErrInvalidCredentials)}) return } else if sess, err = get.Session().Save(sess); err != nil { - event.AuditErr([]string{ClientIP(c), "%s"}, err) + event.AuditErr([]string{clientIp, "%s"}, err) c.AbortWithStatusJSON(sess.HttpStatus(), gin.H{"error": i18n.Msg(i18n.ErrInvalidCredentials)}) return } else if sess == nil { c.AbortWithStatusJSON(sess.HttpStatus(), gin.H{"error": i18n.Msg(i18n.ErrUnexpected)}) return } else if isNew { - event.AuditInfo([]string{ClientIP(c), "session %s", "created"}, sess.RefID) + event.AuditInfo([]string{clientIp, "session %s", "created"}, sess.RefID) } else { - event.AuditInfo([]string{ClientIP(c), "session %s", "updated"}, sess.RefID) + event.AuditInfo([]string{clientIp, "session %s", "updated"}, sess.RefID) } - // Add session id to response headers. - AddSessionHeader(c, sess.AuthToken()) + // Add auth token to response header. + AddAuthTokenHeader(c, sess.AuthToken()) // Response includes user data, session data, and client config values. response := CreateSessionResponse(sess.AuthToken(), sess, conf.ClientSession(sess)) // Return JSON response. c.JSON(sess.HttpStatus(), response) - }) + } + + router.POST("/session", createSessionHandler) + router.POST("/sessions", createSessionHandler) } diff --git a/internal/api/session_delete.go b/internal/api/session_delete.go index 275d0ba00..94a9f6112 100644 --- a/internal/api/session_delete.go +++ b/internal/api/session_delete.go @@ -10,8 +10,10 @@ import ( "github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/internal/get" "github.com/photoprism/photoprism/internal/i18n" + "github.com/photoprism/photoprism/internal/server/limiter" "github.com/photoprism/photoprism/internal/session" "github.com/photoprism/photoprism/pkg/clean" + "github.com/photoprism/photoprism/pkg/header" "github.com/photoprism/photoprism/pkg/rnd" ) @@ -19,8 +21,12 @@ import ( // // DELETE /api/v1/session // DELETE /api/v1/session/:id +// DELETE /api/v1/sessions/:id func DeleteSession(router *gin.RouterGroup) { deleteSessionHandler := func(c *gin.Context) { + // Disable caching of responses. + c.Header(header.CacheControl, header.CacheControlNoStore) + // Abort if running in public mode. if get.Config().Public() { // Return JSON response for confirmation. @@ -30,13 +36,23 @@ func DeleteSession(router *gin.RouterGroup) { id := clean.ID(c.Param("id")) - // Get client IP address for logs and rate limiting checks. - clientIP := ClientIP(c) + // Get client IP and auth token from request headers. + clientIp := ClientIP(c) + authToken := AuthToken(c) + + // Fail if authentication error rate limit is exceeded. + if clientIp != "" && limiter.Auth.Reject(clientIp) { + limiter.AbortJSON(c) + return + } // Find session based on auth token. - sess, err := entity.FindSession(rnd.SessionID(AuthToken(c))) + sess, err := entity.FindSession(rnd.SessionID(authToken)) if err != nil || sess == nil { + if clientIp != "" { + limiter.Auth.Reserve(clientIp) + } Abort(c, http.StatusUnauthorized, i18n.ErrUnauthorized) return } else if sess.Abort(c) { @@ -45,29 +61,29 @@ func DeleteSession(router *gin.RouterGroup) { // Only admins may delete other sessions by ref id. if rnd.IsRefID(id) { - if !acl.Resources.AllowAll(acl.ResourceUsers, sess.User().AclRole(), acl.Permissions{acl.AccessAll, acl.ActionManage}) { - event.AuditErr([]string{clientIP, "session %s", "delete session %s as %s", "denied"}, sess.RefID, id, sess.User().AclRole()) + if !acl.Resources.AllowAll(acl.ResourceSessions, sess.User().AclRole(), acl.Permissions{acl.AccessAll, acl.ActionManage}) { + event.AuditErr([]string{clientIp, "session %s", "delete %s as %s", "denied"}, sess.RefID, acl.ResourceSessions.String(), sess.User().AclRole()) Abort(c, http.StatusForbidden, i18n.ErrForbidden) return } - event.AuditInfo([]string{clientIP, "session %s", "delete session %s as %s", "granted"}, sess.RefID, id, sess.User().AclRole()) + event.AuditInfo([]string{clientIp, "session %s", "delete %s as %s", "granted"}, sess.RefID, acl.ResourceSessions.String(), sess.User().AclRole()) if sess = entity.FindSessionByRefID(id); sess == nil { Abort(c, http.StatusNotFound, i18n.ErrNotFound) return } } else if id != "" && sess.ID != id { - event.AuditWarn([]string{clientIP, "session %s", "delete session as %s", "ids do not match"}, sess.RefID, sess.User().AclRole()) + event.AuditWarn([]string{clientIp, "session %s", "delete %s as %s", "ids do not match"}, sess.RefID, acl.ResourceSessions.String(), sess.User().AclRole()) Abort(c, http.StatusForbidden, i18n.ErrForbidden) return } // Delete session cache and database record. if err = sess.Delete(); err != nil { - event.AuditErr([]string{clientIP, "session %s", "delete session as %s", "%s"}, sess.RefID, sess.User().AclRole(), err) + event.AuditErr([]string{clientIp, "session %s", "delete session as %s", "%s"}, sess.RefID, sess.User().AclRole(), err) } else { - event.AuditDebug([]string{clientIP, "session %s", "deleted"}, sess.RefID) + event.AuditDebug([]string{clientIp, "session %s", "deleted"}, sess.RefID) } // Return JSON response for confirmation. @@ -76,4 +92,5 @@ func DeleteSession(router *gin.RouterGroup) { router.DELETE("/session", deleteSessionHandler) router.DELETE("/session/:id", deleteSessionHandler) + router.DELETE("/sessions/:id", deleteSessionHandler) } diff --git a/internal/api/session_get.go b/internal/api/session_get.go index 21a69b643..ccce11948 100644 --- a/internal/api/session_get.go +++ b/internal/api/session_get.go @@ -7,24 +7,34 @@ import ( "github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/get" + "github.com/photoprism/photoprism/internal/server/limiter" "github.com/photoprism/photoprism/pkg/clean" + "github.com/photoprism/photoprism/pkg/header" + "github.com/photoprism/photoprism/pkg/rnd" ) // GetSession returns the session data as JSON if authentication was successful. // +// GET /api/v1/session // GET /api/v1/session/:id +// GET /api/v1/sessions/:id func GetSession(router *gin.RouterGroup) { getSessionHandler := func(c *gin.Context) { + // Disable caching of responses. + c.Header(header.CacheControl, header.CacheControlNoStore) + id := clean.ID(c.Param("id")) - // Check authentication token. - if id == "" { - // Abort if authentication token is missing or empty. + // Abort if session id is provided but invalid. + if id != "" && !rnd.IsSessionID(id) { AbortBadRequest(c) return } conf := get.Config() + + // Get client IP and auth token from request headers. + clientIp := ClientIP(c) authToken := AuthToken(c) // Skip authentication if app is running in public mode. @@ -33,18 +43,25 @@ func GetSession(router *gin.RouterGroup) { sess = get.Session().Public() id = sess.ID authToken = sess.AuthToken() + } else if clientIp != "" && limiter.Auth.Reject(clientIp) { + // Fail if authentication error rate limit is exceeded. + limiter.AbortJSON(c) + return } else { - sess = Session(authToken) + sess = Session(clientIp, authToken) } switch { case sess == nil: + if clientIp != "" { + limiter.Auth.Reserve(clientIp) + } AbortUnauthorized(c) return case sess.Expired(), sess.ID == "": AbortUnauthorized(c) return - case sess.Invalid(), sess.ID != id && !conf.Public(): + case sess.Invalid(), id != "" && sess.ID != id && !conf.Public(): AbortForbidden(c) return } @@ -52,8 +69,8 @@ func GetSession(router *gin.RouterGroup) { // Update user information. sess.RefreshUser() - // Add session id to response headers. - AddSessionHeader(c, authToken) + // Add auth token to response header. + AddAuthTokenHeader(c, authToken) // Response includes user data, session data, and client config values. response := GetSessionResponse(authToken, sess, get.Config().ClientSession(sess)) @@ -62,5 +79,7 @@ func GetSession(router *gin.RouterGroup) { c.JSON(http.StatusOK, response) } + router.GET("/session", getSessionHandler) router.GET("/session/:id", getSessionHandler) + router.GET("/sessions/:id", getSessionHandler) } diff --git a/internal/api/session_oauth.go b/internal/api/session_oauth.go index da84b7962..5b5f9eb54 100644 --- a/internal/api/session_oauth.go +++ b/internal/api/session_oauth.go @@ -25,12 +25,15 @@ import ( // POST /api/v1/oauth/token func CreateOAuthToken(router *gin.RouterGroup) { router.POST("/oauth/token", func(c *gin.Context) { + // Disable caching of responses. + c.Header(header.CacheControl, header.CacheControlNoStore) + // Get client IP address for logs and rate limiting checks. - clientIP := ClientIP(c) + clientIp := ClientIP(c) // Abort if running in public mode. if get.Config().Public() { - event.AuditErr([]string{clientIP, "create client session", "disabled in public mode"}) + event.AuditErr([]string{clientIp, "create client session", "disabled in public mode"}) Abort(c, http.StatusForbidden, i18n.ErrForbidden) return } @@ -45,20 +48,20 @@ func CreateOAuthToken(router *gin.RouterGroup) { f.ClientID = clientId f.ClientSecret = clientSecret } else if err = c.ShouldBind(&f); err != nil { - event.AuditWarn([]string{clientIP, "create client session", "%s"}, err) + event.AuditWarn([]string{clientIp, "create client session", "%s"}, err) AbortBadRequest(c) return } // Check the credentials for completeness and the correct format. if err = f.Validate(); err != nil { - event.AuditWarn([]string{clientIP, "create client session", "%s"}, err) + event.AuditWarn([]string{clientIp, "create client session", "%s"}, err) c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": i18n.Msg(i18n.ErrInvalidCredentials)}) return } - // Check limit for failed auth requests (max. 10 per minute). - if limiter.Login.Reject(clientIP) { + // Fail if authentication error rate limit is exceeded. + if clientIp != "" && (limiter.Login.Reject(clientIp) || limiter.Auth.Reject(clientIp)) { limiter.AbortJSON(c) return } @@ -68,22 +71,22 @@ func CreateOAuthToken(router *gin.RouterGroup) { // Abort if the client ID or secret are invalid. if client == nil { - event.AuditWarn([]string{clientIP, "client %s", "create session", "invalid client_id"}, f.ClientID) + event.AuditWarn([]string{clientIp, "client %s", "create session", "invalid client id"}, f.ClientID) c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": i18n.Msg(i18n.ErrInvalidCredentials)}) - limiter.Login.Reserve(clientIP) + limiter.Login.Reserve(clientIp) return } else if !client.AuthEnabled { - event.AuditWarn([]string{clientIP, "client %s", "create session", "authentication disabled"}, f.ClientID) + event.AuditWarn([]string{clientIp, "client %s", "create session", "authentication disabled"}, 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", "method %s not supported"}, f.ClientID, clean.LogQuote(method.String())) + event.AuditWarn([]string{clientIp, "client %s", "create session", "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.WrongSecret(f.ClientSecret) { - event.AuditWarn([]string{clientIP, "client %s", "create session", "invalid client_secret"}, f.ClientID) + event.AuditWarn([]string{clientIp, "client %s", "create session", "invalid client secret"}, f.ClientID) c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": i18n.Msg(i18n.ErrInvalidCredentials)}) - limiter.Login.Reserve(clientIP) + limiter.Login.Reserve(clientIp) return } @@ -92,20 +95,20 @@ func CreateOAuthToken(router *gin.RouterGroup) { // Try to log in and save session if successful. if sess, err = get.Session().Save(sess); err != nil { - event.AuditErr([]string{clientIP, "client %s", "create session", "%s"}, f.ClientID, err) + event.AuditErr([]string{clientIp, "client %s", "create session", "%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", StatusFailed.String()}, f.ClientID) + event.AuditErr([]string{clientIp, "client %s", "create session", 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", "created"}, f.ClientID, sess.RefID) + event.AuditInfo([]string{clientIp, "client %s", "session %s", "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", "%s deleted"}, f.ClientID, english.Plural(deleted, "old session", "old sessions")) + event.AuditInfo([]string{clientIp, "client %s", "%s deleted"}, f.ClientID, english.Plural(deleted, "old session", "old sessions")) } // Response includes access token, token type, and token lifetime. @@ -125,12 +128,15 @@ func CreateOAuthToken(router *gin.RouterGroup) { // POST /api/v1/oauth/revoke func RevokeOAuthToken(router *gin.RouterGroup) { router.POST("/oauth/revoke", func(c *gin.Context) { + // Disable caching of responses. + c.Header(header.CacheControl, header.CacheControlNoStore) + // Get client IP address for logs and rate limiting checks. - clientIP := ClientIP(c) + clientIp := ClientIP(c) // Abort if running in public mode. if get.Config().Public() { - event.AuditErr([]string{clientIP, "delete client session", "disabled in public mode"}) + event.AuditErr([]string{clientIp, "delete client session", "disabled in public mode"}) Abort(c, http.StatusForbidden, i18n.ErrForbidden) return } @@ -144,7 +150,7 @@ func RevokeOAuthToken(router *gin.RouterGroup) { // 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, "delete client session", "%s"}, err) + event.AuditWarn([]string{clientIp, "delete client session", "%s"}, err) AbortBadRequest(c) return } else if f.Empty() { @@ -154,7 +160,7 @@ func RevokeOAuthToken(router *gin.RouterGroup) { // Check the token form values. if err = f.Validate(); err != nil { - event.AuditWarn([]string{clientIP, "delete client session", "%s"}, err) + event.AuditWarn([]string{clientIp, "delete client session", "%s"}, err) AbortBadRequest(c) return } @@ -163,28 +169,28 @@ func RevokeOAuthToken(router *gin.RouterGroup) { sess, err := entity.FindSession(rnd.SessionID(f.AuthToken)) if err != nil { - event.AuditErr([]string{clientIP, "client %s", "session %s", "delete session as %s", "%s"}, clean.Log(sess.AuthID), clean.Log(sess.RefID), acl.RoleClient.String(), err.Error()) + event.AuditErr([]string{clientIp, "client %s", "session %s", "delete session as %s", "%s"}, clean.Log(sess.AuthID), clean.Log(sess.RefID), acl.RoleClient.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", "denied"}, clean.Log(sess.AuthID), clean.Log(sess.RefID), acl.RoleClient.String()) + event.AuditErr([]string{clientIp, "client %s", "session %s", "delete session as %s", "denied"}, clean.Log(sess.AuthID), clean.Log(sess.RefID), acl.RoleClient.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", "denied"}, clean.Log(sess.AuthID), clean.Log(sess.RefID), acl.RoleClient.String()) + event.AuditErr([]string{clientIp, "client %s", "session %s", "delete session as %s", "denied"}, clean.Log(sess.AuthID), clean.Log(sess.RefID), acl.RoleClient.String()) return } else if !sess.IsClient() { - event.AuditErr([]string{clientIP, "client %s", "session %s", "delete session as %s", "denied"}, clean.Log(sess.AuthID), clean.Log(sess.RefID), acl.RoleClient.String()) + event.AuditErr([]string{clientIp, "client %s", "session %s", "delete session as %s", "denied"}, clean.Log(sess.AuthID), clean.Log(sess.RefID), acl.RoleClient.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", "granted"}, clean.Log(sess.AuthID), clean.Log(sess.RefID), acl.RoleClient.String()) + event.AuditInfo([]string{clientIp, "client %s", "session %s", "delete session as %s", "granted"}, clean.Log(sess.AuthID), clean.Log(sess.RefID), acl.RoleClient.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", "%s"}, clean.Log(sess.AuthID), clean.Log(sess.RefID), acl.RoleClient.String(), err) + event.AuditErr([]string{clientIp, "client %s", "session %s", "delete session as %s", "%s"}, clean.Log(sess.AuthID), clean.Log(sess.RefID), acl.RoleClient.String(), err) // Return JSON error. c.AbortWithStatusJSON(http.StatusNotFound, i18n.NewResponse(http.StatusNotFound, i18n.ErrNotFound)) @@ -192,7 +198,7 @@ func RevokeOAuthToken(router *gin.RouterGroup) { } // Log event. - event.AuditInfo([]string{clientIP, "client %s", "session %s", "deleted"}, clean.Log(sess.AuthID), clean.Log(sess.RefID)) + event.AuditInfo([]string{clientIp, "client %s", "session %s", "deleted"}, clean.Log(sess.AuthID), clean.Log(sess.RefID)) // Return JSON response for confirmation. c.JSON(http.StatusOK, DeleteSessionResponse(sess.ID)) diff --git a/internal/api/session_test.go b/internal/api/session_test.go index bfdfe2b17..7b59e737e 100644 --- a/internal/api/session_test.go +++ b/internal/api/session_test.go @@ -17,8 +17,8 @@ import ( func TestSession(t *testing.T) { t.Run("Public", func(t *testing.T) { sess := get.Session().Public() - assert.Equal(t, sess, Session("")) - assert.Equal(t, sess, Session("638bffc9b86a8fda0d908ebee84a43930cb8d1e3507f4aa0")) + assert.Equal(t, sess, Session("1.2.3.4", "")) + assert.Equal(t, sess, Session("1.2.3.4", "1234ffc9b86a8fda0d908ebee84a43930cb8d1e3507f4aa0")) }) } @@ -213,13 +213,43 @@ func TestGetSession(t *testing.T) { GetSession(router) authToken := AuthenticateAdmin(app, router) - t.Logf("Session ID: %s", authToken) + t.Logf("Auth Token: %s", authToken) + r := AuthenticatedRequest(app, http.MethodGet, "/api/v1/session", authToken) + t.Logf("Response Body: %s", r.Body.String()) + id := gjson.Get(r.Body.String(), "session_id").String() + assert.Equal(t, rnd.SessionID(authToken), id) + assert.Equal(t, http.StatusOK, r.Code) + }) + t.Run("AdminAuthenticatedRequestWithID", func(t *testing.T) { + app, router, conf := NewApiTest() + conf.SetAuthMode(config.AuthModePasswd) + defer conf.SetAuthMode(config.AuthModePublic) + + GetSession(router) + authToken := AuthenticateAdmin(app, router) + + t.Logf("Auth Token: %s", authToken) r := AuthenticatedRequest(app, http.MethodGet, "/api/v1/session/"+rnd.SessionID(authToken), authToken) t.Logf("Response Body: %s", r.Body.String()) id := gjson.Get(r.Body.String(), "session_id").String() assert.Equal(t, rnd.SessionID(authToken), id) assert.Equal(t, http.StatusOK, r.Code) }) + t.Run("AdminAuthenticatedRequestSessionsWithID", func(t *testing.T) { + app, router, conf := NewApiTest() + conf.SetAuthMode(config.AuthModePasswd) + defer conf.SetAuthMode(config.AuthModePublic) + + GetSession(router) + authToken := AuthenticateAdmin(app, router) + + t.Logf("Auth Token: %s", authToken) + r := AuthenticatedRequest(app, http.MethodGet, "/api/v1/sessions/"+rnd.SessionID(authToken), authToken) + t.Logf("Response Body: %s", r.Body.String()) + id := gjson.Get(r.Body.String(), "session_id").String() + assert.Equal(t, rnd.SessionID(authToken), id) + assert.Equal(t, http.StatusOK, r.Code) + }) } func TestDeleteSession(t *testing.T) { @@ -231,11 +261,8 @@ func TestDeleteSession(t *testing.T) { DeleteSession(router) authToken := AuthenticateAdmin(app, router) - // f9ae12e95a01bcc7faae6497124cd721eaf13c1dad301dbc - t.Logf("authToken: %s", authToken) - - r := AuthenticatedRequest(app, http.MethodDelete, "/api/v1/session/"+rnd.SessionID(authToken), authToken) - assert.Equal(t, http.StatusOK, r.Code) + r := AuthenticatedRequest(app, http.MethodDelete, "/api/v1/session/"+rnd.SessionID(authToken), "") + assert.Equal(t, http.StatusUnauthorized, r.Code) }) t.Run("AdminAuthenticatedRequest", func(t *testing.T) { app, router, conf := NewApiTest() @@ -245,9 +272,31 @@ func TestDeleteSession(t *testing.T) { DeleteSession(router) authToken := AuthenticateAdmin(app, router) + r := AuthenticatedRequest(app, http.MethodDelete, "/api/v1/session", authToken) + assert.Equal(t, http.StatusOK, r.Code) + }) + t.Run("AdminAuthenticatedRequestWithID", func(t *testing.T) { + app, router, conf := NewApiTest() + conf.SetAuthMode(config.AuthModePasswd) + defer conf.SetAuthMode(config.AuthModePublic) + + DeleteSession(router) + authToken := AuthenticateAdmin(app, router) + r := AuthenticatedRequest(app, http.MethodDelete, "/api/v1/session/"+rnd.SessionID(authToken), authToken) assert.Equal(t, http.StatusOK, r.Code) }) + t.Run("AdminAuthenticatedRequestSessionsWithID", func(t *testing.T) { + app, router, conf := NewApiTest() + conf.SetAuthMode(config.AuthModePasswd) + defer conf.SetAuthMode(config.AuthModePublic) + + DeleteSession(router) + authToken := AuthenticateAdmin(app, router) + + r := AuthenticatedRequest(app, http.MethodDelete, "/api/v1/sessions/"+rnd.SessionID(authToken), authToken) + assert.Equal(t, http.StatusOK, r.Code) + }) t.Run("AdminAuthenticatedLogout", func(t *testing.T) { app, router, conf := NewApiTest() conf.SetAuthMode(config.AuthModePasswd) diff --git a/internal/api/websocket_reader.go b/internal/api/websocket_reader.go index 0a7ffbf07..568db6a5d 100644 --- a/internal/api/websocket_reader.go +++ b/internal/api/websocket_reader.go @@ -35,7 +35,7 @@ func wsReader(ws *websocket.Conn, writeMutex *sync.Mutex, connId string, conf *c if jsonErr := json.Unmarshal(m, &info); jsonErr != nil { // Do nothing. } else { - if s := Session(info.AuthToken); s != nil { + if s := Session(ws.RemoteAddr().String(), info.AuthToken); s != nil { wsAuth.mutex.Lock() wsAuth.sid[connId] = s.ID wsAuth.rid[connId] = s.RefID diff --git a/internal/entity/auth_session.go b/internal/entity/auth_session.go index a8db71d0f..4851a0dc6 100644 --- a/internal/entity/auth_session.go +++ b/internal/entity/auth_session.go @@ -154,6 +154,11 @@ func SessionStatusForbidden() *Session { return &Session{Status: http.StatusForbidden} } +// SessionStatusTooManyRequests returns a session with status too many requests (429). +func SessionStatusTooManyRequests() *Session { + return &Session{Status: http.StatusTooManyRequests} +} + // FindSessionByRefID finds an existing session by ref ID. func FindSessionByRefID(refId string) *Session { if !rnd.IsRefID(refId) { @@ -340,9 +345,15 @@ func (m *Session) AuthInfo() string { return fmt.Sprintf("%s (%s)", provider.Pretty(), method.Pretty()) } -// Provider returns the authentication provider. -func (m *Session) Provider() authn.ProviderType { - return authn.Provider(m.AuthProvider) +// SetAuthID sets a custom authentication identifier. +func (m *Session) SetAuthID(id string) *Session { + if id == "" { + return m + } + + m.AuthID = clean.Name(id) + + return m } // Method returns the authentication method. @@ -350,9 +361,20 @@ func (m *Session) Method() authn.MethodType { return authn.Method(m.AuthMethod) } -// IsClient checks whether this session is used to authenticate an API client. -func (m *Session) IsClient() bool { - return authn.Provider(m.AuthProvider).IsClient() +// SetMethod sets a custom authentication method. +func (m *Session) SetMethod(method authn.MethodType) *Session { + if method == "" { + return m + } + + m.AuthMethod = method.String() + + 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. @@ -366,6 +388,11 @@ func (m *Session) SetProvider(provider authn.ProviderType) *Session { return m } +// IsClient checks whether this session is used to authenticate an API client. +func (m *Session) IsClient() bool { + return authn.Provider(m.AuthProvider).IsClient() +} + // ChangePassword changes the password of the current user. func (m *Session) ChangePassword(newPw string) (err error) { u := m.User() @@ -465,8 +492,8 @@ func (m *Session) SetContext(c *gin.Context) *Session { } // Set client ip address from request context. - if ip := header.ClientIP(c); ip != "" { - m.SetClientIP(ip) + if clientIp := header.ClientIP(c); clientIp != "" { + m.SetClientIP(clientIp) } else if m.ClientIP == "" { // Unit tests often do not set a client IP. m.SetClientIP(UnknownIP) @@ -489,8 +516,8 @@ func (m *Session) UpdateContext(c *gin.Context) *Session { changed := false // Set client ip address from request context. - if ip := header.ClientIP(c); ip != "" && (ip != m.ClientIP || m.LoginIP == "") { - m.SetClientIP(ip) + if clientIp := header.ClientIP(c); clientIp != "" && (clientIp != m.ClientIP || m.LoginIP == "") { + m.SetClientIP(clientIp) changed = true } else if m.ClientIP == "" { // Unit tests often do not set a client IP. @@ -701,6 +728,8 @@ func (m *Session) Abort(c *gin.Context) bool { switch m.Status { case http.StatusUnauthorized: c.AbortWithStatusJSON(m.Status, i18n.NewResponse(m.Status, i18n.ErrUnauthorized)) + case http.StatusTooManyRequests: + c.AbortWithStatusJSON(m.Status, gin.H{"error": "rate limit exceeded", "code": http.StatusTooManyRequests}) default: c.AbortWithStatusJSON(http.StatusForbidden, i18n.NewResponse(http.StatusForbidden, i18n.ErrForbidden)) } diff --git a/internal/entity/auth_session_fixtures.go b/internal/entity/auth_session_fixtures.go index ed3d3f2d7..f1c4dcd4e 100644 --- a/internal/entity/auth_session_fixtures.go +++ b/internal/entity/auth_session_fixtures.go @@ -50,6 +50,21 @@ var SessionFixtures = SessionMap{ UserUID: UserFixtures.Pointer("alice").UserUID, UserName: UserFixtures.Pointer("alice").UserName, }, + "alice_token_personal": { + authToken: "bSJu9-2sr54-ZOasm-8QusP", + ID: rnd.SessionID("bSJu9-2sr54-ZOasm-8QusP"), + RefID: "sess6ey1ykya", + SessTimeout: -1, + SessExpires: UnixTime() + UnixDay, + AuthScope: clean.Scope("*"), + AuthProvider: authn.ProviderClient.String(), + AuthMethod: authn.MethodAccessToken.String(), + AuthID: "alice_token_personal", + LastActive: -1, + user: UserFixtures.Pointer("alice"), + UserUID: UserFixtures.Pointer("alice").UserUID, + UserName: UserFixtures.Pointer("alice").UserName, + }, "alice_token_webdav": { authToken: "bHcZP-YxRbi-irKII-W1kpz", ID: rnd.SessionID("bHcZP-YxRbi-irKII-W1kpz"), diff --git a/internal/entity/auth_session_login.go b/internal/entity/auth_session_login.go index c516e7461..e30de8bd9 100644 --- a/internal/entity/auth_session_login.go +++ b/internal/entity/auth_session_login.go @@ -7,21 +7,28 @@ import ( "github.com/gin-gonic/gin" + "github.com/photoprism/photoprism/internal/acl" "github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/internal/form" "github.com/photoprism/photoprism/internal/i18n" "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/rnd" "github.com/photoprism/photoprism/pkg/txt" ) // 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, err error) { - name := f.Username() + // Get username from login form. + nameName := f.Username() - user = FindUserByName(name) - err = AuthLocal(user, f, m) + // Find registered user account. + user = FindUserByName(nameName) + + // Try local authentication. + provider, err = AuthLocal(user, f, m, c) if err != nil { return user, authn.ProviderNone, err @@ -30,60 +37,116 @@ var Auth = func(f form.Login, m *Session, c *gin.Context) (user *User, provider // Update login timestamp. user.UpdateLoginTime() - return user, authn.ProviderLocal, err + return user, provider, err +} + +// AuthSession returns the client session that belongs to the auth token provided, or returns nil if it was not found. +func AuthSession(f form.Login, c *gin.Context) (sess *Session, user *User, err error) { + if f.Password == "" { + // Abort authentication if no token was provided. + return nil, nil, fmt.Errorf("no auth secret provided") + } else if !rnd.IsAuthSecret(f.Password) { + // Abort authentication if token doesn't match expected format. + return nil, nil, fmt.Errorf("auth secret does not match expected format") + } + + // Get session ID for the auth token provided. + sid := rnd.SessionID(f.Password) + + // Find the session based on the hashed token used as session ID and return it. + sess, err = FindSession(sid) + + // Log error and return nil if no matching session was found. + if sess == nil || err != nil { + return nil, nil, fmt.Errorf("invalid auth secret") + } + + // Update the client IP and the user agent from + // the request context if they have changed. + sess.UpdateContext(c) + + // Returns session and user if all checks have passed. + return sess, sess.User(), nil } // AuthLocal authenticates against the local user database with the specified username and password. -func AuthLocal(user *User, f form.Login, m *Session) (err error) { - name := f.Username() +func AuthLocal(user *User, f form.Login, m *Session, c *gin.Context) (authn.ProviderType, error) { + // Get client IP from request context. + clientIp := header.ClientIP(c) - // User found? + // Get username from login form. + userName := f.Username() + + // Check if a session has been created. + if m == nil { + event.AuditErr([]string{clientIp, "login as %s", "invalid session"}, clean.LogQuote(userName)) + return authn.ProviderNone, i18n.Error(i18n.ErrInvalidCredentials) + } + + // Check if user account exists. if user == nil { message := "account not found" - if m != nil { - limiter.Login.Reserve(m.IP()) - event.AuditWarn([]string{m.IP(), "session %s", "login as %s", message}, m.RefID, clean.LogQuote(name)) - event.LoginError(m.IP(), "api", name, m.UserAgent, message) - m.Status = http.StatusUnauthorized - } - return i18n.Error(i18n.ErrInvalidCredentials) + limiter.Login.Reserve(clientIp) + 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 + return authn.ProviderNone, i18n.Error(i18n.ErrInvalidCredentials) } // Login allowed? if !user.Provider().IsDefault() && !user.Provider().IsLocal() { message := fmt.Sprintf("%s authentication disabled", authn.ProviderLocal.String()) - if m != nil { - event.AuditWarn([]string{m.IP(), "session %s", "login as %s", message}, m.RefID, clean.LogQuote(name)) - event.LoginError(m.IP(), "api", name, m.UserAgent, message) - m.Status = http.StatusUnauthorized - } - return i18n.Error(i18n.ErrInvalidCredentials) + 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 + return authn.ProviderNone, i18n.Error(i18n.ErrInvalidCredentials) } else if !user.CanLogIn() { message := "account disabled" - if m != nil { - event.AuditWarn([]string{m.IP(), "session %s", "login as %s", message}, m.RefID, clean.LogQuote(name)) - event.LoginError(m.IP(), "api", name, m.UserAgent, message) - m.Status = http.StatusUnauthorized - } - return i18n.Error(i18n.ErrInvalidCredentials) + 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 + return authn.ProviderNone, i18n.Error(i18n.ErrInvalidCredentials) } - // Password valid? + // Authentication with personal access token if a valid secret has been provided as password. + if authSess, authUser, err := AuthSession(f, c); err == nil { + if !authUser.IsRegistered() || authUser.UserUID != user.UserUID { + message := "incorrect user" + limiter.Login.Reserve(clientIp) + event.AuditErr([]string{clientIp, "session %s", "login as %s with auth secret", message}, m.RefID, clean.LogQuote(userName)) + event.LoginError(clientIp, "api", userName, m.UserAgent, message) + m.Status = http.StatusUnauthorized + return authn.ProviderNone, i18n.Error(i18n.ErrInvalidCredentials) + } else if !authSess.IsClient() || authSess.Method() != authn.MethodAccessToken || !authSess.HasScope(acl.ResourceSessions.String()) { + message := "unauthorized" + limiter.Login.Reserve(clientIp) + event.AuditErr([]string{clientIp, "session %s", "login as %s with auth secret", message}, m.RefID, clean.LogQuote(userName)) + event.LoginError(clientIp, "api", userName, m.UserAgent, message) + m.Status = http.StatusUnauthorized + return authn.ProviderNone, i18n.Error(i18n.ErrInvalidCredentials) + } else { + m.SetAuthID(authSess.AuthID) + m.SetMethod(authn.MethodSession) + event.AuditInfo([]string{clientIp, "session %s", "login as %s with auth secret", "succeeded"}, m.RefID, clean.LogQuote(userName)) + event.LoginInfo(clientIp, "api", userName, m.UserAgent) + return authn.ProviderClient, err + } + } + + // Otherwise, check account password. if user.WrongPassword(f.Password) { message := "incorrect password" - if m != nil { - limiter.Login.Reserve(m.IP()) - event.AuditErr([]string{m.IP(), "session %s", "login as %s", message}, m.RefID, clean.LogQuote(name)) - event.LoginError(m.IP(), "api", name, m.UserAgent, message) - m.Status = http.StatusUnauthorized - } - return i18n.Error(i18n.ErrInvalidCredentials) + limiter.Login.Reserve(clientIp) + 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 + return authn.ProviderNone, i18n.Error(i18n.ErrInvalidCredentials) } else if m != nil { - event.AuditInfo([]string{m.IP(), "session %s", "login as %s", "succeeded"}, m.RefID, clean.LogQuote(name)) - event.LoginInfo(m.IP(), "api", name, m.UserAgent) + event.AuditInfo([]string{clientIp, "session %s", "login as %s", "succeeded"}, m.RefID, clean.LogQuote(userName)) + event.LoginInfo(clientIp, "api", userName, m.UserAgent) } - return err + return authn.ProviderLocal, nil } // LogIn performs authentication checks against the specified login form. diff --git a/internal/entity/auth_session_login_test.go b/internal/entity/auth_session_login_test.go index c3755541e..a45104322 100644 --- a/internal/entity/auth_session_login_test.go +++ b/internal/entity/auth_session_login_test.go @@ -5,78 +5,246 @@ import ( "net/http/httptest" "testing" - "github.com/photoprism/photoprism/pkg/authn" - "github.com/gin-gonic/gin" + "github.com/stretchr/testify/assert" + "github.com/photoprism/photoprism/internal/acl" "github.com/photoprism/photoprism/internal/form" + "github.com/photoprism/photoprism/pkg/authn" + "github.com/photoprism/photoprism/pkg/rnd" ) +func TestAuthSession(t *testing.T) { + t.Run("RandomAuthSecret", func(t *testing.T) { + // Create test request form. + f := form.Login{ + UserName: "alice", + Password: rnd.AuthSecret(), + } + + // Create test request context. + c, _ := gin.CreateTestContext(httptest.NewRecorder()) + c.Request = httptest.NewRequest(http.MethodPost, "/api/v1/session", form.AsReader(f)) + c.Request.RemoteAddr = "1.2.3.4" + + // Check authentication result. + authSess, authUser, authErr := AuthSession(f, c) + + assert.Nil(t, authSess) + assert.Nil(t, authUser) + assert.Error(t, authErr) + }) + t.Run("RandomAuthToken", func(t *testing.T) { + // Create test request form. + f := form.Login{ + UserName: "alice", + Password: rnd.AuthToken(), + } + + // Create test request context. + c, _ := gin.CreateTestContext(httptest.NewRecorder()) + c.Request = httptest.NewRequest(http.MethodPost, "/api/v1/session", form.AsReader(f)) + c.Request.RemoteAddr = "1.2.3.4" + + // Check authentication result. + authSess, authUser, authErr := AuthSession(f, c) + + assert.Nil(t, authSess) + assert.Nil(t, authUser) + assert.Error(t, authErr) + }) + t.Run("AliceAuthToken", func(t *testing.T) { + s := SessionFixtures.Get("alice_token") + + // Create test request form. + f := form.Login{ + UserName: "alice", + Password: s.AuthToken(), + } + + // Create test request context. + c, _ := gin.CreateTestContext(httptest.NewRecorder()) + c.Request = httptest.NewRequest(http.MethodPost, "/api/v1/session", form.AsReader(f)) + c.Request.RemoteAddr = "1.2.3.4" + + // Check authentication result. + authSess, authUser, authErr := AuthSession(f, c) + + assert.Nil(t, authSess) + assert.Nil(t, authUser) + assert.Error(t, authErr) + }) + t.Run("AliceTokenPersonal", func(t *testing.T) { + s := SessionFixtures.Get("alice_token_personal") + u := FindUserByName("alice") + + // Create test request form. + f := form.Login{ + UserName: "alice", + Password: s.AuthToken(), + } + + // Create test request context. + c, _ := gin.CreateTestContext(httptest.NewRecorder()) + c.Request = httptest.NewRequest(http.MethodPost, "/api/v1/session", form.AsReader(f)) + c.Request.RemoteAddr = "1.2.3.4" + + // Check authentication result. + authSess, authUser, authErr := AuthSession(f, c) + + if authErr != nil { + t.Fatal(authErr) + } + + assert.NotNil(t, authSess) + assert.NotNil(t, authUser) + + assert.Equal(t, u.UserUID, s.UserUID) + assert.Equal(t, u.Username(), s.Username()) + assert.Equal(t, authUser.UserUID, authSess.UserUID) + assert.Equal(t, authUser.Username(), authSess.Username()) + assert.Equal(t, authUser.UserUID, authUser.UserUID) + assert.Equal(t, authUser.Username(), authUser.Username()) + + assert.True(t, authSess.IsRegistered()) + assert.True(t, authSess.HasUser()) + + assert.True(t, authSess.HasScope(acl.ResourceWebDAV.String())) + assert.True(t, authSess.HasScope(acl.ResourceSessions.String())) + }) + t.Run("AliceTokenWebdav", func(t *testing.T) { + s := SessionFixtures.Get("alice_token_webdav") + u := FindUserByName("alice") + + // Create test request form. + f := form.Login{ + UserName: "alice", + Password: s.AuthToken(), + } + + // Create test request context. + c, _ := gin.CreateTestContext(httptest.NewRecorder()) + c.Request = httptest.NewRequest(http.MethodPost, "/api/v1/session", form.AsReader(f)) + c.Request.RemoteAddr = "1.2.3.4" + + // Check authentication result. + authSess, authUser, authErr := AuthSession(f, c) + + if authErr != nil { + t.Fatal(authErr) + } + + assert.NotNil(t, authSess) + assert.NotNil(t, authUser) + + assert.Equal(t, u.UserUID, s.UserUID) + assert.Equal(t, u.Username(), s.Username()) + assert.Equal(t, authUser.UserUID, authSess.UserUID) + assert.Equal(t, authUser.Username(), authSess.Username()) + assert.Equal(t, authUser.UserUID, authUser.UserUID) + assert.Equal(t, authUser.Username(), authUser.Username()) + + assert.True(t, authSess.IsRegistered()) + assert.True(t, authSess.HasUser()) + + assert.True(t, authSess.HasScope(acl.ResourceWebDAV.String())) + assert.False(t, authSess.HasScope(acl.ResourceSessions.String())) + }) +} + func TestAuthLocal(t *testing.T) { t.Run("Alice", func(t *testing.T) { - m := FindSessionByRefID("sessxkkcabch") - u := FindUserByName("alice") + // Create test request form. frm := form.Login{ UserName: "alice", Password: "Alice123!", } - if err := AuthLocal(u, frm, m); err != nil { + // Create test request context. + c, _ := gin.CreateTestContext(httptest.NewRecorder()) + c.Request = httptest.NewRequest(http.MethodPost, "/api/v1/session", form.AsReader(frm)) + c.Request.RemoteAddr = "1.2.3.4" + + // Check authentication result. + if provider, err := AuthLocal(u, frm, m, c); err != nil { t.Fatal(err) + } else { + assert.Equal(t, authn.ProviderLocal, provider) } }) t.Run("Wrong credentials", func(t *testing.T) { - m := FindSessionByRefID("sessxkkcabch") - u := FindUserByName("alice") + // Create test request form. frm := form.Login{ UserName: "alice", Password: "photoprism", } - if err := AuthLocal(u, frm, m); err == nil { + // Create test request context. + c, _ := gin.CreateTestContext(httptest.NewRecorder()) + c.Request = httptest.NewRequest(http.MethodPost, "/api/v1/session", form.AsReader(frm)) + c.Request.RemoteAddr = "1.2.3.4" + + // Check authentication result. + if provider, err := AuthLocal(u, frm, m, c); err == nil { t.Fatal("auth should fail") + } else { + assert.Equal(t, authn.ProviderNone, provider) } }) t.Run("No login rights", func(t *testing.T) { - m := &Session{} - u := FindUserByName("friend") u.CanLogin = false + // Create test request form. frm := form.Login{ UserName: "friend", Password: "!Friend321", } - if err := AuthLocal(u, frm, m); err == nil { + // Create test request context. + c, _ := gin.CreateTestContext(httptest.NewRecorder()) + c.Request = httptest.NewRequest(http.MethodPost, "/api/v1/session", form.AsReader(frm)) + c.Request.RemoteAddr = "1.2.3.4" + + // Check authentication result. + if provider, err := AuthLocal(u, frm, m, c); err == nil { t.Fatal("auth should fail") + } else { + assert.Equal(t, authn.ProviderNone, provider) } u.CanLogin = true }) t.Run("Authentication disabled", func(t *testing.T) { - m := &Session{} - u := FindUserByName("friend") u.SetProvider(authn.ProviderNone) + // Create test request form. frm := form.Login{ UserName: "friend", Password: "!Friend321", } - if err := AuthLocal(u, frm, m); err == nil { + // Create test request context. + c, _ := gin.CreateTestContext(httptest.NewRecorder()) + c.Request = httptest.NewRequest(http.MethodPost, "/api/v1/session", form.AsReader(frm)) + c.Request.RemoteAddr = "1.2.3.4" + + // Check authentication result. + if provider, err := AuthLocal(u, frm, m, c); err == nil { t.Fatal("auth should fail") + } else { + assert.Equal(t, authn.ProviderNone, provider) } u.SetProvider(authn.ProviderLocal) @@ -85,9 +253,7 @@ func TestAuthLocal(t *testing.T) { func TestSessionLogIn(t *testing.T) { const clientIp = "1.2.3.4" - rec := httptest.NewRecorder() - ctx, _ := gin.CreateTestContext(rec) t.Run("Admin", func(t *testing.T) { m := NewSession(UnixDay, UnixHour*6) @@ -99,12 +265,13 @@ func TestSessionLogIn(t *testing.T) { Password: "photoprism", } - // Create HTTP request. - ctx.Request = httptest.NewRequest(http.MethodPost, "/api/v1/session", form.AsReader(frm)) - ctx.Request.RemoteAddr = "1.2.3.4" + // Create test request context. + c, _ := gin.CreateTestContext(rec) + c.Request = httptest.NewRequest(http.MethodPost, "/api/v1/session", form.AsReader(frm)) + c.Request.RemoteAddr = "1.2.3.4" // Try to log in. - if err := m.LogIn(frm, ctx); err != nil { + if err := m.LogIn(frm, c); err != nil { t.Fatal(err) } }) @@ -118,12 +285,13 @@ func TestSessionLogIn(t *testing.T) { Password: "wrong", } - // Create HTTP request. - ctx.Request = httptest.NewRequest(http.MethodPost, "/api/v1/session", form.AsReader(frm)) - ctx.Request.RemoteAddr = "1.2.3.4" + // Create test request context. + c, _ := gin.CreateTestContext(rec) + c.Request = httptest.NewRequest(http.MethodPost, "/api/v1/session", form.AsReader(frm)) + c.Request.RemoteAddr = "1.2.3.4" // Try to log in. - if err := m.LogIn(frm, ctx); err == nil { + if err := m.LogIn(frm, c); err == nil { t.Fatal("login should fail") } }) @@ -137,12 +305,13 @@ func TestSessionLogIn(t *testing.T) { Password: "password", } - // Create HTTP request. - ctx.Request = httptest.NewRequest(http.MethodPost, "/api/v1/session", form.AsReader(frm)) - ctx.Request.RemoteAddr = "1.2.3.4" + // Create test request context. + c, _ := gin.CreateTestContext(rec) + c.Request = httptest.NewRequest(http.MethodPost, "/api/v1/session", form.AsReader(frm)) + c.Request.RemoteAddr = "1.2.3.4" // Try to log in. - if err := m.LogIn(frm, ctx); err == nil { + if err := m.LogIn(frm, c); err == nil { t.Fatal("login should fail") } }) @@ -155,12 +324,13 @@ func TestSessionLogIn(t *testing.T) { ShareToken: "1jxf3jfn2k", } - // Create HTTP request. - ctx.Request = httptest.NewRequest(http.MethodPost, "/api/v1/session", form.AsReader(frm)) - ctx.Request.RemoteAddr = "1.2.3.4" + // Create test request context. + c, _ := gin.CreateTestContext(rec) + c.Request = httptest.NewRequest(http.MethodPost, "/api/v1/session", form.AsReader(frm)) + c.Request.RemoteAddr = "1.2.3.4" // Try to log in. - if err := m.LogIn(frm, ctx); err != nil { + if err := m.LogIn(frm, c); err != nil { t.Fatal(err) } }) @@ -174,12 +344,13 @@ func TestSessionLogIn(t *testing.T) { ShareToken: "1jxf3jfxxx", } - // Create HTTP request. - ctx.Request = httptest.NewRequest(http.MethodPost, "/api/v1/session", form.AsReader(frm)) - ctx.Request.RemoteAddr = "1.2.3.4" + // Create test request context. + c, _ := gin.CreateTestContext(rec) + c.Request = httptest.NewRequest(http.MethodPost, "/api/v1/session", form.AsReader(frm)) + c.Request.RemoteAddr = "1.2.3.4" // Try to log in. - if err := m.LogIn(frm, ctx); err == nil { + if err := m.LogIn(frm, c); err == nil { t.Fatal("login should fail") } }) @@ -193,12 +364,13 @@ func TestSessionLogIn(t *testing.T) { ShareToken: "1jxf3jfn2k", } - // Create HTTP request. - ctx.Request = httptest.NewRequest(http.MethodPost, "/api/v1/session", form.AsReader(frm)) - ctx.Request.RemoteAddr = "1.2.3.4" + // Create test request context. + c, _ := gin.CreateTestContext(rec) + c.Request = httptest.NewRequest(http.MethodPost, "/api/v1/session", form.AsReader(frm)) + c.Request.RemoteAddr = "1.2.3.4" // Try to log in. - if err := m.LogIn(frm, ctx); err != nil { + if err := m.LogIn(frm, c); err != nil { t.Fatal(err) } }) @@ -212,12 +384,13 @@ func TestSessionLogIn(t *testing.T) { ShareToken: "1jxf3jfxxx", } - // Create HTTP request. - ctx.Request = httptest.NewRequest(http.MethodPost, "/api/v1/session", form.AsReader(frm)) - ctx.Request.RemoteAddr = "1.2.3.4" + // Create test request context. + c, _ := gin.CreateTestContext(rec) + c.Request = httptest.NewRequest(http.MethodPost, "/api/v1/session", form.AsReader(frm)) + c.Request.RemoteAddr = "1.2.3.4" // Try to log in. - if err := m.LogIn(frm, ctx); err == nil { + if err := m.LogIn(frm, c); err == nil { t.Fatal("login should fail") } }) diff --git a/internal/entity/auth_session_test.go b/internal/entity/auth_session_test.go index db6660e26..99abccc38 100644 --- a/internal/entity/auth_session_test.go +++ b/internal/entity/auth_session_test.go @@ -1,6 +1,7 @@ package entity import ( + "net/http" "testing" "time" @@ -130,13 +131,19 @@ func TestDeleteClientSessions(t *testing.T) { func TestSessionStatusUnauthorized(t *testing.T) { m := SessionStatusUnauthorized() - assert.Equal(t, 401, m.Status) + assert.Equal(t, http.StatusUnauthorized, m.Status) assert.IsType(t, &Session{}, m) } func TestSessionStatusForbidden(t *testing.T) { m := SessionStatusForbidden() - assert.Equal(t, 403, m.Status) + assert.Equal(t, http.StatusForbidden, m.Status) + assert.IsType(t, &Session{}, m) +} + +func TestSessionStatusTooManyRequests(t *testing.T) { + m := SessionStatusTooManyRequests() + assert.Equal(t, http.StatusTooManyRequests, m.Status) assert.IsType(t, &Session{}, m) } diff --git a/internal/server/limiter/auth.go b/internal/server/limiter/auth.go new file mode 100644 index 000000000..2d9beb4f3 --- /dev/null +++ b/internal/server/limiter/auth.go @@ -0,0 +1,20 @@ +package limiter + +import ( + "time" + + "golang.org/x/time/rate" +) + +const ( + DefaultAuthInterval = time.Second * 15 // average authentication errors per second + DefaultAuthLimit = 100 // authentication error burst rate limit + DefaultLoginInterval = time.Minute // average failed logins per second + DefaultLoginLimit = 10 // failed logins burst rate limit +) + +// Auth limits the number of authentication errors from a single IP per time interval (every 15 seconds by default). +var Auth = NewLimit(rate.Every(DefaultAuthInterval), DefaultAuthLimit) + +// Login limits the number of failed login attempts from a single IP per time interval (one per minute by default). +var Login = NewLimit(rate.Every(DefaultLoginInterval), DefaultLoginLimit) diff --git a/internal/server/limiter/login_test.go b/internal/server/limiter/auth_test.go similarity index 100% rename from internal/server/limiter/login_test.go rename to internal/server/limiter/auth_test.go diff --git a/internal/server/limiter/login.go b/internal/server/limiter/login.go deleted file mode 100644 index dd195a3c2..000000000 --- a/internal/server/limiter/login.go +++ /dev/null @@ -1,13 +0,0 @@ -package limiter - -import ( - "time" - - "golang.org/x/time/rate" -) - -const DefaultLoginLimit = 10 -const DefaultLoginInterval = time.Minute - -// Login limits failed authentication requests (one per minute). -var Login = NewLimit(rate.Every(DefaultLoginInterval), DefaultLoginLimit) diff --git a/internal/server/limiter/middleware.go b/internal/server/limiter/middleware.go index 7c9588f86..9989e6932 100644 --- a/internal/server/limiter/middleware.go +++ b/internal/server/limiter/middleware.go @@ -7,9 +7,9 @@ import ( ) // Middleware registers the IP rate limiter middleware. -func Middleware(ip *Limit) gin.HandlerFunc { +func Middleware(limiter *Limit) gin.HandlerFunc { return func(c *gin.Context) { - if l := ip.IP(c.ClientIP()); !l.Allow() { + if l := limiter.IP(c.ClientIP()); !l.Allow() { c.AbortWithStatus(http.StatusTooManyRequests) return } diff --git a/pkg/authn/methods.go b/pkg/authn/methods.go index 95ed0dbcb..dc6f2e340 100644 --- a/pkg/authn/methods.go +++ b/pkg/authn/methods.go @@ -13,6 +13,7 @@ type MethodType string // Authentication methods. const ( MethodDefault MethodType = "default" + MethodSession MethodType = "session" MethodAccessToken MethodType = "access_token" MethodOAuth2 MethodType = "oauth2" MethodOIDC MethodType = "oidc" diff --git a/pkg/header/cache.go b/pkg/header/cache.go index eaf2d8965..49f10a18e 100644 --- a/pkg/header/cache.go +++ b/pkg/header/cache.go @@ -2,6 +2,6 @@ package header const ( CacheControl = "Cache-Control" - CacheControlNoCache = "no-cache" CacheControlNoStore = "no-store" + CacheControlNoCache = "no-cache" )