mirror of
https://github.com/photoprism/photoprism.git
synced 2025-12-12 00:34:13 +01:00
Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
@@ -403,13 +403,13 @@ export default class Session {
|
|||||||
// Use a static auth token in public mode, as no additional authentication is required.
|
// Use a static auth token in public mode, as no additional authentication is required.
|
||||||
this.setAuthToken(PublicAuthToken);
|
this.setAuthToken(PublicAuthToken);
|
||||||
this.setId(PublicSessionID);
|
this.setId(PublicSessionID);
|
||||||
return Api.get("session/" + this.getId()).then((resp) => {
|
return Api.get("session").then((resp) => {
|
||||||
this.setResp(resp);
|
this.setResp(resp);
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
});
|
});
|
||||||
} else if (this.isAuthenticated()) {
|
} else if (this.isAuthenticated()) {
|
||||||
// Check the auth token by fetching the client session data from the API.
|
// 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) => {
|
.then((resp) => {
|
||||||
this.setResp(resp);
|
this.setResp(resp);
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
@@ -452,7 +452,7 @@ export default class Session {
|
|||||||
|
|
||||||
logout(noRedirect) {
|
logout(noRedirect) {
|
||||||
if (this.isAuthenticated()) {
|
if (this.isAuthenticated()) {
|
||||||
return Api.delete("session/" + this.getId())
|
return Api.delete("session")
|
||||||
.then(() => {
|
.then(() => {
|
||||||
return this.onLogout(noRedirect);
|
return this.onLogout(noRedirect);
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -658,8 +658,8 @@ msgstr "Ontfout logs"
|
|||||||
|
|
||||||
#: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100
|
#: 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/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:59 src/options/admin.js:60 src/options/admin.js:74
|
||||||
#: src/options/admin.js:92 src/options/options.js:313
|
#: 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
|
#: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142
|
||||||
msgid "Default"
|
msgid "Default"
|
||||||
msgstr "Verstek"
|
msgstr "Verstek"
|
||||||
@@ -1343,7 +1343,7 @@ msgstr "Laaste sinkronisering"
|
|||||||
msgid "Latitude"
|
msgid "Latitude"
|
||||||
msgstr "Breedtegraad"
|
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"
|
msgid "LDAP/AD"
|
||||||
msgstr "LDAP/AD"
|
msgstr "LDAP/AD"
|
||||||
|
|
||||||
@@ -1408,8 +1408,8 @@ msgstr "Leef"
|
|||||||
msgid "Live Photos"
|
msgid "Live Photos"
|
||||||
msgstr "Regstreekse Foto's"
|
msgstr "Regstreekse Foto's"
|
||||||
|
|
||||||
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77
|
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78
|
||||||
#: src/options/admin.js:96
|
#: src/options/admin.js:97
|
||||||
msgid "Local"
|
msgid "Local"
|
||||||
msgstr "Plaaslik"
|
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."
|
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."
|
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
|
#: src/options/options.js:293 src/options/options.js:389
|
||||||
msgid "None"
|
msgid "None"
|
||||||
msgstr "Geen"
|
msgstr "Geen"
|
||||||
@@ -2157,7 +2157,7 @@ msgstr "Diens-URL"
|
|||||||
msgid "Services"
|
msgid "Services"
|
||||||
msgstr "Dienste"
|
msgstr "Dienste"
|
||||||
|
|
||||||
#: src/model/session.js:98
|
#: src/model/session.js:98 src/options/admin.js:62
|
||||||
msgid "Session"
|
msgid "Session"
|
||||||
msgstr "Sessie"
|
msgstr "Sessie"
|
||||||
|
|
||||||
|
|||||||
@@ -661,8 +661,8 @@ msgstr "سجلات التصحيح"
|
|||||||
|
|
||||||
#: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100
|
#: 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/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:59 src/options/admin.js:60 src/options/admin.js:74
|
||||||
#: src/options/admin.js:92 src/options/options.js:313
|
#: 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
|
#: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142
|
||||||
msgid "Default"
|
msgid "Default"
|
||||||
msgstr "تقصير"
|
msgstr "تقصير"
|
||||||
@@ -1346,7 +1346,7 @@ msgstr "آخر مزامنة"
|
|||||||
msgid "Latitude"
|
msgid "Latitude"
|
||||||
msgstr "خط العرض"
|
msgstr "خط العرض"
|
||||||
|
|
||||||
#: src/options/admin.js:49 src/options/admin.js:81
|
#: src/options/admin.js:49 src/options/admin.js:82
|
||||||
msgid "LDAP/AD"
|
msgid "LDAP/AD"
|
||||||
msgstr "LDAP / AD"
|
msgstr "LDAP / AD"
|
||||||
|
|
||||||
@@ -1411,8 +1411,8 @@ msgstr "يعيش"
|
|||||||
msgid "Live Photos"
|
msgid "Live Photos"
|
||||||
msgstr "Live Photos"
|
msgstr "Live Photos"
|
||||||
|
|
||||||
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77
|
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78
|
||||||
#: src/options/admin.js:96
|
#: src/options/admin.js:97
|
||||||
msgid "Local"
|
msgid "Local"
|
||||||
msgstr "محلي"
|
msgstr "محلي"
|
||||||
|
|
||||||
@@ -1658,7 +1658,7 @@ msgstr "لا تحذيرات أو خطأ يحتوي على هذه الكلمة ا
|
|||||||
msgid "Non-photographic and low-quality images require a review before they appear in search results."
|
msgid "Non-photographic and low-quality images require a review before they appear in search results."
|
||||||
msgstr "تتطلب الصور غير الفوتوغرافية وذات الجودة المنخفضة المراجعة قبل ظهورها في نتائج البحث."
|
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
|
#: src/options/options.js:293 src/options/options.js:389
|
||||||
msgid "None"
|
msgid "None"
|
||||||
msgstr "لا أحد"
|
msgstr "لا أحد"
|
||||||
@@ -2160,7 +2160,7 @@ msgstr "URL الخدمة"
|
|||||||
msgid "Services"
|
msgid "Services"
|
||||||
msgstr "خدمات"
|
msgstr "خدمات"
|
||||||
|
|
||||||
#: src/model/session.js:98
|
#: src/model/session.js:98 src/options/admin.js:62
|
||||||
msgid "Session"
|
msgid "Session"
|
||||||
msgstr "حصة"
|
msgstr "حصة"
|
||||||
|
|
||||||
|
|||||||
@@ -658,8 +658,8 @@ msgstr "Журналы адладкі"
|
|||||||
|
|
||||||
#: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100
|
#: 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/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:59 src/options/admin.js:60 src/options/admin.js:74
|
||||||
#: src/options/admin.js:92 src/options/options.js:313
|
#: 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
|
#: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142
|
||||||
msgid "Default"
|
msgid "Default"
|
||||||
msgstr "Па змаўчанні"
|
msgstr "Па змаўчанні"
|
||||||
@@ -1343,7 +1343,7 @@ msgstr "Апошняя сінхранізацыя"
|
|||||||
msgid "Latitude"
|
msgid "Latitude"
|
||||||
msgstr "Шырата"
|
msgstr "Шырата"
|
||||||
|
|
||||||
#: src/options/admin.js:49 src/options/admin.js:81
|
#: src/options/admin.js:49 src/options/admin.js:82
|
||||||
msgid "LDAP/AD"
|
msgid "LDAP/AD"
|
||||||
msgstr "LDAP/AD"
|
msgstr "LDAP/AD"
|
||||||
|
|
||||||
@@ -1408,8 +1408,8 @@ msgstr "жыць"
|
|||||||
msgid "Live Photos"
|
msgid "Live Photos"
|
||||||
msgstr "Жывыя фатаграфіі"
|
msgstr "Жывыя фатаграфіі"
|
||||||
|
|
||||||
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77
|
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78
|
||||||
#: src/options/admin.js:96
|
#: src/options/admin.js:97
|
||||||
msgid "Local"
|
msgid "Local"
|
||||||
msgstr "Мясцовы"
|
msgstr "Мясцовы"
|
||||||
|
|
||||||
@@ -1655,7 +1655,7 @@ msgstr "Няма папярэджанняў або памылак з гэтым
|
|||||||
msgid "Non-photographic and low-quality images require a review before they appear in search results."
|
msgid "Non-photographic and low-quality images require a review before they appear in search results."
|
||||||
msgstr "Нефатаграфічныя і нізкаякасныя выявы патрабуюць праверкі, перш чым яны з'явяцца ў выніках пошуку."
|
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
|
#: src/options/options.js:293 src/options/options.js:389
|
||||||
msgid "None"
|
msgid "None"
|
||||||
msgstr "Няма"
|
msgstr "Няма"
|
||||||
@@ -2157,7 +2157,7 @@ msgstr "URL службы"
|
|||||||
msgid "Services"
|
msgid "Services"
|
||||||
msgstr "Паслугі"
|
msgstr "Паслугі"
|
||||||
|
|
||||||
#: src/model/session.js:98
|
#: src/model/session.js:98 src/options/admin.js:62
|
||||||
msgid "Session"
|
msgid "Session"
|
||||||
msgstr "сесія"
|
msgstr "сесія"
|
||||||
|
|
||||||
|
|||||||
@@ -661,8 +661,8 @@ msgstr "Протоколи за отработване"
|
|||||||
|
|
||||||
#: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100
|
#: 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/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:59 src/options/admin.js:60 src/options/admin.js:74
|
||||||
#: src/options/admin.js:92 src/options/options.js:313
|
#: 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
|
#: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142
|
||||||
msgid "Default"
|
msgid "Default"
|
||||||
msgstr "По подразбиране"
|
msgstr "По подразбиране"
|
||||||
@@ -1346,7 +1346,7 @@ msgstr "Синхронизиране"
|
|||||||
msgid "Latitude"
|
msgid "Latitude"
|
||||||
msgstr "Географска ширина"
|
msgstr "Географска ширина"
|
||||||
|
|
||||||
#: src/options/admin.js:49 src/options/admin.js:81
|
#: src/options/admin.js:49 src/options/admin.js:82
|
||||||
msgid "LDAP/AD"
|
msgid "LDAP/AD"
|
||||||
msgstr "LDAP/AD"
|
msgstr "LDAP/AD"
|
||||||
|
|
||||||
@@ -1411,8 +1411,8 @@ msgstr "На живо"
|
|||||||
msgid "Live Photos"
|
msgid "Live Photos"
|
||||||
msgstr "Снимки"
|
msgstr "Снимки"
|
||||||
|
|
||||||
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77
|
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78
|
||||||
#: src/options/admin.js:96
|
#: src/options/admin.js:97
|
||||||
msgid "Local"
|
msgid "Local"
|
||||||
msgstr "Местни"
|
msgstr "Местни"
|
||||||
|
|
||||||
@@ -1658,7 +1658,7 @@ msgstr "Няма предупреждения или грешки, съдърж
|
|||||||
msgid "Non-photographic and low-quality images require a review before they appear in search results."
|
msgid "Non-photographic and low-quality images require a review before they appear in search results."
|
||||||
msgstr "Нефотографските изображения и изображенията с ниско качество изискват преглед, преди да се появят в резултатите от търсенето."
|
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
|
#: src/options/options.js:293 src/options/options.js:389
|
||||||
msgid "None"
|
msgid "None"
|
||||||
msgstr "Няма"
|
msgstr "Няма"
|
||||||
@@ -2160,7 +2160,7 @@ msgstr "URL адрес на услугата"
|
|||||||
msgid "Services"
|
msgid "Services"
|
||||||
msgstr "URL адрес на услугата"
|
msgstr "URL адрес на услугата"
|
||||||
|
|
||||||
#: src/model/session.js:98
|
#: src/model/session.js:98 src/options/admin.js:62
|
||||||
msgid "Session"
|
msgid "Session"
|
||||||
msgstr "Сесия"
|
msgstr "Сесия"
|
||||||
|
|
||||||
|
|||||||
@@ -661,8 +661,8 @@ msgstr "Registres de depuració"
|
|||||||
|
|
||||||
#: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100
|
#: 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/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:59 src/options/admin.js:60 src/options/admin.js:74
|
||||||
#: src/options/admin.js:92 src/options/options.js:313
|
#: 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
|
#: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142
|
||||||
msgid "Default"
|
msgid "Default"
|
||||||
msgstr "Per defecte"
|
msgstr "Per defecte"
|
||||||
@@ -1346,7 +1346,7 @@ msgstr "Última sincronització"
|
|||||||
msgid "Latitude"
|
msgid "Latitude"
|
||||||
msgstr "Latitud"
|
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"
|
msgid "LDAP/AD"
|
||||||
msgstr "LDAP/AD"
|
msgstr "LDAP/AD"
|
||||||
|
|
||||||
@@ -1411,8 +1411,8 @@ msgstr "En viu"
|
|||||||
msgid "Live Photos"
|
msgid "Live Photos"
|
||||||
msgstr "Fotos en directe"
|
msgstr "Fotos en directe"
|
||||||
|
|
||||||
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77
|
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78
|
||||||
#: src/options/admin.js:96
|
#: src/options/admin.js:97
|
||||||
msgid "Local"
|
msgid "Local"
|
||||||
msgstr "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."
|
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."
|
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
|
#: src/options/options.js:293 src/options/options.js:389
|
||||||
msgid "None"
|
msgid "None"
|
||||||
msgstr "Cap"
|
msgstr "Cap"
|
||||||
@@ -2160,7 +2160,7 @@ msgstr "URL del servei"
|
|||||||
msgid "Services"
|
msgid "Services"
|
||||||
msgstr "Serveis"
|
msgstr "Serveis"
|
||||||
|
|
||||||
#: src/model/session.js:98
|
#: src/model/session.js:98 src/options/admin.js:62
|
||||||
msgid "Session"
|
msgid "Session"
|
||||||
msgstr "Sessió"
|
msgstr "Sessió"
|
||||||
|
|
||||||
|
|||||||
@@ -661,8 +661,8 @@ msgstr "Protokoly ladění"
|
|||||||
|
|
||||||
#: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100
|
#: 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/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:59 src/options/admin.js:60 src/options/admin.js:74
|
||||||
#: src/options/admin.js:92 src/options/options.js:313
|
#: 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
|
#: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142
|
||||||
msgid "Default"
|
msgid "Default"
|
||||||
msgstr "Výchozí"
|
msgstr "Výchozí"
|
||||||
@@ -1346,7 +1346,7 @@ msgstr "Poslední synchronizace"
|
|||||||
msgid "Latitude"
|
msgid "Latitude"
|
||||||
msgstr "Zeměpisná šířka"
|
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"
|
msgid "LDAP/AD"
|
||||||
msgstr "LDAP/AD"
|
msgstr "LDAP/AD"
|
||||||
|
|
||||||
@@ -1411,8 +1411,8 @@ msgstr "Živé"
|
|||||||
msgid "Live Photos"
|
msgid "Live Photos"
|
||||||
msgstr "Živé fotografie"
|
msgstr "Živé fotografie"
|
||||||
|
|
||||||
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77
|
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78
|
||||||
#: src/options/admin.js:96
|
#: src/options/admin.js:97
|
||||||
msgid "Local"
|
msgid "Local"
|
||||||
msgstr "Místní"
|
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."
|
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í."
|
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
|
#: src/options/options.js:293 src/options/options.js:389
|
||||||
msgid "None"
|
msgid "None"
|
||||||
msgstr "Žádné"
|
msgstr "Žádné"
|
||||||
@@ -2160,7 +2160,7 @@ msgstr "URL služby"
|
|||||||
msgid "Services"
|
msgid "Services"
|
||||||
msgstr "Služby"
|
msgstr "Služby"
|
||||||
|
|
||||||
#: src/model/session.js:98
|
#: src/model/session.js:98 src/options/admin.js:62
|
||||||
msgid "Session"
|
msgid "Session"
|
||||||
msgstr "Relace"
|
msgstr "Relace"
|
||||||
|
|
||||||
|
|||||||
@@ -661,8 +661,8 @@ msgstr "Fejlfindingslog"
|
|||||||
|
|
||||||
#: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100
|
#: 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/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:59 src/options/admin.js:60 src/options/admin.js:74
|
||||||
#: src/options/admin.js:92 src/options/options.js:313
|
#: 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
|
#: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142
|
||||||
msgid "Default"
|
msgid "Default"
|
||||||
msgstr "Standard"
|
msgstr "Standard"
|
||||||
@@ -1346,7 +1346,7 @@ msgstr "Seneste synkronisering"
|
|||||||
msgid "Latitude"
|
msgid "Latitude"
|
||||||
msgstr "Breddegrad"
|
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"
|
msgid "LDAP/AD"
|
||||||
msgstr "LDAP/AD"
|
msgstr "LDAP/AD"
|
||||||
|
|
||||||
@@ -1411,8 +1411,8 @@ msgstr "Direkte"
|
|||||||
msgid "Live Photos"
|
msgid "Live Photos"
|
||||||
msgstr "Live-fotos"
|
msgstr "Live-fotos"
|
||||||
|
|
||||||
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77
|
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78
|
||||||
#: src/options/admin.js:96
|
#: src/options/admin.js:97
|
||||||
msgid "Local"
|
msgid "Local"
|
||||||
msgstr "Lokal"
|
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."
|
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."
|
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
|
#: src/options/options.js:293 src/options/options.js:389
|
||||||
msgid "None"
|
msgid "None"
|
||||||
msgstr "Ingen"
|
msgstr "Ingen"
|
||||||
@@ -2160,7 +2160,7 @@ msgstr "Service-URL"
|
|||||||
msgid "Services"
|
msgid "Services"
|
||||||
msgstr "Tjenester"
|
msgstr "Tjenester"
|
||||||
|
|
||||||
#: src/model/session.js:98
|
#: src/model/session.js:98 src/options/admin.js:62
|
||||||
msgid "Session"
|
msgid "Session"
|
||||||
msgstr "Session"
|
msgstr "Session"
|
||||||
|
|
||||||
|
|||||||
@@ -661,8 +661,8 @@ msgstr "Debug Logs"
|
|||||||
|
|
||||||
#: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100
|
#: 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/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:59 src/options/admin.js:60 src/options/admin.js:74
|
||||||
#: src/options/admin.js:92 src/options/options.js:313
|
#: 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
|
#: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142
|
||||||
msgid "Default"
|
msgid "Default"
|
||||||
msgstr "Standard"
|
msgstr "Standard"
|
||||||
@@ -1346,7 +1346,7 @@ msgstr "Letzte Synchronisation"
|
|||||||
msgid "Latitude"
|
msgid "Latitude"
|
||||||
msgstr "Breitengrad"
|
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"
|
msgid "LDAP/AD"
|
||||||
msgstr "LDAP/AD"
|
msgstr "LDAP/AD"
|
||||||
|
|
||||||
@@ -1411,8 +1411,8 @@ msgstr "Live"
|
|||||||
msgid "Live Photos"
|
msgid "Live Photos"
|
||||||
msgstr "Live Photos"
|
msgstr "Live Photos"
|
||||||
|
|
||||||
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77
|
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78
|
||||||
#: src/options/admin.js:96
|
#: src/options/admin.js:97
|
||||||
msgid "Local"
|
msgid "Local"
|
||||||
msgstr "Lokal"
|
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."
|
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."
|
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
|
#: src/options/options.js:293 src/options/options.js:389
|
||||||
msgid "None"
|
msgid "None"
|
||||||
msgstr "Keine"
|
msgstr "Keine"
|
||||||
@@ -2160,7 +2160,7 @@ msgstr "Dienst-URL"
|
|||||||
msgid "Services"
|
msgid "Services"
|
||||||
msgstr "Dienste"
|
msgstr "Dienste"
|
||||||
|
|
||||||
#: src/model/session.js:98
|
#: src/model/session.js:98 src/options/admin.js:62
|
||||||
msgid "Session"
|
msgid "Session"
|
||||||
msgstr "Session"
|
msgstr "Session"
|
||||||
|
|
||||||
|
|||||||
@@ -658,8 +658,8 @@ msgstr "Αρχεία καταγραφής σφαλμάτων"
|
|||||||
|
|
||||||
#: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100
|
#: 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/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:59 src/options/admin.js:60 src/options/admin.js:74
|
||||||
#: src/options/admin.js:92 src/options/options.js:313
|
#: 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
|
#: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142
|
||||||
msgid "Default"
|
msgid "Default"
|
||||||
msgstr "Προεπιλογή"
|
msgstr "Προεπιλογή"
|
||||||
@@ -1343,7 +1343,7 @@ msgstr "Τελευταίος συγχρονισμός"
|
|||||||
msgid "Latitude"
|
msgid "Latitude"
|
||||||
msgstr "Γεωγραφικό πλάτος"
|
msgstr "Γεωγραφικό πλάτος"
|
||||||
|
|
||||||
#: src/options/admin.js:49 src/options/admin.js:81
|
#: src/options/admin.js:49 src/options/admin.js:82
|
||||||
msgid "LDAP/AD"
|
msgid "LDAP/AD"
|
||||||
msgstr "LDAP/AD"
|
msgstr "LDAP/AD"
|
||||||
|
|
||||||
@@ -1408,8 +1408,8 @@ msgstr "Ζωντανό"
|
|||||||
msgid "Live Photos"
|
msgid "Live Photos"
|
||||||
msgstr "Φωτογραφίες"
|
msgstr "Φωτογραφίες"
|
||||||
|
|
||||||
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77
|
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78
|
||||||
#: src/options/admin.js:96
|
#: src/options/admin.js:97
|
||||||
msgid "Local"
|
msgid "Local"
|
||||||
msgstr "Τοπικό"
|
msgstr "Τοπικό"
|
||||||
|
|
||||||
@@ -1655,7 +1655,7 @@ msgstr "Δεν υπάρχουν προειδοποιήσεις ή σφάλματ
|
|||||||
msgid "Non-photographic and low-quality images require a review before they appear in search results."
|
msgid "Non-photographic and low-quality images require a review before they appear in search results."
|
||||||
msgstr "Οι μη φωτογραφικές εικόνες και οι εικόνες χαμηλής ποιότητας απαιτούν επανεξέταση προτού εμφανιστούν στα αποτελέσματα αναζήτησης."
|
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
|
#: src/options/options.js:293 src/options/options.js:389
|
||||||
msgid "None"
|
msgid "None"
|
||||||
msgstr "Κανένα"
|
msgstr "Κανένα"
|
||||||
@@ -2157,7 +2157,7 @@ msgstr "URL υπηρεσίας"
|
|||||||
msgid "Services"
|
msgid "Services"
|
||||||
msgstr "URL υπηρεσίας"
|
msgstr "URL υπηρεσίας"
|
||||||
|
|
||||||
#: src/model/session.js:98
|
#: src/model/session.js:98 src/options/admin.js:62
|
||||||
msgid "Session"
|
msgid "Session"
|
||||||
msgstr "Σύνοδος"
|
msgstr "Σύνοδος"
|
||||||
|
|
||||||
|
|||||||
@@ -660,8 +660,8 @@ msgstr ""
|
|||||||
|
|
||||||
#: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100
|
#: 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/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:59 src/options/admin.js:60 src/options/admin.js:74
|
||||||
#: src/options/admin.js:92 src/options/options.js:313
|
#: 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
|
#: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142
|
||||||
msgid "Default"
|
msgid "Default"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1345,7 +1345,7 @@ msgstr ""
|
|||||||
msgid "Latitude"
|
msgid "Latitude"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/options/admin.js:49 src/options/admin.js:81
|
#: src/options/admin.js:49 src/options/admin.js:82
|
||||||
msgid "LDAP/AD"
|
msgid "LDAP/AD"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1410,8 +1410,8 @@ msgstr ""
|
|||||||
msgid "Live Photos"
|
msgid "Live Photos"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77
|
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78
|
||||||
#: src/options/admin.js:96
|
#: src/options/admin.js:97
|
||||||
msgid "Local"
|
msgid "Local"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1657,7 +1657,7 @@ msgstr ""
|
|||||||
msgid "Non-photographic and low-quality images require a review before they appear in search results."
|
msgid "Non-photographic and low-quality images require a review before they appear in search results."
|
||||||
msgstr ""
|
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
|
#: src/options/options.js:293 src/options/options.js:389
|
||||||
msgid "None"
|
msgid "None"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -2159,7 +2159,7 @@ msgstr ""
|
|||||||
msgid "Services"
|
msgid "Services"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/model/session.js:98
|
#: src/model/session.js:98 src/options/admin.js:62
|
||||||
msgid "Session"
|
msgid "Session"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
@@ -661,8 +661,8 @@ msgstr "Registros de depuración"
|
|||||||
|
|
||||||
#: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100
|
#: 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/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:59 src/options/admin.js:60 src/options/admin.js:74
|
||||||
#: src/options/admin.js:92 src/options/options.js:313
|
#: 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
|
#: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142
|
||||||
msgid "Default"
|
msgid "Default"
|
||||||
msgstr "Por defecto"
|
msgstr "Por defecto"
|
||||||
@@ -1346,7 +1346,7 @@ msgstr "Última sincronización"
|
|||||||
msgid "Latitude"
|
msgid "Latitude"
|
||||||
msgstr "Latitud"
|
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"
|
msgid "LDAP/AD"
|
||||||
msgstr "LDAP/AD"
|
msgstr "LDAP/AD"
|
||||||
|
|
||||||
@@ -1412,8 +1412,8 @@ msgstr "En vivo"
|
|||||||
msgid "Live Photos"
|
msgid "Live Photos"
|
||||||
msgstr "Fotos en vivo"
|
msgstr "Fotos en vivo"
|
||||||
|
|
||||||
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77
|
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78
|
||||||
#: src/options/admin.js:96
|
#: src/options/admin.js:97
|
||||||
msgid "Local"
|
msgid "Local"
|
||||||
msgstr "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."
|
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."
|
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
|
#: src/options/options.js:293 src/options/options.js:389
|
||||||
msgid "None"
|
msgid "None"
|
||||||
msgstr "Ninguno"
|
msgstr "Ninguno"
|
||||||
@@ -2161,7 +2161,7 @@ msgstr "URL del servicio"
|
|||||||
msgid "Services"
|
msgid "Services"
|
||||||
msgstr "Servicios"
|
msgstr "Servicios"
|
||||||
|
|
||||||
#: src/model/session.js:98
|
#: src/model/session.js:98 src/options/admin.js:62
|
||||||
msgid "Session"
|
msgid "Session"
|
||||||
msgstr "Sesión"
|
msgstr "Sesión"
|
||||||
|
|
||||||
|
|||||||
@@ -658,8 +658,8 @@ msgstr "Tõrkeotsingu logid"
|
|||||||
|
|
||||||
#: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100
|
#: 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/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:59 src/options/admin.js:60 src/options/admin.js:74
|
||||||
#: src/options/admin.js:92 src/options/options.js:313
|
#: 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
|
#: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142
|
||||||
msgid "Default"
|
msgid "Default"
|
||||||
msgstr "Vaikimisi"
|
msgstr "Vaikimisi"
|
||||||
@@ -1343,7 +1343,7 @@ msgstr "Viimane sünkroonimine"
|
|||||||
msgid "Latitude"
|
msgid "Latitude"
|
||||||
msgstr "Laiuskraad"
|
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"
|
msgid "LDAP/AD"
|
||||||
msgstr "LDAP/AD"
|
msgstr "LDAP/AD"
|
||||||
|
|
||||||
@@ -1408,8 +1408,8 @@ msgstr "Live"
|
|||||||
msgid "Live Photos"
|
msgid "Live Photos"
|
||||||
msgstr "Liikuvad fotod"
|
msgstr "Liikuvad fotod"
|
||||||
|
|
||||||
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77
|
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78
|
||||||
#: src/options/admin.js:96
|
#: src/options/admin.js:97
|
||||||
msgid "Local"
|
msgid "Local"
|
||||||
msgstr "Kohalik"
|
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."
|
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."
|
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
|
#: src/options/options.js:293 src/options/options.js:389
|
||||||
msgid "None"
|
msgid "None"
|
||||||
msgstr "Puudub"
|
msgstr "Puudub"
|
||||||
@@ -2157,7 +2157,7 @@ msgstr "Teenuse URL"
|
|||||||
msgid "Services"
|
msgid "Services"
|
||||||
msgstr "Teenused"
|
msgstr "Teenused"
|
||||||
|
|
||||||
#: src/model/session.js:98
|
#: src/model/session.js:98 src/options/admin.js:62
|
||||||
msgid "Session"
|
msgid "Session"
|
||||||
msgstr "Sessioon"
|
msgstr "Sessioon"
|
||||||
|
|
||||||
|
|||||||
@@ -658,8 +658,8 @@ msgstr "Arazte-erregistroak"
|
|||||||
|
|
||||||
#: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100
|
#: 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/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:59 src/options/admin.js:60 src/options/admin.js:74
|
||||||
#: src/options/admin.js:92 src/options/options.js:313
|
#: 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
|
#: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142
|
||||||
msgid "Default"
|
msgid "Default"
|
||||||
msgstr "Lehenetsia"
|
msgstr "Lehenetsia"
|
||||||
@@ -1343,7 +1343,7 @@ msgstr "Azken sinkronizazioa"
|
|||||||
msgid "Latitude"
|
msgid "Latitude"
|
||||||
msgstr "Latitudea"
|
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"
|
msgid "LDAP/AD"
|
||||||
msgstr "LDAP/AD"
|
msgstr "LDAP/AD"
|
||||||
|
|
||||||
@@ -1408,8 +1408,8 @@ msgstr "Zuzenean"
|
|||||||
msgid "Live Photos"
|
msgid "Live Photos"
|
||||||
msgstr "Zuzeneko Argazkiak"
|
msgstr "Zuzeneko Argazkiak"
|
||||||
|
|
||||||
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77
|
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78
|
||||||
#: src/options/admin.js:96
|
#: src/options/admin.js:97
|
||||||
msgid "Local"
|
msgid "Local"
|
||||||
msgstr "Tokikoa"
|
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."
|
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."
|
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
|
#: src/options/options.js:293 src/options/options.js:389
|
||||||
msgid "None"
|
msgid "None"
|
||||||
msgstr "Bat ere ez"
|
msgstr "Bat ere ez"
|
||||||
@@ -2157,7 +2157,7 @@ msgstr "Zerbitzuaren URLa"
|
|||||||
msgid "Services"
|
msgid "Services"
|
||||||
msgstr "Zerbitzuak"
|
msgstr "Zerbitzuak"
|
||||||
|
|
||||||
#: src/model/session.js:98
|
#: src/model/session.js:98 src/options/admin.js:62
|
||||||
msgid "Session"
|
msgid "Session"
|
||||||
msgstr "Saioa"
|
msgstr "Saioa"
|
||||||
|
|
||||||
|
|||||||
@@ -661,8 +661,8 @@ msgstr "گزارشهای اشکال زدایی"
|
|||||||
|
|
||||||
#: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100
|
#: 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/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:59 src/options/admin.js:60 src/options/admin.js:74
|
||||||
#: src/options/admin.js:92 src/options/options.js:313
|
#: 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
|
#: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142
|
||||||
msgid "Default"
|
msgid "Default"
|
||||||
msgstr "پیشفرض"
|
msgstr "پیشفرض"
|
||||||
@@ -1346,7 +1346,7 @@ msgstr "آخرین همگام سازی"
|
|||||||
msgid "Latitude"
|
msgid "Latitude"
|
||||||
msgstr "عرض جغرافیایی"
|
msgstr "عرض جغرافیایی"
|
||||||
|
|
||||||
#: src/options/admin.js:49 src/options/admin.js:81
|
#: src/options/admin.js:49 src/options/admin.js:82
|
||||||
msgid "LDAP/AD"
|
msgid "LDAP/AD"
|
||||||
msgstr "LDAP/AD"
|
msgstr "LDAP/AD"
|
||||||
|
|
||||||
@@ -1411,8 +1411,8 @@ msgstr "زنده"
|
|||||||
msgid "Live Photos"
|
msgid "Live Photos"
|
||||||
msgstr "تصاویر"
|
msgstr "تصاویر"
|
||||||
|
|
||||||
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77
|
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78
|
||||||
#: src/options/admin.js:96
|
#: src/options/admin.js:97
|
||||||
msgid "Local"
|
msgid "Local"
|
||||||
msgstr "محلی"
|
msgstr "محلی"
|
||||||
|
|
||||||
@@ -1658,7 +1658,7 @@ msgstr "هیچ هشدار یا خطایی حاوی این کلمه کلیدی ن
|
|||||||
msgid "Non-photographic and low-quality images require a review before they appear in search results."
|
msgid "Non-photographic and low-quality images require a review before they appear in search results."
|
||||||
msgstr "تصاویر غیرعکاسی و با کیفیت پایین قبل از اینکه در نتایج جستجو ظاهر شوند نیاز به بررسی دارند."
|
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
|
#: src/options/options.js:293 src/options/options.js:389
|
||||||
msgid "None"
|
msgid "None"
|
||||||
msgstr "هیچ یک"
|
msgstr "هیچ یک"
|
||||||
@@ -2160,7 +2160,7 @@ msgstr "URL سرویس"
|
|||||||
msgid "Services"
|
msgid "Services"
|
||||||
msgstr "URL سرویس"
|
msgstr "URL سرویس"
|
||||||
|
|
||||||
#: src/model/session.js:98
|
#: src/model/session.js:98 src/options/admin.js:62
|
||||||
msgid "Session"
|
msgid "Session"
|
||||||
msgstr "جلسه"
|
msgstr "جلسه"
|
||||||
|
|
||||||
|
|||||||
@@ -658,8 +658,8 @@ msgstr "Vianmäärityslokit"
|
|||||||
|
|
||||||
#: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100
|
#: 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/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:59 src/options/admin.js:60 src/options/admin.js:74
|
||||||
#: src/options/admin.js:92 src/options/options.js:313
|
#: 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
|
#: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142
|
||||||
msgid "Default"
|
msgid "Default"
|
||||||
msgstr "Oletus"
|
msgstr "Oletus"
|
||||||
@@ -1343,7 +1343,7 @@ msgstr "Viimeisin synkronointi"
|
|||||||
msgid "Latitude"
|
msgid "Latitude"
|
||||||
msgstr "Leveysaste"
|
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"
|
msgid "LDAP/AD"
|
||||||
msgstr "LDAP/AD"
|
msgstr "LDAP/AD"
|
||||||
|
|
||||||
@@ -1408,8 +1408,8 @@ msgstr "Live Photo -kuva"
|
|||||||
msgid "Live Photos"
|
msgid "Live Photos"
|
||||||
msgstr "Kuvat"
|
msgstr "Kuvat"
|
||||||
|
|
||||||
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77
|
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78
|
||||||
#: src/options/admin.js:96
|
#: src/options/admin.js:97
|
||||||
msgid "Local"
|
msgid "Local"
|
||||||
msgstr "Paikallinen"
|
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."
|
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."
|
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
|
#: src/options/options.js:293 src/options/options.js:389
|
||||||
msgid "None"
|
msgid "None"
|
||||||
msgstr "Ei mitään"
|
msgstr "Ei mitään"
|
||||||
@@ -2157,7 +2157,7 @@ msgstr "Palvelun URL-osoite"
|
|||||||
msgid "Services"
|
msgid "Services"
|
||||||
msgstr "Palvelun URL-osoite"
|
msgstr "Palvelun URL-osoite"
|
||||||
|
|
||||||
#: src/model/session.js:98
|
#: src/model/session.js:98 src/options/admin.js:62
|
||||||
msgid "Session"
|
msgid "Session"
|
||||||
msgstr "Istunto"
|
msgstr "Istunto"
|
||||||
|
|
||||||
|
|||||||
@@ -661,8 +661,8 @@ msgstr "Journaux de débogage"
|
|||||||
|
|
||||||
#: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100
|
#: 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/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:59 src/options/admin.js:60 src/options/admin.js:74
|
||||||
#: src/options/admin.js:92 src/options/options.js:313
|
#: 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
|
#: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142
|
||||||
msgid "Default"
|
msgid "Default"
|
||||||
msgstr "Valeur par défaut"
|
msgstr "Valeur par défaut"
|
||||||
@@ -1346,7 +1346,7 @@ msgstr "Dernière synchro"
|
|||||||
msgid "Latitude"
|
msgid "Latitude"
|
||||||
msgstr "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"
|
msgid "LDAP/AD"
|
||||||
msgstr "LDAP/AD"
|
msgstr "LDAP/AD"
|
||||||
|
|
||||||
@@ -1411,8 +1411,8 @@ msgstr "Live"
|
|||||||
msgid "Live Photos"
|
msgid "Live Photos"
|
||||||
msgstr "Photos en direct"
|
msgstr "Photos en direct"
|
||||||
|
|
||||||
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77
|
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78
|
||||||
#: src/options/admin.js:96
|
#: src/options/admin.js:97
|
||||||
msgid "Local"
|
msgid "Local"
|
||||||
msgstr "Locale"
|
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."
|
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."
|
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
|
#: src/options/options.js:293 src/options/options.js:389
|
||||||
msgid "None"
|
msgid "None"
|
||||||
msgstr "Aucun"
|
msgstr "Aucun"
|
||||||
@@ -2160,7 +2160,7 @@ msgstr "URL du service"
|
|||||||
msgid "Services"
|
msgid "Services"
|
||||||
msgstr "Services"
|
msgstr "Services"
|
||||||
|
|
||||||
#: src/model/session.js:98
|
#: src/model/session.js:98 src/options/admin.js:62
|
||||||
msgid "Session"
|
msgid "Session"
|
||||||
msgstr "Session"
|
msgstr "Session"
|
||||||
|
|
||||||
|
|||||||
@@ -661,8 +661,8 @@ msgstr "Debug Logs"
|
|||||||
|
|
||||||
#: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100
|
#: 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/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:59 src/options/admin.js:60 src/options/admin.js:74
|
||||||
#: src/options/admin.js:92 src/options/options.js:313
|
#: 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
|
#: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142
|
||||||
msgid "Default"
|
msgid "Default"
|
||||||
msgstr "ברירת מחדל"
|
msgstr "ברירת מחדל"
|
||||||
@@ -1346,7 +1346,7 @@ msgstr "סנכרון אחרון"
|
|||||||
msgid "Latitude"
|
msgid "Latitude"
|
||||||
msgstr "קו רוחב"
|
msgstr "קו רוחב"
|
||||||
|
|
||||||
#: src/options/admin.js:49 src/options/admin.js:81
|
#: src/options/admin.js:49 src/options/admin.js:82
|
||||||
msgid "LDAP/AD"
|
msgid "LDAP/AD"
|
||||||
msgstr "LDAP/AD"
|
msgstr "LDAP/AD"
|
||||||
|
|
||||||
@@ -1411,8 +1411,8 @@ msgstr "חי"
|
|||||||
msgid "Live Photos"
|
msgid "Live Photos"
|
||||||
msgstr "תמונות חיות"
|
msgstr "תמונות חיות"
|
||||||
|
|
||||||
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77
|
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78
|
||||||
#: src/options/admin.js:96
|
#: src/options/admin.js:97
|
||||||
msgid "Local"
|
msgid "Local"
|
||||||
msgstr "מְקוֹמִי"
|
msgstr "מְקוֹמִי"
|
||||||
|
|
||||||
@@ -1658,7 +1658,7 @@ msgstr "אין אזהרות או שגיאות המכילות מילת מפתח
|
|||||||
msgid "Non-photographic and low-quality images require a review before they appear in search results."
|
msgid "Non-photographic and low-quality images require a review before they appear in search results."
|
||||||
msgstr "תמונות שאינן נראות צילום או באיכות נמוכה דורשות בדיקה לפני שהן מופיעות בתוצאות החיפוש."
|
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
|
#: src/options/options.js:293 src/options/options.js:389
|
||||||
msgid "None"
|
msgid "None"
|
||||||
msgstr "ללא"
|
msgstr "ללא"
|
||||||
@@ -2160,7 +2160,7 @@ msgstr "נתיב השרות"
|
|||||||
msgid "Services"
|
msgid "Services"
|
||||||
msgstr "שירותים"
|
msgstr "שירותים"
|
||||||
|
|
||||||
#: src/model/session.js:98
|
#: src/model/session.js:98 src/options/admin.js:62
|
||||||
msgid "Session"
|
msgid "Session"
|
||||||
msgstr "מוֹשָׁב"
|
msgstr "מוֹשָׁב"
|
||||||
|
|
||||||
|
|||||||
@@ -661,8 +661,8 @@ msgstr "दोषमार्जन लॉग"
|
|||||||
|
|
||||||
#: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100
|
#: 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/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:59 src/options/admin.js:60 src/options/admin.js:74
|
||||||
#: src/options/admin.js:92 src/options/options.js:313
|
#: 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
|
#: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142
|
||||||
msgid "Default"
|
msgid "Default"
|
||||||
msgstr "चूक"
|
msgstr "चूक"
|
||||||
@@ -1346,7 +1346,7 @@ msgstr "अंतिम सिंक"
|
|||||||
msgid "Latitude"
|
msgid "Latitude"
|
||||||
msgstr "अक्षांश"
|
msgstr "अक्षांश"
|
||||||
|
|
||||||
#: src/options/admin.js:49 src/options/admin.js:81
|
#: src/options/admin.js:49 src/options/admin.js:82
|
||||||
msgid "LDAP/AD"
|
msgid "LDAP/AD"
|
||||||
msgstr "एलडीएपी/एडी"
|
msgstr "एलडीएपी/एडी"
|
||||||
|
|
||||||
@@ -1411,8 +1411,8 @@ msgstr "लाइव"
|
|||||||
msgid "Live Photos"
|
msgid "Live Photos"
|
||||||
msgstr "लाइव तस्वीरें"
|
msgstr "लाइव तस्वीरें"
|
||||||
|
|
||||||
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77
|
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78
|
||||||
#: src/options/admin.js:96
|
#: src/options/admin.js:97
|
||||||
msgid "Local"
|
msgid "Local"
|
||||||
msgstr "स्थानीय"
|
msgstr "स्थानीय"
|
||||||
|
|
||||||
@@ -1658,7 +1658,7 @@ msgstr "इस कीवर्ड से कोई चेतावनी या
|
|||||||
msgid "Non-photographic and low-quality images require a review before they appear in search results."
|
msgid "Non-photographic and low-quality images require a review before they appear in search results."
|
||||||
msgstr "गैर-फोटोग्राफिक और निम्न-गुणवत्ता वाली छवियों को खोज परिणामों में प्रदर्शित होने से पहले समीक्षा की आवश्यकता होती है।"
|
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
|
#: src/options/options.js:293 src/options/options.js:389
|
||||||
msgid "None"
|
msgid "None"
|
||||||
msgstr "कोई नहीं"
|
msgstr "कोई नहीं"
|
||||||
@@ -2160,7 +2160,7 @@ msgstr "सेवा URL"
|
|||||||
msgid "Services"
|
msgid "Services"
|
||||||
msgstr "सेवाएं"
|
msgstr "सेवाएं"
|
||||||
|
|
||||||
#: src/model/session.js:98
|
#: src/model/session.js:98 src/options/admin.js:62
|
||||||
msgid "Session"
|
msgid "Session"
|
||||||
msgstr "सत्र"
|
msgstr "सत्र"
|
||||||
|
|
||||||
|
|||||||
@@ -661,8 +661,8 @@ msgstr "Zapisnici otklanjanja pogrešaka"
|
|||||||
|
|
||||||
#: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100
|
#: 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/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:59 src/options/admin.js:60 src/options/admin.js:74
|
||||||
#: src/options/admin.js:92 src/options/options.js:313
|
#: 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
|
#: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142
|
||||||
msgid "Default"
|
msgid "Default"
|
||||||
msgstr "Zadano"
|
msgstr "Zadano"
|
||||||
@@ -1346,7 +1346,7 @@ msgstr "Zadnja sinkronizacija"
|
|||||||
msgid "Latitude"
|
msgid "Latitude"
|
||||||
msgstr "Zemljopisna širina"
|
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"
|
msgid "LDAP/AD"
|
||||||
msgstr "LDAP/AD"
|
msgstr "LDAP/AD"
|
||||||
|
|
||||||
@@ -1411,8 +1411,8 @@ msgstr "Uživo"
|
|||||||
msgid "Live Photos"
|
msgid "Live Photos"
|
||||||
msgstr "Slike"
|
msgstr "Slike"
|
||||||
|
|
||||||
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77
|
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78
|
||||||
#: src/options/admin.js:96
|
#: src/options/admin.js:97
|
||||||
msgid "Local"
|
msgid "Local"
|
||||||
msgstr "Lokalni"
|
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."
|
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."
|
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
|
#: src/options/options.js:293 src/options/options.js:389
|
||||||
msgid "None"
|
msgid "None"
|
||||||
msgstr "Nijedan"
|
msgstr "Nijedan"
|
||||||
@@ -2160,7 +2160,7 @@ msgstr "URL usluge"
|
|||||||
msgid "Services"
|
msgid "Services"
|
||||||
msgstr "URL usluge"
|
msgstr "URL usluge"
|
||||||
|
|
||||||
#: src/model/session.js:98
|
#: src/model/session.js:98 src/options/admin.js:62
|
||||||
msgid "Session"
|
msgid "Session"
|
||||||
msgstr "Sjednica"
|
msgstr "Sjednica"
|
||||||
|
|
||||||
|
|||||||
@@ -660,8 +660,8 @@ msgstr "Hibakeresési naplók"
|
|||||||
|
|
||||||
#: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100
|
#: 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/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:59 src/options/admin.js:60 src/options/admin.js:74
|
||||||
#: src/options/admin.js:92 src/options/options.js:313
|
#: 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
|
#: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142
|
||||||
msgid "Default"
|
msgid "Default"
|
||||||
msgstr "Alapértelmezett"
|
msgstr "Alapértelmezett"
|
||||||
@@ -1345,7 +1345,7 @@ msgstr "Utolsó szinkronizálás"
|
|||||||
msgid "Latitude"
|
msgid "Latitude"
|
||||||
msgstr "Szélességi kör"
|
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"
|
msgid "LDAP/AD"
|
||||||
msgstr "LDAP/AD"
|
msgstr "LDAP/AD"
|
||||||
|
|
||||||
@@ -1410,8 +1410,8 @@ msgstr "Élő"
|
|||||||
msgid "Live Photos"
|
msgid "Live Photos"
|
||||||
msgstr "Fényképek"
|
msgstr "Fényképek"
|
||||||
|
|
||||||
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77
|
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78
|
||||||
#: src/options/admin.js:96
|
#: src/options/admin.js:97
|
||||||
msgid "Local"
|
msgid "Local"
|
||||||
msgstr "Helyi"
|
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."
|
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."
|
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
|
#: src/options/options.js:293 src/options/options.js:389
|
||||||
msgid "None"
|
msgid "None"
|
||||||
msgstr "Egyik sem"
|
msgstr "Egyik sem"
|
||||||
@@ -2159,7 +2159,7 @@ msgstr "Szolgáltatás URL-je"
|
|||||||
msgid "Services"
|
msgid "Services"
|
||||||
msgstr "Szolgáltatások"
|
msgstr "Szolgáltatások"
|
||||||
|
|
||||||
#: src/model/session.js:98
|
#: src/model/session.js:98 src/options/admin.js:62
|
||||||
msgid "Session"
|
msgid "Session"
|
||||||
msgstr "Ülés"
|
msgstr "Ülés"
|
||||||
|
|
||||||
|
|||||||
@@ -661,8 +661,8 @@ msgstr "Log Debug"
|
|||||||
|
|
||||||
#: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100
|
#: 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/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:59 src/options/admin.js:60 src/options/admin.js:74
|
||||||
#: src/options/admin.js:92 src/options/options.js:313
|
#: 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
|
#: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142
|
||||||
msgid "Default"
|
msgid "Default"
|
||||||
msgstr "Bawaan"
|
msgstr "Bawaan"
|
||||||
@@ -1346,7 +1346,7 @@ msgstr "Sinkronisasi Terakhir"
|
|||||||
msgid "Latitude"
|
msgid "Latitude"
|
||||||
msgstr "Lintang"
|
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"
|
msgid "LDAP/AD"
|
||||||
msgstr "LDAP/AD"
|
msgstr "LDAP/AD"
|
||||||
|
|
||||||
@@ -1411,8 +1411,8 @@ msgstr "Langsung"
|
|||||||
msgid "Live Photos"
|
msgid "Live Photos"
|
||||||
msgstr "Foto"
|
msgstr "Foto"
|
||||||
|
|
||||||
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77
|
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78
|
||||||
#: src/options/admin.js:96
|
#: src/options/admin.js:97
|
||||||
msgid "Local"
|
msgid "Local"
|
||||||
msgstr "Lokal"
|
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."
|
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."
|
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
|
#: src/options/options.js:293 src/options/options.js:389
|
||||||
msgid "None"
|
msgid "None"
|
||||||
msgstr "Tidak ada"
|
msgstr "Tidak ada"
|
||||||
@@ -2160,7 +2160,7 @@ msgstr "URL Layanan"
|
|||||||
msgid "Services"
|
msgid "Services"
|
||||||
msgstr "URL Layanan"
|
msgstr "URL Layanan"
|
||||||
|
|
||||||
#: src/model/session.js:98
|
#: src/model/session.js:98 src/options/admin.js:62
|
||||||
msgid "Session"
|
msgid "Session"
|
||||||
msgstr "Sesi"
|
msgstr "Sesi"
|
||||||
|
|
||||||
|
|||||||
@@ -661,8 +661,8 @@ msgstr "Registri di debug"
|
|||||||
|
|
||||||
#: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100
|
#: 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/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:59 src/options/admin.js:60 src/options/admin.js:74
|
||||||
#: src/options/admin.js:92 src/options/options.js:313
|
#: 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
|
#: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142
|
||||||
msgid "Default"
|
msgid "Default"
|
||||||
msgstr "Predefinito"
|
msgstr "Predefinito"
|
||||||
@@ -1346,7 +1346,7 @@ msgstr "Ultima sincronizzazione"
|
|||||||
msgid "Latitude"
|
msgid "Latitude"
|
||||||
msgstr "Latitudine"
|
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"
|
msgid "LDAP/AD"
|
||||||
msgstr "LDAP/AD"
|
msgstr "LDAP/AD"
|
||||||
|
|
||||||
@@ -1411,8 +1411,8 @@ msgstr "Live"
|
|||||||
msgid "Live Photos"
|
msgid "Live Photos"
|
||||||
msgstr "Foto dal vivo"
|
msgstr "Foto dal vivo"
|
||||||
|
|
||||||
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77
|
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78
|
||||||
#: src/options/admin.js:96
|
#: src/options/admin.js:97
|
||||||
msgid "Local"
|
msgid "Local"
|
||||||
msgstr "Locale"
|
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."
|
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."
|
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
|
#: src/options/options.js:293 src/options/options.js:389
|
||||||
msgid "None"
|
msgid "None"
|
||||||
msgstr "Nessuno"
|
msgstr "Nessuno"
|
||||||
@@ -2160,7 +2160,7 @@ msgstr "URL Servizio"
|
|||||||
msgid "Services"
|
msgid "Services"
|
||||||
msgstr "Servizi"
|
msgstr "Servizi"
|
||||||
|
|
||||||
#: src/model/session.js:98
|
#: src/model/session.js:98 src/options/admin.js:62
|
||||||
msgid "Session"
|
msgid "Session"
|
||||||
msgstr "Sessione"
|
msgstr "Sessione"
|
||||||
|
|
||||||
|
|||||||
@@ -661,8 +661,8 @@ msgstr "デバッグログ"
|
|||||||
|
|
||||||
#: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100
|
#: 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/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:59 src/options/admin.js:60 src/options/admin.js:74
|
||||||
#: src/options/admin.js:92 src/options/options.js:313
|
#: 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
|
#: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142
|
||||||
msgid "Default"
|
msgid "Default"
|
||||||
msgstr "既定"
|
msgstr "既定"
|
||||||
@@ -1346,7 +1346,7 @@ msgstr "最終同期"
|
|||||||
msgid "Latitude"
|
msgid "Latitude"
|
||||||
msgstr "緯度"
|
msgstr "緯度"
|
||||||
|
|
||||||
#: src/options/admin.js:49 src/options/admin.js:81
|
#: src/options/admin.js:49 src/options/admin.js:82
|
||||||
msgid "LDAP/AD"
|
msgid "LDAP/AD"
|
||||||
msgstr "LDAP/AD"
|
msgstr "LDAP/AD"
|
||||||
|
|
||||||
@@ -1411,8 +1411,8 @@ msgstr "ライブ"
|
|||||||
msgid "Live Photos"
|
msgid "Live Photos"
|
||||||
msgstr "ライブ写真"
|
msgstr "ライブ写真"
|
||||||
|
|
||||||
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77
|
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78
|
||||||
#: src/options/admin.js:96
|
#: src/options/admin.js:97
|
||||||
msgid "Local"
|
msgid "Local"
|
||||||
msgstr "ローカル"
|
msgstr "ローカル"
|
||||||
|
|
||||||
@@ -1658,7 +1658,7 @@ msgstr "このキーワードを含む警告やエラーは1つも見つかり
|
|||||||
msgid "Non-photographic and low-quality images require a review before they appear in search results."
|
msgid "Non-photographic and low-quality images require a review before they appear in search results."
|
||||||
msgstr "写真ではないものや、低品質な画像は検索結果に現れる前にレビューが必要です。"
|
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
|
#: src/options/options.js:293 src/options/options.js:389
|
||||||
msgid "None"
|
msgid "None"
|
||||||
msgstr "なし"
|
msgstr "なし"
|
||||||
@@ -2160,7 +2160,7 @@ msgstr "サービス URL"
|
|||||||
msgid "Services"
|
msgid "Services"
|
||||||
msgstr "サービス"
|
msgstr "サービス"
|
||||||
|
|
||||||
#: src/model/session.js:98
|
#: src/model/session.js:98 src/options/admin.js:62
|
||||||
msgid "Session"
|
msgid "Session"
|
||||||
msgstr "セッション"
|
msgstr "セッション"
|
||||||
|
|
||||||
|
|||||||
@@ -661,8 +661,8 @@ msgstr "디버그 로그"
|
|||||||
|
|
||||||
#: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100
|
#: 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/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:59 src/options/admin.js:60 src/options/admin.js:74
|
||||||
#: src/options/admin.js:92 src/options/options.js:313
|
#: 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
|
#: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142
|
||||||
msgid "Default"
|
msgid "Default"
|
||||||
msgstr "기본값"
|
msgstr "기본값"
|
||||||
@@ -1346,7 +1346,7 @@ msgstr "마지막 동기화"
|
|||||||
msgid "Latitude"
|
msgid "Latitude"
|
||||||
msgstr "위도"
|
msgstr "위도"
|
||||||
|
|
||||||
#: src/options/admin.js:49 src/options/admin.js:81
|
#: src/options/admin.js:49 src/options/admin.js:82
|
||||||
msgid "LDAP/AD"
|
msgid "LDAP/AD"
|
||||||
msgstr "LDAP/AD"
|
msgstr "LDAP/AD"
|
||||||
|
|
||||||
@@ -1411,8 +1411,8 @@ msgstr "라이브"
|
|||||||
msgid "Live Photos"
|
msgid "Live Photos"
|
||||||
msgstr "라이브 포토"
|
msgstr "라이브 포토"
|
||||||
|
|
||||||
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77
|
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78
|
||||||
#: src/options/admin.js:96
|
#: src/options/admin.js:97
|
||||||
msgid "Local"
|
msgid "Local"
|
||||||
msgstr "로컬"
|
msgstr "로컬"
|
||||||
|
|
||||||
@@ -1658,7 +1658,7 @@ msgstr "이 키워드를 포함하는 경고 또는 오류가 없습니다. 검
|
|||||||
msgid "Non-photographic and low-quality images require a review before they appear in search results."
|
msgid "Non-photographic and low-quality images require a review before they appear in search results."
|
||||||
msgstr "사진이 아닌 저품질 이미지는 검색 결과에 표시되기 전에 검토가 필요합니다."
|
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
|
#: src/options/options.js:293 src/options/options.js:389
|
||||||
msgid "None"
|
msgid "None"
|
||||||
msgstr "없음"
|
msgstr "없음"
|
||||||
@@ -2160,7 +2160,7 @@ msgstr "서비스 URL"
|
|||||||
msgid "Services"
|
msgid "Services"
|
||||||
msgstr "서비스"
|
msgstr "서비스"
|
||||||
|
|
||||||
#: src/model/session.js:98
|
#: src/model/session.js:98 src/options/admin.js:62
|
||||||
msgid "Session"
|
msgid "Session"
|
||||||
msgstr "세션"
|
msgstr "세션"
|
||||||
|
|
||||||
|
|||||||
@@ -661,8 +661,8 @@ msgstr "تۆماری هەڵەکان"
|
|||||||
|
|
||||||
#: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100
|
#: 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/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:59 src/options/admin.js:60 src/options/admin.js:74
|
||||||
#: src/options/admin.js:92 src/options/options.js:313
|
#: 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
|
#: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142
|
||||||
msgid "Default"
|
msgid "Default"
|
||||||
msgstr "بنهڕهت"
|
msgstr "بنهڕهت"
|
||||||
@@ -1346,7 +1346,7 @@ msgstr "هاوکاتگەری"
|
|||||||
msgid "Latitude"
|
msgid "Latitude"
|
||||||
msgstr "هێڵی پانیی"
|
msgstr "هێڵی پانیی"
|
||||||
|
|
||||||
#: src/options/admin.js:49 src/options/admin.js:81
|
#: src/options/admin.js:49 src/options/admin.js:82
|
||||||
msgid "LDAP/AD"
|
msgid "LDAP/AD"
|
||||||
msgstr "LDAP/AD"
|
msgstr "LDAP/AD"
|
||||||
|
|
||||||
@@ -1411,8 +1411,8 @@ msgstr "زیندوو"
|
|||||||
msgid "Live Photos"
|
msgid "Live Photos"
|
||||||
msgstr "وێنەکان"
|
msgstr "وێنەکان"
|
||||||
|
|
||||||
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77
|
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78
|
||||||
#: src/options/admin.js:96
|
#: src/options/admin.js:97
|
||||||
msgid "Local"
|
msgid "Local"
|
||||||
msgstr "Herêmî"
|
msgstr "Herêmî"
|
||||||
|
|
||||||
@@ -1658,7 +1658,7 @@ msgstr "هیچ ئاگادارییەک یان هەڵەیەک نیە کە ئەم
|
|||||||
msgid "Non-photographic and low-quality images require a review before they appear in search results."
|
msgid "Non-photographic and low-quality images require a review before they appear in search results."
|
||||||
msgstr "وێنە نافۆتۆگرافی و کوالێتی نزمەکان پێویستی بە پێداچونەوە هەیە پێش ئەوەی لە ئەنجامی گەڕاندا دەرکەون."
|
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
|
#: src/options/options.js:293 src/options/options.js:389
|
||||||
msgid "None"
|
msgid "None"
|
||||||
msgstr "هیچ"
|
msgstr "هیچ"
|
||||||
@@ -2160,7 +2160,7 @@ msgstr "بەستەری خزمەتگوزاری"
|
|||||||
msgid "Services"
|
msgid "Services"
|
||||||
msgstr "بەستەری خزمەتگوزاری"
|
msgstr "بەستەری خزمەتگوزاری"
|
||||||
|
|
||||||
#: src/model/session.js:98
|
#: src/model/session.js:98 src/options/admin.js:62
|
||||||
msgid "Session"
|
msgid "Session"
|
||||||
msgstr "Rûniştinî"
|
msgstr "Rûniştinî"
|
||||||
|
|
||||||
|
|||||||
@@ -661,8 +661,8 @@ msgstr "Derinimo žurnalai"
|
|||||||
|
|
||||||
#: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100
|
#: 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/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:59 src/options/admin.js:60 src/options/admin.js:74
|
||||||
#: src/options/admin.js:92 src/options/options.js:313
|
#: 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
|
#: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142
|
||||||
msgid "Default"
|
msgid "Default"
|
||||||
msgstr "Numatytoji"
|
msgstr "Numatytoji"
|
||||||
@@ -1346,7 +1346,7 @@ msgstr "Sinchronizavimas"
|
|||||||
msgid "Latitude"
|
msgid "Latitude"
|
||||||
msgstr "Platuma"
|
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"
|
msgid "LDAP/AD"
|
||||||
msgstr "LDAP/AD"
|
msgstr "LDAP/AD"
|
||||||
|
|
||||||
@@ -1411,8 +1411,8 @@ msgstr "Gyvai"
|
|||||||
msgid "Live Photos"
|
msgid "Live Photos"
|
||||||
msgstr "Nuotraukos"
|
msgstr "Nuotraukos"
|
||||||
|
|
||||||
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77
|
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78
|
||||||
#: src/options/admin.js:96
|
#: src/options/admin.js:97
|
||||||
msgid "Local"
|
msgid "Local"
|
||||||
msgstr "Vietinis"
|
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."
|
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."
|
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
|
#: src/options/options.js:293 src/options/options.js:389
|
||||||
msgid "None"
|
msgid "None"
|
||||||
msgstr "Nėra"
|
msgstr "Nėra"
|
||||||
@@ -2160,7 +2160,7 @@ msgstr "Paslaugos URL"
|
|||||||
msgid "Services"
|
msgid "Services"
|
||||||
msgstr "Paslaugos URL"
|
msgstr "Paslaugos URL"
|
||||||
|
|
||||||
#: src/model/session.js:98
|
#: src/model/session.js:98 src/options/admin.js:62
|
||||||
msgid "Session"
|
msgid "Session"
|
||||||
msgstr "Sesija"
|
msgstr "Sesija"
|
||||||
|
|
||||||
|
|||||||
@@ -661,8 +661,8 @@ msgstr "Log Nyahpepijat"
|
|||||||
|
|
||||||
#: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100
|
#: 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/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:59 src/options/admin.js:60 src/options/admin.js:74
|
||||||
#: src/options/admin.js:92 src/options/options.js:313
|
#: 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
|
#: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142
|
||||||
msgid "Default"
|
msgid "Default"
|
||||||
msgstr "Lalai"
|
msgstr "Lalai"
|
||||||
@@ -1346,7 +1346,7 @@ msgstr "Penyegerakan Terakhir"
|
|||||||
msgid "Latitude"
|
msgid "Latitude"
|
||||||
msgstr "Latitud"
|
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"
|
msgid "LDAP/AD"
|
||||||
msgstr "LDAP/AD"
|
msgstr "LDAP/AD"
|
||||||
|
|
||||||
@@ -1411,8 +1411,8 @@ msgstr "Langsung"
|
|||||||
msgid "Live Photos"
|
msgid "Live Photos"
|
||||||
msgstr "Foto"
|
msgstr "Foto"
|
||||||
|
|
||||||
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77
|
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78
|
||||||
#: src/options/admin.js:96
|
#: src/options/admin.js:97
|
||||||
msgid "Local"
|
msgid "Local"
|
||||||
msgstr "Tempatan"
|
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."
|
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."
|
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
|
#: src/options/options.js:293 src/options/options.js:389
|
||||||
msgid "None"
|
msgid "None"
|
||||||
msgstr "Tiada"
|
msgstr "Tiada"
|
||||||
@@ -2160,7 +2160,7 @@ msgstr "URL perkhidmatan"
|
|||||||
msgid "Services"
|
msgid "Services"
|
||||||
msgstr "URL perkhidmatan"
|
msgstr "URL perkhidmatan"
|
||||||
|
|
||||||
#: src/model/session.js:98
|
#: src/model/session.js:98 src/options/admin.js:62
|
||||||
msgid "Session"
|
msgid "Session"
|
||||||
msgstr "Sesi"
|
msgstr "Sesi"
|
||||||
|
|
||||||
|
|||||||
@@ -661,8 +661,8 @@ msgstr "Feilsøkingslogger"
|
|||||||
|
|
||||||
#: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100
|
#: 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/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:59 src/options/admin.js:60 src/options/admin.js:74
|
||||||
#: src/options/admin.js:92 src/options/options.js:313
|
#: 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
|
#: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142
|
||||||
msgid "Default"
|
msgid "Default"
|
||||||
msgstr "Standard"
|
msgstr "Standard"
|
||||||
@@ -1346,7 +1346,7 @@ msgstr "Siste synkronisering"
|
|||||||
msgid "Latitude"
|
msgid "Latitude"
|
||||||
msgstr "Breddegrad"
|
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"
|
msgid "LDAP/AD"
|
||||||
msgstr "LDAP/AD"
|
msgstr "LDAP/AD"
|
||||||
|
|
||||||
@@ -1411,8 +1411,8 @@ msgstr "Direkte"
|
|||||||
msgid "Live Photos"
|
msgid "Live Photos"
|
||||||
msgstr "Fotoer"
|
msgstr "Fotoer"
|
||||||
|
|
||||||
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77
|
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78
|
||||||
#: src/options/admin.js:96
|
#: src/options/admin.js:97
|
||||||
msgid "Local"
|
msgid "Local"
|
||||||
msgstr "Lokalt"
|
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."
|
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."
|
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
|
#: src/options/options.js:293 src/options/options.js:389
|
||||||
msgid "None"
|
msgid "None"
|
||||||
msgstr "Ingen"
|
msgstr "Ingen"
|
||||||
@@ -2160,7 +2160,7 @@ msgstr "Tjeneste-URL"
|
|||||||
msgid "Services"
|
msgid "Services"
|
||||||
msgstr "Tjenester"
|
msgstr "Tjenester"
|
||||||
|
|
||||||
#: src/model/session.js:98
|
#: src/model/session.js:98 src/options/admin.js:62
|
||||||
msgid "Session"
|
msgid "Session"
|
||||||
msgstr "Sesjon"
|
msgstr "Sesjon"
|
||||||
|
|
||||||
|
|||||||
@@ -661,8 +661,8 @@ msgstr "Debug-logs"
|
|||||||
|
|
||||||
#: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100
|
#: 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/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:59 src/options/admin.js:60 src/options/admin.js:74
|
||||||
#: src/options/admin.js:92 src/options/options.js:313
|
#: 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
|
#: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142
|
||||||
msgid "Default"
|
msgid "Default"
|
||||||
msgstr "Standaard"
|
msgstr "Standaard"
|
||||||
@@ -1346,7 +1346,7 @@ msgstr "Laatste synchronisatie"
|
|||||||
msgid "Latitude"
|
msgid "Latitude"
|
||||||
msgstr "Breedtegraad"
|
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"
|
msgid "LDAP/AD"
|
||||||
msgstr "LDAP/AD"
|
msgstr "LDAP/AD"
|
||||||
|
|
||||||
@@ -1411,8 +1411,8 @@ msgstr "Live"
|
|||||||
msgid "Live Photos"
|
msgid "Live Photos"
|
||||||
msgstr "Live foto's"
|
msgstr "Live foto's"
|
||||||
|
|
||||||
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77
|
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78
|
||||||
#: src/options/admin.js:96
|
#: src/options/admin.js:97
|
||||||
msgid "Local"
|
msgid "Local"
|
||||||
msgstr "Lokaal"
|
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."
|
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."
|
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
|
#: src/options/options.js:293 src/options/options.js:389
|
||||||
msgid "None"
|
msgid "None"
|
||||||
msgstr "Geen"
|
msgstr "Geen"
|
||||||
@@ -2160,7 +2160,7 @@ msgstr "Service URL"
|
|||||||
msgid "Services"
|
msgid "Services"
|
||||||
msgstr "Diensten"
|
msgstr "Diensten"
|
||||||
|
|
||||||
#: src/model/session.js:98
|
#: src/model/session.js:98 src/options/admin.js:62
|
||||||
msgid "Session"
|
msgid "Session"
|
||||||
msgstr "Sessie"
|
msgstr "Sessie"
|
||||||
|
|
||||||
|
|||||||
@@ -661,8 +661,8 @@ msgstr "Logi debugowania"
|
|||||||
|
|
||||||
#: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100
|
#: 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/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:59 src/options/admin.js:60 src/options/admin.js:74
|
||||||
#: src/options/admin.js:92 src/options/options.js:313
|
#: 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
|
#: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142
|
||||||
msgid "Default"
|
msgid "Default"
|
||||||
msgstr "Domyślny"
|
msgstr "Domyślny"
|
||||||
@@ -1346,7 +1346,7 @@ msgstr "Ostatnia synchronizacja"
|
|||||||
msgid "Latitude"
|
msgid "Latitude"
|
||||||
msgstr "Szerokość geograficzna"
|
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"
|
msgid "LDAP/AD"
|
||||||
msgstr "LDAP/AD"
|
msgstr "LDAP/AD"
|
||||||
|
|
||||||
@@ -1411,8 +1411,8 @@ msgstr "Live"
|
|||||||
msgid "Live Photos"
|
msgid "Live Photos"
|
||||||
msgstr "Zdjęcia na żywo"
|
msgstr "Zdjęcia na żywo"
|
||||||
|
|
||||||
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77
|
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78
|
||||||
#: src/options/admin.js:96
|
#: src/options/admin.js:97
|
||||||
msgid "Local"
|
msgid "Local"
|
||||||
msgstr "Lokalnie"
|
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."
|
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."
|
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
|
#: src/options/options.js:293 src/options/options.js:389
|
||||||
msgid "None"
|
msgid "None"
|
||||||
msgstr "Brak"
|
msgstr "Brak"
|
||||||
@@ -2160,7 +2160,7 @@ msgstr "Adres URL do usługi"
|
|||||||
msgid "Services"
|
msgid "Services"
|
||||||
msgstr "Usługi"
|
msgstr "Usługi"
|
||||||
|
|
||||||
#: src/model/session.js:98
|
#: src/model/session.js:98 src/options/admin.js:62
|
||||||
msgid "Session"
|
msgid "Session"
|
||||||
msgstr "Sesja"
|
msgstr "Sesja"
|
||||||
|
|
||||||
|
|||||||
@@ -661,8 +661,8 @@ msgstr "Registros de depuração"
|
|||||||
|
|
||||||
#: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100
|
#: 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/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:59 src/options/admin.js:60 src/options/admin.js:74
|
||||||
#: src/options/admin.js:92 src/options/options.js:313
|
#: 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
|
#: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142
|
||||||
msgid "Default"
|
msgid "Default"
|
||||||
msgstr "Padrão"
|
msgstr "Padrão"
|
||||||
@@ -1346,7 +1346,7 @@ msgstr "Última Sincronia"
|
|||||||
msgid "Latitude"
|
msgid "Latitude"
|
||||||
msgstr "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"
|
msgid "LDAP/AD"
|
||||||
msgstr "LDAP/AD"
|
msgstr "LDAP/AD"
|
||||||
|
|
||||||
@@ -1411,8 +1411,8 @@ msgstr "Ao vivo"
|
|||||||
msgid "Live Photos"
|
msgid "Live Photos"
|
||||||
msgstr "Fotos ao vivo"
|
msgstr "Fotos ao vivo"
|
||||||
|
|
||||||
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77
|
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78
|
||||||
#: src/options/admin.js:96
|
#: src/options/admin.js:97
|
||||||
msgid "Local"
|
msgid "Local"
|
||||||
msgstr "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."
|
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."
|
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
|
#: src/options/options.js:293 src/options/options.js:389
|
||||||
msgid "None"
|
msgid "None"
|
||||||
msgstr "Nenhum"
|
msgstr "Nenhum"
|
||||||
@@ -2160,7 +2160,7 @@ msgstr "URL do serviço"
|
|||||||
msgid "Services"
|
msgid "Services"
|
||||||
msgstr "Serviços"
|
msgstr "Serviços"
|
||||||
|
|
||||||
#: src/model/session.js:98
|
#: src/model/session.js:98 src/options/admin.js:62
|
||||||
msgid "Session"
|
msgid "Session"
|
||||||
msgstr "Sessão"
|
msgstr "Sessão"
|
||||||
|
|
||||||
|
|||||||
@@ -661,8 +661,8 @@ msgstr "Registros de depuração"
|
|||||||
|
|
||||||
#: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100
|
#: 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/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:59 src/options/admin.js:60 src/options/admin.js:74
|
||||||
#: src/options/admin.js:92 src/options/options.js:313
|
#: 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
|
#: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142
|
||||||
msgid "Default"
|
msgid "Default"
|
||||||
msgstr "Padrão"
|
msgstr "Padrão"
|
||||||
@@ -1346,7 +1346,7 @@ msgstr "Última Sincronia"
|
|||||||
msgid "Latitude"
|
msgid "Latitude"
|
||||||
msgstr "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"
|
msgid "LDAP/AD"
|
||||||
msgstr "LDAP/AD"
|
msgstr "LDAP/AD"
|
||||||
|
|
||||||
@@ -1411,8 +1411,8 @@ msgstr "Ao vivo"
|
|||||||
msgid "Live Photos"
|
msgid "Live Photos"
|
||||||
msgstr "Fotos ao vivo"
|
msgstr "Fotos ao vivo"
|
||||||
|
|
||||||
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77
|
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78
|
||||||
#: src/options/admin.js:96
|
#: src/options/admin.js:97
|
||||||
msgid "Local"
|
msgid "Local"
|
||||||
msgstr "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."
|
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."
|
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
|
#: src/options/options.js:293 src/options/options.js:389
|
||||||
msgid "None"
|
msgid "None"
|
||||||
msgstr "Nenhum"
|
msgstr "Nenhum"
|
||||||
@@ -2160,7 +2160,7 @@ msgstr "URL do serviço"
|
|||||||
msgid "Services"
|
msgid "Services"
|
||||||
msgstr "Serviços"
|
msgstr "Serviços"
|
||||||
|
|
||||||
#: src/model/session.js:98
|
#: src/model/session.js:98 src/options/admin.js:62
|
||||||
msgid "Session"
|
msgid "Session"
|
||||||
msgstr "Sessão"
|
msgstr "Sessão"
|
||||||
|
|
||||||
|
|||||||
@@ -661,8 +661,8 @@ msgstr "Jurnalele de depanare"
|
|||||||
|
|
||||||
#: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100
|
#: 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/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:59 src/options/admin.js:60 src/options/admin.js:74
|
||||||
#: src/options/admin.js:92 src/options/options.js:313
|
#: 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
|
#: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142
|
||||||
msgid "Default"
|
msgid "Default"
|
||||||
msgstr "Implicit"
|
msgstr "Implicit"
|
||||||
@@ -1346,7 +1346,7 @@ msgstr "Ultima sincronizare"
|
|||||||
msgid "Latitude"
|
msgid "Latitude"
|
||||||
msgstr "Latitudine"
|
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"
|
msgid "LDAP/AD"
|
||||||
msgstr "LDAP/AD"
|
msgstr "LDAP/AD"
|
||||||
|
|
||||||
@@ -1411,8 +1411,8 @@ msgstr "Live"
|
|||||||
msgid "Live Photos"
|
msgid "Live Photos"
|
||||||
msgstr "Fotografii în direct"
|
msgstr "Fotografii în direct"
|
||||||
|
|
||||||
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77
|
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78
|
||||||
#: src/options/admin.js:96
|
#: src/options/admin.js:97
|
||||||
msgid "Local"
|
msgid "Local"
|
||||||
msgstr "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."
|
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."
|
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
|
#: src/options/options.js:293 src/options/options.js:389
|
||||||
msgid "None"
|
msgid "None"
|
||||||
msgstr "Nici unul"
|
msgstr "Nici unul"
|
||||||
@@ -2160,7 +2160,7 @@ msgstr "URL de serviciu"
|
|||||||
msgid "Services"
|
msgid "Services"
|
||||||
msgstr "Servicii"
|
msgstr "Servicii"
|
||||||
|
|
||||||
#: src/model/session.js:98
|
#: src/model/session.js:98 src/options/admin.js:62
|
||||||
msgid "Session"
|
msgid "Session"
|
||||||
msgstr "Sesiunea"
|
msgstr "Sesiunea"
|
||||||
|
|
||||||
|
|||||||
@@ -661,8 +661,8 @@ msgstr "Отладочные Логи"
|
|||||||
|
|
||||||
#: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100
|
#: 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/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:59 src/options/admin.js:60 src/options/admin.js:74
|
||||||
#: src/options/admin.js:92 src/options/options.js:313
|
#: 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
|
#: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142
|
||||||
msgid "Default"
|
msgid "Default"
|
||||||
msgstr "По умолчанию"
|
msgstr "По умолчанию"
|
||||||
@@ -1346,7 +1346,7 @@ msgstr "Последняя синхронизация"
|
|||||||
msgid "Latitude"
|
msgid "Latitude"
|
||||||
msgstr "Широта"
|
msgstr "Широта"
|
||||||
|
|
||||||
#: src/options/admin.js:49 src/options/admin.js:81
|
#: src/options/admin.js:49 src/options/admin.js:82
|
||||||
msgid "LDAP/AD"
|
msgid "LDAP/AD"
|
||||||
msgstr "LDAP/AD"
|
msgstr "LDAP/AD"
|
||||||
|
|
||||||
@@ -1411,8 +1411,8 @@ msgstr "Прямой эфир"
|
|||||||
msgid "Live Photos"
|
msgid "Live Photos"
|
||||||
msgstr "Живые фотографии"
|
msgstr "Живые фотографии"
|
||||||
|
|
||||||
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77
|
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78
|
||||||
#: src/options/admin.js:96
|
#: src/options/admin.js:97
|
||||||
msgid "Local"
|
msgid "Local"
|
||||||
msgstr "Местный"
|
msgstr "Местный"
|
||||||
|
|
||||||
@@ -1658,7 +1658,7 @@ msgstr "Нет предупреждений или ошибок содержащ
|
|||||||
msgid "Non-photographic and low-quality images require a review before they appear in search results."
|
msgid "Non-photographic and low-quality images require a review before they appear in search results."
|
||||||
msgstr "Файлы, не являющиеся фотографиями, или изображения низкого качества нужно одобрить, чтобы они появились в результатах поиска."
|
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
|
#: src/options/options.js:293 src/options/options.js:389
|
||||||
msgid "None"
|
msgid "None"
|
||||||
msgstr "Ничего"
|
msgstr "Ничего"
|
||||||
@@ -2160,7 +2160,7 @@ msgstr "URL сервиса"
|
|||||||
msgid "Services"
|
msgid "Services"
|
||||||
msgstr "Сервисы"
|
msgstr "Сервисы"
|
||||||
|
|
||||||
#: src/model/session.js:98
|
#: src/model/session.js:98 src/options/admin.js:62
|
||||||
msgid "Session"
|
msgid "Session"
|
||||||
msgstr "Сессия"
|
msgstr "Сессия"
|
||||||
|
|
||||||
|
|||||||
@@ -661,8 +661,8 @@ msgstr "Denníky ladenia"
|
|||||||
|
|
||||||
#: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100
|
#: 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/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:59 src/options/admin.js:60 src/options/admin.js:74
|
||||||
#: src/options/admin.js:92 src/options/options.js:313
|
#: 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
|
#: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142
|
||||||
msgid "Default"
|
msgid "Default"
|
||||||
msgstr "Predvolená"
|
msgstr "Predvolená"
|
||||||
@@ -1346,7 +1346,7 @@ msgstr "Posledná synchronizácia"
|
|||||||
msgid "Latitude"
|
msgid "Latitude"
|
||||||
msgstr "Šírka"
|
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"
|
msgid "LDAP/AD"
|
||||||
msgstr "LDAP/AD"
|
msgstr "LDAP/AD"
|
||||||
|
|
||||||
@@ -1411,8 +1411,8 @@ msgstr "Živé"
|
|||||||
msgid "Live Photos"
|
msgid "Live Photos"
|
||||||
msgstr "Živé fotografie"
|
msgstr "Živé fotografie"
|
||||||
|
|
||||||
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77
|
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78
|
||||||
#: src/options/admin.js:96
|
#: src/options/admin.js:97
|
||||||
msgid "Local"
|
msgid "Local"
|
||||||
msgstr "Miestne stránky"
|
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."
|
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."
|
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
|
#: src/options/options.js:293 src/options/options.js:389
|
||||||
msgid "None"
|
msgid "None"
|
||||||
msgstr "Žiadne"
|
msgstr "Žiadne"
|
||||||
@@ -2160,7 +2160,7 @@ msgstr "URL Služby"
|
|||||||
msgid "Services"
|
msgid "Services"
|
||||||
msgstr "Služby"
|
msgstr "Služby"
|
||||||
|
|
||||||
#: src/model/session.js:98
|
#: src/model/session.js:98 src/options/admin.js:62
|
||||||
msgid "Session"
|
msgid "Session"
|
||||||
msgstr "Zasadnutie"
|
msgstr "Zasadnutie"
|
||||||
|
|
||||||
|
|||||||
@@ -658,8 +658,8 @@ msgstr "Dnevniki za odpravljanje napak"
|
|||||||
|
|
||||||
#: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100
|
#: 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/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:59 src/options/admin.js:60 src/options/admin.js:74
|
||||||
#: src/options/admin.js:92 src/options/options.js:313
|
#: 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
|
#: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142
|
||||||
msgid "Default"
|
msgid "Default"
|
||||||
msgstr "Privzeto"
|
msgstr "Privzeto"
|
||||||
@@ -1343,7 +1343,7 @@ msgstr "Zadnja sinhronizacija"
|
|||||||
msgid "Latitude"
|
msgid "Latitude"
|
||||||
msgstr "Zemljepisna širina"
|
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"
|
msgid "LDAP/AD"
|
||||||
msgstr "LDAP/AD"
|
msgstr "LDAP/AD"
|
||||||
|
|
||||||
@@ -1408,8 +1408,8 @@ msgstr "V živo"
|
|||||||
msgid "Live Photos"
|
msgid "Live Photos"
|
||||||
msgstr "Fotografije"
|
msgstr "Fotografije"
|
||||||
|
|
||||||
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77
|
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78
|
||||||
#: src/options/admin.js:96
|
#: src/options/admin.js:97
|
||||||
msgid "Local"
|
msgid "Local"
|
||||||
msgstr "Lokalni"
|
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."
|
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."
|
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
|
#: src/options/options.js:293 src/options/options.js:389
|
||||||
msgid "None"
|
msgid "None"
|
||||||
msgstr "Ni"
|
msgstr "Ni"
|
||||||
@@ -2157,7 +2157,7 @@ msgstr "URL storitve"
|
|||||||
msgid "Services"
|
msgid "Services"
|
||||||
msgstr "URL storitve"
|
msgstr "URL storitve"
|
||||||
|
|
||||||
#: src/model/session.js:98
|
#: src/model/session.js:98 src/options/admin.js:62
|
||||||
msgid "Session"
|
msgid "Session"
|
||||||
msgstr "Seja"
|
msgstr "Seja"
|
||||||
|
|
||||||
|
|||||||
@@ -661,8 +661,8 @@ msgstr "Felsökningsloggar"
|
|||||||
|
|
||||||
#: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100
|
#: 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/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:59 src/options/admin.js:60 src/options/admin.js:74
|
||||||
#: src/options/admin.js:92 src/options/options.js:313
|
#: 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
|
#: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142
|
||||||
msgid "Default"
|
msgid "Default"
|
||||||
msgstr "Standard"
|
msgstr "Standard"
|
||||||
@@ -1346,7 +1346,7 @@ msgstr "Senaste synkronisering"
|
|||||||
msgid "Latitude"
|
msgid "Latitude"
|
||||||
msgstr "Latitud"
|
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"
|
msgid "LDAP/AD"
|
||||||
msgstr "LDAP/AD"
|
msgstr "LDAP/AD"
|
||||||
|
|
||||||
@@ -1411,8 +1411,8 @@ msgstr "Live"
|
|||||||
msgid "Live Photos"
|
msgid "Live Photos"
|
||||||
msgstr "Foton"
|
msgstr "Foton"
|
||||||
|
|
||||||
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77
|
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78
|
||||||
#: src/options/admin.js:96
|
#: src/options/admin.js:97
|
||||||
msgid "Local"
|
msgid "Local"
|
||||||
msgstr "Lokal"
|
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."
|
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."
|
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
|
#: src/options/options.js:293 src/options/options.js:389
|
||||||
msgid "None"
|
msgid "None"
|
||||||
msgstr "Ingen"
|
msgstr "Ingen"
|
||||||
@@ -2160,7 +2160,7 @@ msgstr "Tjänstens URL"
|
|||||||
msgid "Services"
|
msgid "Services"
|
||||||
msgstr "Tjänstens URL"
|
msgstr "Tjänstens URL"
|
||||||
|
|
||||||
#: src/model/session.js:98
|
#: src/model/session.js:98 src/options/admin.js:62
|
||||||
msgid "Session"
|
msgid "Session"
|
||||||
msgstr "Sammanträde"
|
msgstr "Sammanträde"
|
||||||
|
|
||||||
|
|||||||
@@ -661,8 +661,8 @@ msgstr "บันทึกการดีบัก"
|
|||||||
|
|
||||||
#: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100
|
#: 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/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:59 src/options/admin.js:60 src/options/admin.js:74
|
||||||
#: src/options/admin.js:92 src/options/options.js:313
|
#: 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
|
#: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142
|
||||||
msgid "Default"
|
msgid "Default"
|
||||||
msgstr "ค่าเริ่มต้น"
|
msgstr "ค่าเริ่มต้น"
|
||||||
@@ -1346,7 +1346,7 @@ msgstr "ซิงค์ล่าสุด"
|
|||||||
msgid "Latitude"
|
msgid "Latitude"
|
||||||
msgstr "ละติจูด"
|
msgstr "ละติจูด"
|
||||||
|
|
||||||
#: src/options/admin.js:49 src/options/admin.js:81
|
#: src/options/admin.js:49 src/options/admin.js:82
|
||||||
msgid "LDAP/AD"
|
msgid "LDAP/AD"
|
||||||
msgstr "แอลดีเอพี/ค.ศ"
|
msgstr "แอลดีเอพี/ค.ศ"
|
||||||
|
|
||||||
@@ -1411,8 +1411,8 @@ msgstr "สด"
|
|||||||
msgid "Live Photos"
|
msgid "Live Photos"
|
||||||
msgstr "ภาพถ่าย"
|
msgstr "ภาพถ่าย"
|
||||||
|
|
||||||
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77
|
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78
|
||||||
#: src/options/admin.js:96
|
#: src/options/admin.js:97
|
||||||
msgid "Local"
|
msgid "Local"
|
||||||
msgstr "ท้องถิ่น"
|
msgstr "ท้องถิ่น"
|
||||||
|
|
||||||
@@ -1658,7 +1658,7 @@ msgstr "ไม่มีคำเตือนหรือข้อผิดพล
|
|||||||
msgid "Non-photographic and low-quality images require a review before they appear in search results."
|
msgid "Non-photographic and low-quality images require a review before they appear in search results."
|
||||||
msgstr "รูปภาพที่ไม่ใช่ภาพถ่ายและคุณภาพต่ำต้องได้รับการตรวจสอบก่อนที่จะปรากฏในผลการค้นหา"
|
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
|
#: src/options/options.js:293 src/options/options.js:389
|
||||||
msgid "None"
|
msgid "None"
|
||||||
msgstr "ไม่มี"
|
msgstr "ไม่มี"
|
||||||
@@ -2160,7 +2160,7 @@ msgstr "URL บริการ"
|
|||||||
msgid "Services"
|
msgid "Services"
|
||||||
msgstr "URL บริการ"
|
msgstr "URL บริการ"
|
||||||
|
|
||||||
#: src/model/session.js:98
|
#: src/model/session.js:98 src/options/admin.js:62
|
||||||
msgid "Session"
|
msgid "Session"
|
||||||
msgstr "การประชุม"
|
msgstr "การประชุม"
|
||||||
|
|
||||||
|
|||||||
@@ -661,8 +661,8 @@ msgstr "Hata Kayıtları"
|
|||||||
|
|
||||||
#: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100
|
#: 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/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:59 src/options/admin.js:60 src/options/admin.js:74
|
||||||
#: src/options/admin.js:92 src/options/options.js:313
|
#: 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
|
#: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142
|
||||||
msgid "Default"
|
msgid "Default"
|
||||||
msgstr "Varsayılan"
|
msgstr "Varsayılan"
|
||||||
@@ -1346,7 +1346,7 @@ msgstr "Son Senkronizasyon"
|
|||||||
msgid "Latitude"
|
msgid "Latitude"
|
||||||
msgstr "Enlem"
|
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"
|
msgid "LDAP/AD"
|
||||||
msgstr "LDAP/AD"
|
msgstr "LDAP/AD"
|
||||||
|
|
||||||
@@ -1411,8 +1411,8 @@ msgstr "Canlı"
|
|||||||
msgid "Live Photos"
|
msgid "Live Photos"
|
||||||
msgstr "Canlı Fotoğraflar"
|
msgstr "Canlı Fotoğraflar"
|
||||||
|
|
||||||
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77
|
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78
|
||||||
#: src/options/admin.js:96
|
#: src/options/admin.js:97
|
||||||
msgid "Local"
|
msgid "Local"
|
||||||
msgstr "Yerel"
|
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."
|
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."
|
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
|
#: src/options/options.js:293 src/options/options.js:389
|
||||||
msgid "None"
|
msgid "None"
|
||||||
msgstr "Hiçbiri"
|
msgstr "Hiçbiri"
|
||||||
@@ -2160,7 +2160,7 @@ msgstr "Hizmet URL'si"
|
|||||||
msgid "Services"
|
msgid "Services"
|
||||||
msgstr "Hizmetler"
|
msgstr "Hizmetler"
|
||||||
|
|
||||||
#: src/model/session.js:98
|
#: src/model/session.js:98 src/options/admin.js:62
|
||||||
msgid "Session"
|
msgid "Session"
|
||||||
msgstr "Oturum"
|
msgstr "Oturum"
|
||||||
|
|
||||||
|
|||||||
@@ -773,8 +773,8 @@ msgstr ""
|
|||||||
#: src/options/admin.js:45
|
#: src/options/admin.js:45
|
||||||
#: src/options/admin.js:59
|
#: src/options/admin.js:59
|
||||||
#: src/options/admin.js:60
|
#: src/options/admin.js:60
|
||||||
#: src/options/admin.js:73
|
#: src/options/admin.js:74
|
||||||
#: src/options/admin.js:92
|
#: src/options/admin.js:93
|
||||||
#: src/options/options.js:313
|
#: src/options/options.js:313
|
||||||
#: src/options/options.js:377
|
#: src/options/options.js:377
|
||||||
#: src/options/themes.js:492
|
#: src/options/themes.js:492
|
||||||
@@ -1579,7 +1579,7 @@ msgid "Latitude"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/options/admin.js:49
|
#: src/options/admin.js:49
|
||||||
#: src/options/admin.js:81
|
#: src/options/admin.js:82
|
||||||
msgid "LDAP/AD"
|
msgid "LDAP/AD"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1664,8 +1664,8 @@ msgstr ""
|
|||||||
|
|
||||||
#: src/options/admin.js:46
|
#: src/options/admin.js:46
|
||||||
#: src/options/admin.js:48
|
#: src/options/admin.js:48
|
||||||
#: src/options/admin.js:77
|
#: src/options/admin.js:78
|
||||||
#: src/options/admin.js:96
|
#: src/options/admin.js:97
|
||||||
msgid "Local"
|
msgid "Local"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1977,8 +1977,8 @@ msgid "Non-photographic and low-quality images require a review before they appe
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/options/admin.js:52
|
#: src/options/admin.js:52
|
||||||
#: src/options/admin.js:85
|
#: src/options/admin.js:86
|
||||||
#: src/options/admin.js:100
|
#: src/options/admin.js:101
|
||||||
#: src/options/options.js:293
|
#: src/options/options.js:293
|
||||||
#: src/options/options.js:389
|
#: src/options/options.js:389
|
||||||
msgid "None"
|
msgid "None"
|
||||||
@@ -2564,6 +2564,7 @@ msgid "Services"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/model/session.js:98
|
#: src/model/session.js:98
|
||||||
|
#: src/options/admin.js:62
|
||||||
msgid "Session"
|
msgid "Session"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
@@ -661,8 +661,8 @@ msgstr "Журнали налагодження"
|
|||||||
|
|
||||||
#: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100
|
#: 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/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:59 src/options/admin.js:60 src/options/admin.js:74
|
||||||
#: src/options/admin.js:92 src/options/options.js:313
|
#: 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
|
#: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142
|
||||||
msgid "Default"
|
msgid "Default"
|
||||||
msgstr "За замовчуванням"
|
msgstr "За замовчуванням"
|
||||||
@@ -1346,7 +1346,7 @@ msgstr "Остання синхронізація"
|
|||||||
msgid "Latitude"
|
msgid "Latitude"
|
||||||
msgstr "Широта"
|
msgstr "Широта"
|
||||||
|
|
||||||
#: src/options/admin.js:49 src/options/admin.js:81
|
#: src/options/admin.js:49 src/options/admin.js:82
|
||||||
msgid "LDAP/AD"
|
msgid "LDAP/AD"
|
||||||
msgstr "LDAP/AD"
|
msgstr "LDAP/AD"
|
||||||
|
|
||||||
@@ -1411,8 +1411,8 @@ msgstr "Live фото"
|
|||||||
msgid "Live Photos"
|
msgid "Live Photos"
|
||||||
msgstr "Живі фото"
|
msgstr "Живі фото"
|
||||||
|
|
||||||
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77
|
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78
|
||||||
#: src/options/admin.js:96
|
#: src/options/admin.js:97
|
||||||
msgid "Local"
|
msgid "Local"
|
||||||
msgstr "Місцевий"
|
msgstr "Місцевий"
|
||||||
|
|
||||||
@@ -1658,7 +1658,7 @@ msgstr "Немає попереджень або помилок із цим кл
|
|||||||
msgid "Non-photographic and low-quality images require a review before they appear in search results."
|
msgid "Non-photographic and low-quality images require a review before they appear in search results."
|
||||||
msgstr "Нефотографічні та низькоякісні зображення потребують перевірки, перш ніж з’являться в результатах пошуку."
|
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
|
#: src/options/options.js:293 src/options/options.js:389
|
||||||
msgid "None"
|
msgid "None"
|
||||||
msgstr "Жодного"
|
msgstr "Жодного"
|
||||||
@@ -2160,7 +2160,7 @@ msgstr "URL служби"
|
|||||||
msgid "Services"
|
msgid "Services"
|
||||||
msgstr "Послуги"
|
msgstr "Послуги"
|
||||||
|
|
||||||
#: src/model/session.js:98
|
#: src/model/session.js:98 src/options/admin.js:62
|
||||||
msgid "Session"
|
msgid "Session"
|
||||||
msgstr "Сесія"
|
msgstr "Сесія"
|
||||||
|
|
||||||
|
|||||||
@@ -661,8 +661,8 @@ msgstr "调试日志"
|
|||||||
|
|
||||||
#: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100
|
#: 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/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:59 src/options/admin.js:60 src/options/admin.js:74
|
||||||
#: src/options/admin.js:92 src/options/options.js:313
|
#: 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
|
#: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142
|
||||||
msgid "Default"
|
msgid "Default"
|
||||||
msgstr "默认"
|
msgstr "默认"
|
||||||
@@ -1346,7 +1346,7 @@ msgstr "上次同步"
|
|||||||
msgid "Latitude"
|
msgid "Latitude"
|
||||||
msgstr "纬度"
|
msgstr "纬度"
|
||||||
|
|
||||||
#: src/options/admin.js:49 src/options/admin.js:81
|
#: src/options/admin.js:49 src/options/admin.js:82
|
||||||
msgid "LDAP/AD"
|
msgid "LDAP/AD"
|
||||||
msgstr "LDAP/AD"
|
msgstr "LDAP/AD"
|
||||||
|
|
||||||
@@ -1411,8 +1411,8 @@ msgstr "实况"
|
|||||||
msgid "Live Photos"
|
msgid "Live Photos"
|
||||||
msgstr "现场照片"
|
msgstr "现场照片"
|
||||||
|
|
||||||
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77
|
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78
|
||||||
#: src/options/admin.js:96
|
#: src/options/admin.js:97
|
||||||
msgid "Local"
|
msgid "Local"
|
||||||
msgstr "当地"
|
msgstr "当地"
|
||||||
|
|
||||||
@@ -1658,7 +1658,7 @@ msgstr "没有包含此关键字的警告或错误,请注意,搜索区分大
|
|||||||
msgid "Non-photographic and low-quality images require a review before they appear in search results."
|
msgid "Non-photographic and low-quality images require a review before they appear in search results."
|
||||||
msgstr "非照片和低质量图像出现在搜索结果中前需要进行审查。"
|
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
|
#: src/options/options.js:293 src/options/options.js:389
|
||||||
msgid "None"
|
msgid "None"
|
||||||
msgstr "无"
|
msgstr "无"
|
||||||
@@ -2162,7 +2162,7 @@ msgstr "服务 URL"
|
|||||||
msgid "Services"
|
msgid "Services"
|
||||||
msgstr "服务"
|
msgstr "服务"
|
||||||
|
|
||||||
#: src/model/session.js:98
|
#: src/model/session.js:98 src/options/admin.js:62
|
||||||
msgid "Session"
|
msgid "Session"
|
||||||
msgstr "会议"
|
msgstr "会议"
|
||||||
|
|
||||||
|
|||||||
@@ -661,8 +661,8 @@ msgstr "除錯紀錄"
|
|||||||
|
|
||||||
#: src/page/admin/sessions.vue:63 src/page/admin/users.vue:100
|
#: 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/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:59 src/options/admin.js:60 src/options/admin.js:74
|
||||||
#: src/options/admin.js:92 src/options/options.js:313
|
#: 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
|
#: src/options/options.js:377 src/options/themes.js:492 src/page/places.vue:142
|
||||||
msgid "Default"
|
msgid "Default"
|
||||||
msgstr "預設"
|
msgstr "預設"
|
||||||
@@ -1346,7 +1346,7 @@ msgstr "上次同步"
|
|||||||
msgid "Latitude"
|
msgid "Latitude"
|
||||||
msgstr "緯度"
|
msgstr "緯度"
|
||||||
|
|
||||||
#: src/options/admin.js:49 src/options/admin.js:81
|
#: src/options/admin.js:49 src/options/admin.js:82
|
||||||
msgid "LDAP/AD"
|
msgid "LDAP/AD"
|
||||||
msgstr "LDAP/AD"
|
msgstr "LDAP/AD"
|
||||||
|
|
||||||
@@ -1411,8 +1411,8 @@ msgstr "即時"
|
|||||||
msgid "Live Photos"
|
msgid "Live Photos"
|
||||||
msgstr "原況照片"
|
msgstr "原況照片"
|
||||||
|
|
||||||
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:77
|
#: src/options/admin.js:46 src/options/admin.js:48 src/options/admin.js:78
|
||||||
#: src/options/admin.js:96
|
#: src/options/admin.js:97
|
||||||
msgid "Local"
|
msgid "Local"
|
||||||
msgstr "当地"
|
msgstr "当地"
|
||||||
|
|
||||||
@@ -1658,7 +1658,7 @@ msgstr "沒有包含此關鍵字的警告或錯誤。請注意,搜尋區分大
|
|||||||
msgid "Non-photographic and low-quality images require a review before they appear in search results."
|
msgid "Non-photographic and low-quality images require a review before they appear in search results."
|
||||||
msgstr "非照片和低品質圖像需要進行手動確認,才會出現在搜尋結果中。"
|
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
|
#: src/options/options.js:293 src/options/options.js:389
|
||||||
msgid "None"
|
msgid "None"
|
||||||
msgstr "無"
|
msgstr "無"
|
||||||
@@ -2160,7 +2160,7 @@ msgstr "服務 URL"
|
|||||||
msgid "Services"
|
msgid "Services"
|
||||||
msgstr "服務"
|
msgstr "服務"
|
||||||
|
|
||||||
#: src/model/session.js:98
|
#: src/model/session.js:98 src/options/admin.js:62
|
||||||
msgid "Session"
|
msgid "Session"
|
||||||
msgstr "工作階段"
|
msgstr "工作階段"
|
||||||
|
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ export class Session extends RestModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static getCollectionResource() {
|
static getCollectionResource() {
|
||||||
return "session";
|
return "sessions";
|
||||||
}
|
}
|
||||||
|
|
||||||
static getModelName() {
|
static getModelName() {
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ export const AuthMethods = () => {
|
|||||||
"": $gettext("Default"),
|
"": $gettext("Default"),
|
||||||
default: $gettext("Default"),
|
default: $gettext("Default"),
|
||||||
access_token: $gettext("Access Token"),
|
access_token: $gettext("Access Token"),
|
||||||
|
session: $gettext("Session"),
|
||||||
"2fa": "2FA",
|
"2fa": "2FA",
|
||||||
oauth2: "OAuth2",
|
oauth2: "OAuth2",
|
||||||
oidc: "OIDC",
|
oidc: "OIDC",
|
||||||
|
|||||||
@@ -7,6 +7,14 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestACL_Allow(t *testing.T) {
|
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) {
|
t.Run("ResourcePhotosRoleAdminActionModify", func(t *testing.T) {
|
||||||
assert.True(t, Resources.Allow(ResourcePhotos, RoleAdmin, ActionUpdate))
|
assert.True(t, Resources.Allow(ResourcePhotos, RoleAdmin, ActionUpdate))
|
||||||
})
|
})
|
||||||
@@ -124,6 +132,6 @@ func TestACL_DenyAll(t *testing.T) {
|
|||||||
func TestACL_Resources(t *testing.T) {
|
func TestACL_Resources(t *testing.T) {
|
||||||
t.Run("Resources", func(t *testing.T) {
|
t.Run("Resources", func(t *testing.T) {
|
||||||
result := Resources.Resources()
|
result := Resources.Resources()
|
||||||
assert.Len(t, result, 21)
|
assert.Len(t, result, 22)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ const (
|
|||||||
ResourcePassword Resource = "password"
|
ResourcePassword Resource = "password"
|
||||||
ResourceServices Resource = "services"
|
ResourceServices Resource = "services"
|
||||||
ResourceUsers Resource = "users"
|
ResourceUsers Resource = "users"
|
||||||
|
ResourceSessions Resource = "sessions"
|
||||||
ResourceLogs Resource = "logs"
|
ResourceLogs Resource = "logs"
|
||||||
ResourceWebDAV Resource = "webdav"
|
ResourceWebDAV Resource = "webdav"
|
||||||
ResourceMetrics Resource = "metrics"
|
ResourceMetrics Resource = "metrics"
|
||||||
|
|||||||
@@ -61,7 +61,12 @@ var Resources = ACL{
|
|||||||
RoleAdmin: GrantFullAccess,
|
RoleAdmin: GrantFullAccess,
|
||||||
},
|
},
|
||||||
ResourceUsers: Roles{
|
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{
|
ResourceLogs: Roles{
|
||||||
RoleAdmin: GrantFullAccess,
|
RoleAdmin: GrantFullAccess,
|
||||||
|
|||||||
@@ -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
|
// AuthAny checks if the user is authorized to access a resource with any of the specified permissions
|
||||||
// and returns the session or nil otherwise.
|
// and returns the session or nil otherwise.
|
||||||
func AuthAny(c *gin.Context, resource acl.Resource, grants acl.Permissions) (s *entity.Session) {
|
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.
|
// Get client IP and auth token from the request headers.
|
||||||
ip := ClientIP(c)
|
clientIp := ClientIP(c)
|
||||||
authToken := AuthToken(c)
|
authToken := AuthToken(c)
|
||||||
|
|
||||||
// Find active session to perform authorization check or deny if no session was found.
|
// Find active session to perform authorization check or deny if no session was found.
|
||||||
if s = Session(authToken); s == nil {
|
if s = Session(clientIp, authToken); s == nil {
|
||||||
event.AuditWarn([]string{ip, "unauthenticated", "%s %s", "denied"}, grants.String(), string(resource))
|
event.AuditWarn([]string{clientIp, "unauthenticated", "%s %s", "denied"}, grants.String(), string(resource))
|
||||||
return entity.SessionStatusUnauthorized()
|
return entity.SessionStatusUnauthorized()
|
||||||
} else {
|
} else {
|
||||||
s.SetClientIP(ip)
|
s.SetClientIP(clientIp)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the request is from a client application, check its authorization based
|
// 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() {
|
if s.IsClient() {
|
||||||
// Check ACL resource name against the permitted scope.
|
// Check ACL resource name against the permitted scope.
|
||||||
if !s.HasScope(resource.String()) {
|
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
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
// Perform an authorization check based on the ACL defaults for client applications.
|
// Perform an authorization check based on the ACL defaults for client applications.
|
||||||
if acl.Resources.DenyAll(resource, acl.RoleClient, grants) {
|
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()
|
return entity.SessionStatusForbidden()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Additionally check the user authorization if the client belongs to a user account.
|
// Additionally check the user authorization if the client belongs to a user account.
|
||||||
if s.NoUser() {
|
if s.NoUser() {
|
||||||
// Allow access based on the ACL defaults for client applications.
|
// 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() {
|
} else if u := s.User(); !u.IsDisabled() && !u.IsUnknown() && u.IsRegistered() {
|
||||||
if acl.Resources.DenyAll(resource, u.AclRole(), grants) {
|
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()
|
return entity.SessionStatusForbidden()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allow access based on the user role.
|
// 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 {
|
} else {
|
||||||
// Deny access if it is not a regular user account or the account has been disabled.
|
// 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()
|
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.
|
// Otherwise, perform a regular ACL authorization check based on the user role.
|
||||||
if u := s.User(); u.IsUnknown() || u.IsDisabled() {
|
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()
|
return entity.SessionStatusUnauthorized()
|
||||||
} else if acl.Resources.DenyAll(resource, u.AclRole(), grants) {
|
} 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()
|
return entity.SessionStatusForbidden()
|
||||||
} else {
|
} 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
|
return s
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,13 +19,13 @@ func UpdateClientConfig() {
|
|||||||
// GET /api/v1/config
|
// GET /api/v1/config
|
||||||
func GetClientConfig(router *gin.RouterGroup) {
|
func GetClientConfig(router *gin.RouterGroup) {
|
||||||
router.GET("/config", func(c *gin.Context) {
|
router.GET("/config", func(c *gin.Context) {
|
||||||
s := Session(AuthToken(c))
|
sess := Session(ClientIP(c), AuthToken(c))
|
||||||
conf := get.Config()
|
conf := get.Config()
|
||||||
|
|
||||||
if s == nil {
|
if sess == nil {
|
||||||
c.JSON(http.StatusOK, conf.ClientPublic())
|
c.JSON(http.StatusOK, conf.ClientPublic())
|
||||||
} else {
|
} else {
|
||||||
c.JSON(http.StatusOK, conf.ClientSession(s))
|
c.JSON(http.StatusOK, conf.ClientSession(sess))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,9 +31,9 @@ func AddDownloadHeader(c *gin.Context, fileName string) {
|
|||||||
c.Header(header.ContentDisposition, fmt.Sprintf("attachment; filename=%s", fileName))
|
c.Header(header.ContentDisposition, fmt.Sprintf("attachment; filename=%s", fileName))
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddSessionHeader adds a session id header to the response.
|
// AddAuthTokenHeader adds an X-Auth-Token header to the response.
|
||||||
func AddSessionHeader(c *gin.Context, id string) {
|
func AddAuthTokenHeader(c *gin.Context, authToken string) {
|
||||||
c.Header(header.XSessionID, id)
|
c.Header(header.XAuthToken, authToken)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddContentTypeHeader adds a content type header to the response.
|
// AddContentTypeHeader adds a content type header to the response.
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ func AuthenticateUser(app *gin.Engine, router *gin.RouterGroup, name string, pas
|
|||||||
Password: password,
|
Password: password,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
authToken = r.Header().Get(header.XSessionID)
|
authToken = r.Header().Get(header.XAuthToken)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,22 +3,33 @@ package api
|
|||||||
import (
|
import (
|
||||||
"github.com/photoprism/photoprism/internal/entity"
|
"github.com/photoprism/photoprism/internal/entity"
|
||||||
"github.com/photoprism/photoprism/internal/get"
|
"github.com/photoprism/photoprism/internal/get"
|
||||||
|
"github.com/photoprism/photoprism/internal/server/limiter"
|
||||||
"github.com/photoprism/photoprism/pkg/rnd"
|
"github.com/photoprism/photoprism/pkg/rnd"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Session finds the client session for the specified
|
// Session finds the client session for the specified auth token, or returns nil if not found.
|
||||||
// auth token, or returns nil if not found.
|
func Session(clientIp, authToken string) *entity.Session {
|
||||||
func Session(authToken string) *entity.Session {
|
|
||||||
// Skip authentication when running in public mode.
|
// Skip authentication when running in public mode.
|
||||||
if get.Config().Public() {
|
if get.Config().Public() {
|
||||||
return get.Session().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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the session based on the hashed auth
|
// Fail if authentication error rate limit is exceeded.
|
||||||
// token used as id, or return nil otherwise.
|
if clientIp != "" && limiter.Auth.Reject(clientIp) {
|
||||||
if s, err := get.Session().Get(rnd.SessionID(authToken)); err != nil {
|
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
|
return nil
|
||||||
} else {
|
} else {
|
||||||
return s
|
return s
|
||||||
|
|||||||
@@ -11,17 +11,24 @@ import (
|
|||||||
"github.com/photoprism/photoprism/internal/get"
|
"github.com/photoprism/photoprism/internal/get"
|
||||||
"github.com/photoprism/photoprism/internal/i18n"
|
"github.com/photoprism/photoprism/internal/i18n"
|
||||||
"github.com/photoprism/photoprism/internal/server/limiter"
|
"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.
|
// CreateSession creates a new client session and returns it as JSON if authentication was successful.
|
||||||
//
|
//
|
||||||
// POST /api/v1/session
|
// POST /api/v1/session
|
||||||
|
// POST /api/v1/sessions
|
||||||
func CreateSession(router *gin.RouterGroup) {
|
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
|
var f form.Login
|
||||||
|
|
||||||
|
clientIp := ClientIP(c)
|
||||||
|
|
||||||
if err := c.BindJSON(&f); err != nil {
|
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)
|
AbortBadRequest(c)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -40,8 +47,8 @@ func CreateSession(router *gin.RouterGroup) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check limit for failed auth requests (max. 10 per minute).
|
// Fail if authentication error rate limit is exceeded.
|
||||||
if limiter.Login.Reject(ClientIP(c)) {
|
if clientIp != "" && (limiter.Login.Reject(clientIp) || limiter.Auth.Reject(clientIp)) {
|
||||||
limiter.AbortJSON(c)
|
limiter.AbortJSON(c)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -50,7 +57,7 @@ func CreateSession(router *gin.RouterGroup) {
|
|||||||
var isNew bool
|
var isNew bool
|
||||||
|
|
||||||
// Find existing session, if any.
|
// Find existing session, if any.
|
||||||
if s := Session(AuthToken(c)); s != nil {
|
if s := Session(clientIp, AuthToken(c)); s != nil {
|
||||||
// Update existing session.
|
// Update existing session.
|
||||||
sess = s
|
sess = s
|
||||||
} else {
|
} else {
|
||||||
@@ -64,25 +71,28 @@ func CreateSession(router *gin.RouterGroup) {
|
|||||||
c.AbortWithStatusJSON(sess.HttpStatus(), gin.H{"error": i18n.Msg(i18n.ErrInvalidCredentials)})
|
c.AbortWithStatusJSON(sess.HttpStatus(), gin.H{"error": i18n.Msg(i18n.ErrInvalidCredentials)})
|
||||||
return
|
return
|
||||||
} else if sess, err = get.Session().Save(sess); err != nil {
|
} 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)})
|
c.AbortWithStatusJSON(sess.HttpStatus(), gin.H{"error": i18n.Msg(i18n.ErrInvalidCredentials)})
|
||||||
return
|
return
|
||||||
} else if sess == nil {
|
} else if sess == nil {
|
||||||
c.AbortWithStatusJSON(sess.HttpStatus(), gin.H{"error": i18n.Msg(i18n.ErrUnexpected)})
|
c.AbortWithStatusJSON(sess.HttpStatus(), gin.H{"error": i18n.Msg(i18n.ErrUnexpected)})
|
||||||
return
|
return
|
||||||
} else if isNew {
|
} else if isNew {
|
||||||
event.AuditInfo([]string{ClientIP(c), "session %s", "created"}, sess.RefID)
|
event.AuditInfo([]string{clientIp, "session %s", "created"}, sess.RefID)
|
||||||
} else {
|
} 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.
|
// Add auth token to response header.
|
||||||
AddSessionHeader(c, sess.AuthToken())
|
AddAuthTokenHeader(c, sess.AuthToken())
|
||||||
|
|
||||||
// Response includes user data, session data, and client config values.
|
// Response includes user data, session data, and client config values.
|
||||||
response := CreateSessionResponse(sess.AuthToken(), sess, conf.ClientSession(sess))
|
response := CreateSessionResponse(sess.AuthToken(), sess, conf.ClientSession(sess))
|
||||||
|
|
||||||
// Return JSON response.
|
// Return JSON response.
|
||||||
c.JSON(sess.HttpStatus(), response)
|
c.JSON(sess.HttpStatus(), response)
|
||||||
})
|
}
|
||||||
|
|
||||||
|
router.POST("/session", createSessionHandler)
|
||||||
|
router.POST("/sessions", createSessionHandler)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,8 +10,10 @@ import (
|
|||||||
"github.com/photoprism/photoprism/internal/event"
|
"github.com/photoprism/photoprism/internal/event"
|
||||||
"github.com/photoprism/photoprism/internal/get"
|
"github.com/photoprism/photoprism/internal/get"
|
||||||
"github.com/photoprism/photoprism/internal/i18n"
|
"github.com/photoprism/photoprism/internal/i18n"
|
||||||
|
"github.com/photoprism/photoprism/internal/server/limiter"
|
||||||
"github.com/photoprism/photoprism/internal/session"
|
"github.com/photoprism/photoprism/internal/session"
|
||||||
"github.com/photoprism/photoprism/pkg/clean"
|
"github.com/photoprism/photoprism/pkg/clean"
|
||||||
|
"github.com/photoprism/photoprism/pkg/header"
|
||||||
"github.com/photoprism/photoprism/pkg/rnd"
|
"github.com/photoprism/photoprism/pkg/rnd"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -19,8 +21,12 @@ import (
|
|||||||
//
|
//
|
||||||
// DELETE /api/v1/session
|
// DELETE /api/v1/session
|
||||||
// DELETE /api/v1/session/:id
|
// DELETE /api/v1/session/:id
|
||||||
|
// DELETE /api/v1/sessions/:id
|
||||||
func DeleteSession(router *gin.RouterGroup) {
|
func DeleteSession(router *gin.RouterGroup) {
|
||||||
deleteSessionHandler := func(c *gin.Context) {
|
deleteSessionHandler := func(c *gin.Context) {
|
||||||
|
// Disable caching of responses.
|
||||||
|
c.Header(header.CacheControl, header.CacheControlNoStore)
|
||||||
|
|
||||||
// Abort if running in public mode.
|
// Abort if running in public mode.
|
||||||
if get.Config().Public() {
|
if get.Config().Public() {
|
||||||
// Return JSON response for confirmation.
|
// Return JSON response for confirmation.
|
||||||
@@ -30,13 +36,23 @@ func DeleteSession(router *gin.RouterGroup) {
|
|||||||
|
|
||||||
id := clean.ID(c.Param("id"))
|
id := clean.ID(c.Param("id"))
|
||||||
|
|
||||||
// Get client IP address for logs and rate limiting checks.
|
// Get client IP and auth token from request headers.
|
||||||
clientIP := ClientIP(c)
|
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.
|
// 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 err != nil || sess == nil {
|
||||||
|
if clientIp != "" {
|
||||||
|
limiter.Auth.Reserve(clientIp)
|
||||||
|
}
|
||||||
Abort(c, http.StatusUnauthorized, i18n.ErrUnauthorized)
|
Abort(c, http.StatusUnauthorized, i18n.ErrUnauthorized)
|
||||||
return
|
return
|
||||||
} else if sess.Abort(c) {
|
} else if sess.Abort(c) {
|
||||||
@@ -45,29 +61,29 @@ func DeleteSession(router *gin.RouterGroup) {
|
|||||||
|
|
||||||
// Only admins may delete other sessions by ref id.
|
// Only admins may delete other sessions by ref id.
|
||||||
if rnd.IsRefID(id) {
|
if rnd.IsRefID(id) {
|
||||||
if !acl.Resources.AllowAll(acl.ResourceUsers, sess.User().AclRole(), acl.Permissions{acl.AccessAll, acl.ActionManage}) {
|
if !acl.Resources.AllowAll(acl.ResourceSessions, 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())
|
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)
|
Abort(c, http.StatusForbidden, i18n.ErrForbidden)
|
||||||
return
|
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 {
|
if sess = entity.FindSessionByRefID(id); sess == nil {
|
||||||
Abort(c, http.StatusNotFound, i18n.ErrNotFound)
|
Abort(c, http.StatusNotFound, i18n.ErrNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else if id != "" && sess.ID != id {
|
} 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)
|
Abort(c, http.StatusForbidden, i18n.ErrForbidden)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete session cache and database record.
|
// Delete session cache and database record.
|
||||||
if err = sess.Delete(); err != nil {
|
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 {
|
} else {
|
||||||
event.AuditDebug([]string{clientIP, "session %s", "deleted"}, sess.RefID)
|
event.AuditDebug([]string{clientIp, "session %s", "deleted"}, sess.RefID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return JSON response for confirmation.
|
// Return JSON response for confirmation.
|
||||||
@@ -76,4 +92,5 @@ func DeleteSession(router *gin.RouterGroup) {
|
|||||||
|
|
||||||
router.DELETE("/session", deleteSessionHandler)
|
router.DELETE("/session", deleteSessionHandler)
|
||||||
router.DELETE("/session/:id", deleteSessionHandler)
|
router.DELETE("/session/:id", deleteSessionHandler)
|
||||||
|
router.DELETE("/sessions/:id", deleteSessionHandler)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,24 +7,34 @@ import (
|
|||||||
|
|
||||||
"github.com/photoprism/photoprism/internal/entity"
|
"github.com/photoprism/photoprism/internal/entity"
|
||||||
"github.com/photoprism/photoprism/internal/get"
|
"github.com/photoprism/photoprism/internal/get"
|
||||||
|
"github.com/photoprism/photoprism/internal/server/limiter"
|
||||||
"github.com/photoprism/photoprism/pkg/clean"
|
"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.
|
// GetSession returns the session data as JSON if authentication was successful.
|
||||||
//
|
//
|
||||||
|
// GET /api/v1/session
|
||||||
// GET /api/v1/session/:id
|
// GET /api/v1/session/:id
|
||||||
|
// GET /api/v1/sessions/:id
|
||||||
func GetSession(router *gin.RouterGroup) {
|
func GetSession(router *gin.RouterGroup) {
|
||||||
getSessionHandler := func(c *gin.Context) {
|
getSessionHandler := func(c *gin.Context) {
|
||||||
|
// Disable caching of responses.
|
||||||
|
c.Header(header.CacheControl, header.CacheControlNoStore)
|
||||||
|
|
||||||
id := clean.ID(c.Param("id"))
|
id := clean.ID(c.Param("id"))
|
||||||
|
|
||||||
// Check authentication token.
|
// Abort if session id is provided but invalid.
|
||||||
if id == "" {
|
if id != "" && !rnd.IsSessionID(id) {
|
||||||
// Abort if authentication token is missing or empty.
|
|
||||||
AbortBadRequest(c)
|
AbortBadRequest(c)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
conf := get.Config()
|
conf := get.Config()
|
||||||
|
|
||||||
|
// Get client IP and auth token from request headers.
|
||||||
|
clientIp := ClientIP(c)
|
||||||
authToken := AuthToken(c)
|
authToken := AuthToken(c)
|
||||||
|
|
||||||
// Skip authentication if app is running in public mode.
|
// Skip authentication if app is running in public mode.
|
||||||
@@ -33,18 +43,25 @@ func GetSession(router *gin.RouterGroup) {
|
|||||||
sess = get.Session().Public()
|
sess = get.Session().Public()
|
||||||
id = sess.ID
|
id = sess.ID
|
||||||
authToken = sess.AuthToken()
|
authToken = sess.AuthToken()
|
||||||
|
} else if clientIp != "" && limiter.Auth.Reject(clientIp) {
|
||||||
|
// Fail if authentication error rate limit is exceeded.
|
||||||
|
limiter.AbortJSON(c)
|
||||||
|
return
|
||||||
} else {
|
} else {
|
||||||
sess = Session(authToken)
|
sess = Session(clientIp, authToken)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case sess == nil:
|
case sess == nil:
|
||||||
|
if clientIp != "" {
|
||||||
|
limiter.Auth.Reserve(clientIp)
|
||||||
|
}
|
||||||
AbortUnauthorized(c)
|
AbortUnauthorized(c)
|
||||||
return
|
return
|
||||||
case sess.Expired(), sess.ID == "":
|
case sess.Expired(), sess.ID == "":
|
||||||
AbortUnauthorized(c)
|
AbortUnauthorized(c)
|
||||||
return
|
return
|
||||||
case sess.Invalid(), sess.ID != id && !conf.Public():
|
case sess.Invalid(), id != "" && sess.ID != id && !conf.Public():
|
||||||
AbortForbidden(c)
|
AbortForbidden(c)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -52,8 +69,8 @@ func GetSession(router *gin.RouterGroup) {
|
|||||||
// Update user information.
|
// Update user information.
|
||||||
sess.RefreshUser()
|
sess.RefreshUser()
|
||||||
|
|
||||||
// Add session id to response headers.
|
// Add auth token to response header.
|
||||||
AddSessionHeader(c, authToken)
|
AddAuthTokenHeader(c, authToken)
|
||||||
|
|
||||||
// Response includes user data, session data, and client config values.
|
// Response includes user data, session data, and client config values.
|
||||||
response := GetSessionResponse(authToken, sess, get.Config().ClientSession(sess))
|
response := GetSessionResponse(authToken, sess, get.Config().ClientSession(sess))
|
||||||
@@ -62,5 +79,7 @@ func GetSession(router *gin.RouterGroup) {
|
|||||||
c.JSON(http.StatusOK, response)
|
c.JSON(http.StatusOK, response)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
router.GET("/session", getSessionHandler)
|
||||||
router.GET("/session/:id", getSessionHandler)
|
router.GET("/session/:id", getSessionHandler)
|
||||||
|
router.GET("/sessions/:id", getSessionHandler)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,12 +25,15 @@ import (
|
|||||||
// POST /api/v1/oauth/token
|
// POST /api/v1/oauth/token
|
||||||
func CreateOAuthToken(router *gin.RouterGroup) {
|
func CreateOAuthToken(router *gin.RouterGroup) {
|
||||||
router.POST("/oauth/token", func(c *gin.Context) {
|
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.
|
// Get client IP address for logs and rate limiting checks.
|
||||||
clientIP := ClientIP(c)
|
clientIp := ClientIP(c)
|
||||||
|
|
||||||
// Abort if running in public mode.
|
// Abort if running in public mode.
|
||||||
if get.Config().Public() {
|
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)
|
Abort(c, http.StatusForbidden, i18n.ErrForbidden)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -45,20 +48,20 @@ func CreateOAuthToken(router *gin.RouterGroup) {
|
|||||||
f.ClientID = clientId
|
f.ClientID = clientId
|
||||||
f.ClientSecret = clientSecret
|
f.ClientSecret = clientSecret
|
||||||
} else if err = c.ShouldBind(&f); err != nil {
|
} 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)
|
AbortBadRequest(c)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check the credentials for completeness and the correct format.
|
// Check the credentials for completeness and the correct format.
|
||||||
if err = f.Validate(); err != nil {
|
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)})
|
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": i18n.Msg(i18n.ErrInvalidCredentials)})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check limit for failed auth requests (max. 10 per minute).
|
// Fail if authentication error rate limit is exceeded.
|
||||||
if limiter.Login.Reject(clientIP) {
|
if clientIp != "" && (limiter.Login.Reject(clientIp) || limiter.Auth.Reject(clientIp)) {
|
||||||
limiter.AbortJSON(c)
|
limiter.AbortJSON(c)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -68,22 +71,22 @@ func CreateOAuthToken(router *gin.RouterGroup) {
|
|||||||
|
|
||||||
// Abort if the client ID or secret are invalid.
|
// Abort if the client ID or secret are invalid.
|
||||||
if client == nil {
|
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)})
|
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": i18n.Msg(i18n.ErrInvalidCredentials)})
|
||||||
limiter.Login.Reserve(clientIP)
|
limiter.Login.Reserve(clientIp)
|
||||||
return
|
return
|
||||||
} else if !client.AuthEnabled {
|
} 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)})
|
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": i18n.Msg(i18n.ErrInvalidCredentials)})
|
||||||
return
|
return
|
||||||
} else if method := client.Method(); !method.IsDefault() && method != authn.MethodOAuth2 {
|
} 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)})
|
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": i18n.Msg(i18n.ErrInvalidCredentials)})
|
||||||
return
|
return
|
||||||
} else if client.WrongSecret(f.ClientSecret) {
|
} 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)})
|
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": i18n.Msg(i18n.ErrInvalidCredentials)})
|
||||||
limiter.Login.Reserve(clientIP)
|
limiter.Login.Reserve(clientIp)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,20 +95,20 @@ func CreateOAuthToken(router *gin.RouterGroup) {
|
|||||||
|
|
||||||
// Try to log in and save session if successful.
|
// Try to log in and save session if successful.
|
||||||
if sess, err = get.Session().Save(sess); err != nil {
|
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)})
|
c.AbortWithStatusJSON(sess.HttpStatus(), gin.H{"error": i18n.Msg(i18n.ErrInvalidCredentials)})
|
||||||
return
|
return
|
||||||
} else if sess == nil {
|
} 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)})
|
c.AbortWithStatusJSON(sess.HttpStatus(), gin.H{"error": i18n.Msg(i18n.ErrUnexpected)})
|
||||||
return
|
return
|
||||||
} else {
|
} 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.
|
// Deletes old client sessions above the configured limit.
|
||||||
if deleted := client.EnforceAuthTokenLimit(); deleted > 0 {
|
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.
|
// Response includes access token, token type, and token lifetime.
|
||||||
@@ -125,12 +128,15 @@ func CreateOAuthToken(router *gin.RouterGroup) {
|
|||||||
// POST /api/v1/oauth/revoke
|
// POST /api/v1/oauth/revoke
|
||||||
func RevokeOAuthToken(router *gin.RouterGroup) {
|
func RevokeOAuthToken(router *gin.RouterGroup) {
|
||||||
router.POST("/oauth/revoke", func(c *gin.Context) {
|
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.
|
// Get client IP address for logs and rate limiting checks.
|
||||||
clientIP := ClientIP(c)
|
clientIp := ClientIP(c)
|
||||||
|
|
||||||
// Abort if running in public mode.
|
// Abort if running in public mode.
|
||||||
if get.Config().Public() {
|
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)
|
Abort(c, http.StatusForbidden, i18n.ErrForbidden)
|
||||||
return
|
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.
|
// Get the auth token to be revoked from the submitted form values or the request header.
|
||||||
if err = c.ShouldBind(&f); err != nil && authToken == "" {
|
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)
|
AbortBadRequest(c)
|
||||||
return
|
return
|
||||||
} else if f.Empty() {
|
} else if f.Empty() {
|
||||||
@@ -154,7 +160,7 @@ func RevokeOAuthToken(router *gin.RouterGroup) {
|
|||||||
|
|
||||||
// Check the token form values.
|
// Check the token form values.
|
||||||
if err = f.Validate(); err != nil {
|
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)
|
AbortBadRequest(c)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -163,28 +169,28 @@ func RevokeOAuthToken(router *gin.RouterGroup) {
|
|||||||
sess, err := entity.FindSession(rnd.SessionID(f.AuthToken))
|
sess, err := entity.FindSession(rnd.SessionID(f.AuthToken))
|
||||||
|
|
||||||
if err != nil {
|
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))
|
c.AbortWithStatusJSON(http.StatusUnauthorized, i18n.NewResponse(http.StatusUnauthorized, i18n.ErrUnauthorized))
|
||||||
return
|
return
|
||||||
} else if sess == nil {
|
} 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))
|
c.AbortWithStatusJSON(http.StatusUnauthorized, i18n.NewResponse(http.StatusUnauthorized, i18n.ErrUnauthorized))
|
||||||
return
|
return
|
||||||
} else if sess.Abort(c) {
|
} 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
|
return
|
||||||
} else if !sess.IsClient() {
|
} 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))
|
c.AbortWithStatusJSON(http.StatusForbidden, i18n.NewResponse(http.StatusForbidden, i18n.ErrForbidden))
|
||||||
return
|
return
|
||||||
} else {
|
} 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.
|
// Delete session cache and database record.
|
||||||
if err = sess.Delete(); err != nil {
|
if err = sess.Delete(); err != nil {
|
||||||
// Log error.
|
// 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.
|
// Return JSON error.
|
||||||
c.AbortWithStatusJSON(http.StatusNotFound, i18n.NewResponse(http.StatusNotFound, i18n.ErrNotFound))
|
c.AbortWithStatusJSON(http.StatusNotFound, i18n.NewResponse(http.StatusNotFound, i18n.ErrNotFound))
|
||||||
@@ -192,7 +198,7 @@ func RevokeOAuthToken(router *gin.RouterGroup) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Log event.
|
// 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.
|
// Return JSON response for confirmation.
|
||||||
c.JSON(http.StatusOK, DeleteSessionResponse(sess.ID))
|
c.JSON(http.StatusOK, DeleteSessionResponse(sess.ID))
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ import (
|
|||||||
func TestSession(t *testing.T) {
|
func TestSession(t *testing.T) {
|
||||||
t.Run("Public", func(t *testing.T) {
|
t.Run("Public", func(t *testing.T) {
|
||||||
sess := get.Session().Public()
|
sess := get.Session().Public()
|
||||||
assert.Equal(t, sess, Session(""))
|
assert.Equal(t, sess, Session("1.2.3.4", ""))
|
||||||
assert.Equal(t, sess, Session("638bffc9b86a8fda0d908ebee84a43930cb8d1e3507f4aa0"))
|
assert.Equal(t, sess, Session("1.2.3.4", "1234ffc9b86a8fda0d908ebee84a43930cb8d1e3507f4aa0"))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,13 +213,43 @@ func TestGetSession(t *testing.T) {
|
|||||||
GetSession(router)
|
GetSession(router)
|
||||||
authToken := AuthenticateAdmin(app, 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)
|
r := AuthenticatedRequest(app, http.MethodGet, "/api/v1/session/"+rnd.SessionID(authToken), authToken)
|
||||||
t.Logf("Response Body: %s", r.Body.String())
|
t.Logf("Response Body: %s", r.Body.String())
|
||||||
id := gjson.Get(r.Body.String(), "session_id").String()
|
id := gjson.Get(r.Body.String(), "session_id").String()
|
||||||
assert.Equal(t, rnd.SessionID(authToken), id)
|
assert.Equal(t, rnd.SessionID(authToken), id)
|
||||||
assert.Equal(t, http.StatusOK, r.Code)
|
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) {
|
func TestDeleteSession(t *testing.T) {
|
||||||
@@ -231,11 +261,8 @@ func TestDeleteSession(t *testing.T) {
|
|||||||
DeleteSession(router)
|
DeleteSession(router)
|
||||||
authToken := AuthenticateAdmin(app, router)
|
authToken := AuthenticateAdmin(app, router)
|
||||||
|
|
||||||
// f9ae12e95a01bcc7faae6497124cd721eaf13c1dad301dbc
|
r := AuthenticatedRequest(app, http.MethodDelete, "/api/v1/session/"+rnd.SessionID(authToken), "")
|
||||||
t.Logf("authToken: %s", authToken)
|
assert.Equal(t, http.StatusUnauthorized, r.Code)
|
||||||
|
|
||||||
r := AuthenticatedRequest(app, http.MethodDelete, "/api/v1/session/"+rnd.SessionID(authToken), authToken)
|
|
||||||
assert.Equal(t, http.StatusOK, r.Code)
|
|
||||||
})
|
})
|
||||||
t.Run("AdminAuthenticatedRequest", func(t *testing.T) {
|
t.Run("AdminAuthenticatedRequest", func(t *testing.T) {
|
||||||
app, router, conf := NewApiTest()
|
app, router, conf := NewApiTest()
|
||||||
@@ -245,9 +272,31 @@ func TestDeleteSession(t *testing.T) {
|
|||||||
DeleteSession(router)
|
DeleteSession(router)
|
||||||
authToken := AuthenticateAdmin(app, 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)
|
r := AuthenticatedRequest(app, http.MethodDelete, "/api/v1/session/"+rnd.SessionID(authToken), authToken)
|
||||||
assert.Equal(t, http.StatusOK, r.Code)
|
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) {
|
t.Run("AdminAuthenticatedLogout", func(t *testing.T) {
|
||||||
app, router, conf := NewApiTest()
|
app, router, conf := NewApiTest()
|
||||||
conf.SetAuthMode(config.AuthModePasswd)
|
conf.SetAuthMode(config.AuthModePasswd)
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ func wsReader(ws *websocket.Conn, writeMutex *sync.Mutex, connId string, conf *c
|
|||||||
if jsonErr := json.Unmarshal(m, &info); jsonErr != nil {
|
if jsonErr := json.Unmarshal(m, &info); jsonErr != nil {
|
||||||
// Do nothing.
|
// Do nothing.
|
||||||
} else {
|
} else {
|
||||||
if s := Session(info.AuthToken); s != nil {
|
if s := Session(ws.RemoteAddr().String(), info.AuthToken); s != nil {
|
||||||
wsAuth.mutex.Lock()
|
wsAuth.mutex.Lock()
|
||||||
wsAuth.sid[connId] = s.ID
|
wsAuth.sid[connId] = s.ID
|
||||||
wsAuth.rid[connId] = s.RefID
|
wsAuth.rid[connId] = s.RefID
|
||||||
|
|||||||
@@ -154,6 +154,11 @@ func SessionStatusForbidden() *Session {
|
|||||||
return &Session{Status: http.StatusForbidden}
|
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.
|
// FindSessionByRefID finds an existing session by ref ID.
|
||||||
func FindSessionByRefID(refId string) *Session {
|
func FindSessionByRefID(refId string) *Session {
|
||||||
if !rnd.IsRefID(refId) {
|
if !rnd.IsRefID(refId) {
|
||||||
@@ -340,9 +345,15 @@ func (m *Session) AuthInfo() string {
|
|||||||
return fmt.Sprintf("%s (%s)", provider.Pretty(), method.Pretty())
|
return fmt.Sprintf("%s (%s)", provider.Pretty(), method.Pretty())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Provider returns the authentication provider.
|
// SetAuthID sets a custom authentication identifier.
|
||||||
func (m *Session) Provider() authn.ProviderType {
|
func (m *Session) SetAuthID(id string) *Session {
|
||||||
return authn.Provider(m.AuthProvider)
|
if id == "" {
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
m.AuthID = clean.Name(id)
|
||||||
|
|
||||||
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
// Method returns the authentication method.
|
// Method returns the authentication method.
|
||||||
@@ -350,9 +361,20 @@ func (m *Session) Method() authn.MethodType {
|
|||||||
return authn.Method(m.AuthMethod)
|
return authn.Method(m.AuthMethod)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsClient checks whether this session is used to authenticate an API client.
|
// SetMethod sets a custom authentication method.
|
||||||
func (m *Session) IsClient() bool {
|
func (m *Session) SetMethod(method authn.MethodType) *Session {
|
||||||
return authn.Provider(m.AuthProvider).IsClient()
|
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.
|
// SetProvider updates the session's authentication provider.
|
||||||
@@ -366,6 +388,11 @@ func (m *Session) SetProvider(provider authn.ProviderType) *Session {
|
|||||||
return m
|
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.
|
// ChangePassword changes the password of the current user.
|
||||||
func (m *Session) ChangePassword(newPw string) (err error) {
|
func (m *Session) ChangePassword(newPw string) (err error) {
|
||||||
u := m.User()
|
u := m.User()
|
||||||
@@ -465,8 +492,8 @@ func (m *Session) SetContext(c *gin.Context) *Session {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set client ip address from request context.
|
// Set client ip address from request context.
|
||||||
if ip := header.ClientIP(c); ip != "" {
|
if clientIp := header.ClientIP(c); clientIp != "" {
|
||||||
m.SetClientIP(ip)
|
m.SetClientIP(clientIp)
|
||||||
} else if m.ClientIP == "" {
|
} else if m.ClientIP == "" {
|
||||||
// Unit tests often do not set a client IP.
|
// Unit tests often do not set a client IP.
|
||||||
m.SetClientIP(UnknownIP)
|
m.SetClientIP(UnknownIP)
|
||||||
@@ -489,8 +516,8 @@ func (m *Session) UpdateContext(c *gin.Context) *Session {
|
|||||||
changed := false
|
changed := false
|
||||||
|
|
||||||
// Set client ip address from request context.
|
// Set client ip address from request context.
|
||||||
if ip := header.ClientIP(c); ip != "" && (ip != m.ClientIP || m.LoginIP == "") {
|
if clientIp := header.ClientIP(c); clientIp != "" && (clientIp != m.ClientIP || m.LoginIP == "") {
|
||||||
m.SetClientIP(ip)
|
m.SetClientIP(clientIp)
|
||||||
changed = true
|
changed = true
|
||||||
} else if m.ClientIP == "" {
|
} else if m.ClientIP == "" {
|
||||||
// Unit tests often do not set a client IP.
|
// Unit tests often do not set a client IP.
|
||||||
@@ -701,6 +728,8 @@ func (m *Session) Abort(c *gin.Context) bool {
|
|||||||
switch m.Status {
|
switch m.Status {
|
||||||
case http.StatusUnauthorized:
|
case http.StatusUnauthorized:
|
||||||
c.AbortWithStatusJSON(m.Status, i18n.NewResponse(m.Status, i18n.ErrUnauthorized))
|
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:
|
default:
|
||||||
c.AbortWithStatusJSON(http.StatusForbidden, i18n.NewResponse(http.StatusForbidden, i18n.ErrForbidden))
|
c.AbortWithStatusJSON(http.StatusForbidden, i18n.NewResponse(http.StatusForbidden, i18n.ErrForbidden))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,6 +50,21 @@ var SessionFixtures = SessionMap{
|
|||||||
UserUID: UserFixtures.Pointer("alice").UserUID,
|
UserUID: UserFixtures.Pointer("alice").UserUID,
|
||||||
UserName: UserFixtures.Pointer("alice").UserName,
|
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": {
|
"alice_token_webdav": {
|
||||||
authToken: "bHcZP-YxRbi-irKII-W1kpz",
|
authToken: "bHcZP-YxRbi-irKII-W1kpz",
|
||||||
ID: rnd.SessionID("bHcZP-YxRbi-irKII-W1kpz"),
|
ID: rnd.SessionID("bHcZP-YxRbi-irKII-W1kpz"),
|
||||||
|
|||||||
@@ -7,21 +7,28 @@ import (
|
|||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
|
"github.com/photoprism/photoprism/internal/acl"
|
||||||
"github.com/photoprism/photoprism/internal/event"
|
"github.com/photoprism/photoprism/internal/event"
|
||||||
"github.com/photoprism/photoprism/internal/form"
|
"github.com/photoprism/photoprism/internal/form"
|
||||||
"github.com/photoprism/photoprism/internal/i18n"
|
"github.com/photoprism/photoprism/internal/i18n"
|
||||||
"github.com/photoprism/photoprism/internal/server/limiter"
|
"github.com/photoprism/photoprism/internal/server/limiter"
|
||||||
"github.com/photoprism/photoprism/pkg/authn"
|
"github.com/photoprism/photoprism/pkg/authn"
|
||||||
"github.com/photoprism/photoprism/pkg/clean"
|
"github.com/photoprism/photoprism/pkg/clean"
|
||||||
|
"github.com/photoprism/photoprism/pkg/header"
|
||||||
|
"github.com/photoprism/photoprism/pkg/rnd"
|
||||||
"github.com/photoprism/photoprism/pkg/txt"
|
"github.com/photoprism/photoprism/pkg/txt"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Auth checks if the credentials are valid and returns the user and authentication provider.
|
// 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) {
|
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)
|
// Find registered user account.
|
||||||
err = AuthLocal(user, f, m)
|
user = FindUserByName(nameName)
|
||||||
|
|
||||||
|
// Try local authentication.
|
||||||
|
provider, err = AuthLocal(user, f, m, c)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return user, authn.ProviderNone, err
|
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.
|
// Update login timestamp.
|
||||||
user.UpdateLoginTime()
|
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.
|
// AuthLocal authenticates against the local user database with the specified username and password.
|
||||||
func AuthLocal(user *User, f form.Login, m *Session) (err error) {
|
func AuthLocal(user *User, f form.Login, m *Session, c *gin.Context) (authn.ProviderType, error) {
|
||||||
name := f.Username()
|
// 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 {
|
if user == nil {
|
||||||
message := "account not found"
|
message := "account not found"
|
||||||
if m != nil {
|
limiter.Login.Reserve(clientIp)
|
||||||
limiter.Login.Reserve(m.IP())
|
event.AuditWarn([]string{clientIp, "session %s", "login as %s", message}, m.RefID, clean.LogQuote(userName))
|
||||||
event.AuditWarn([]string{m.IP(), "session %s", "login as %s", message}, m.RefID, clean.LogQuote(name))
|
event.LoginError(clientIp, "api", userName, m.UserAgent, message)
|
||||||
event.LoginError(m.IP(), "api", name, m.UserAgent, message)
|
m.Status = http.StatusUnauthorized
|
||||||
m.Status = http.StatusUnauthorized
|
return authn.ProviderNone, i18n.Error(i18n.ErrInvalidCredentials)
|
||||||
}
|
|
||||||
return i18n.Error(i18n.ErrInvalidCredentials)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Login allowed?
|
// Login allowed?
|
||||||
if !user.Provider().IsDefault() && !user.Provider().IsLocal() {
|
if !user.Provider().IsDefault() && !user.Provider().IsLocal() {
|
||||||
message := fmt.Sprintf("%s authentication disabled", authn.ProviderLocal.String())
|
message := fmt.Sprintf("%s authentication disabled", authn.ProviderLocal.String())
|
||||||
if m != nil {
|
event.AuditWarn([]string{clientIp, "session %s", "login as %s", message}, m.RefID, clean.LogQuote(userName))
|
||||||
event.AuditWarn([]string{m.IP(), "session %s", "login as %s", message}, m.RefID, clean.LogQuote(name))
|
event.LoginError(clientIp, "api", userName, m.UserAgent, message)
|
||||||
event.LoginError(m.IP(), "api", name, m.UserAgent, message)
|
m.Status = http.StatusUnauthorized
|
||||||
m.Status = http.StatusUnauthorized
|
return authn.ProviderNone, i18n.Error(i18n.ErrInvalidCredentials)
|
||||||
}
|
|
||||||
return i18n.Error(i18n.ErrInvalidCredentials)
|
|
||||||
} else if !user.CanLogIn() {
|
} else if !user.CanLogIn() {
|
||||||
message := "account disabled"
|
message := "account disabled"
|
||||||
if m != nil {
|
event.AuditWarn([]string{clientIp, "session %s", "login as %s", message}, m.RefID, clean.LogQuote(userName))
|
||||||
event.AuditWarn([]string{m.IP(), "session %s", "login as %s", message}, m.RefID, clean.LogQuote(name))
|
event.LoginError(clientIp, "api", userName, m.UserAgent, message)
|
||||||
event.LoginError(m.IP(), "api", name, m.UserAgent, message)
|
m.Status = http.StatusUnauthorized
|
||||||
m.Status = http.StatusUnauthorized
|
return authn.ProviderNone, i18n.Error(i18n.ErrInvalidCredentials)
|
||||||
}
|
|
||||||
return 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) {
|
if user.WrongPassword(f.Password) {
|
||||||
message := "incorrect password"
|
message := "incorrect password"
|
||||||
if m != nil {
|
limiter.Login.Reserve(clientIp)
|
||||||
limiter.Login.Reserve(m.IP())
|
event.AuditErr([]string{clientIp, "session %s", "login as %s", message}, m.RefID, clean.LogQuote(userName))
|
||||||
event.AuditErr([]string{m.IP(), "session %s", "login as %s", message}, m.RefID, clean.LogQuote(name))
|
event.LoginError(clientIp, "api", userName, m.UserAgent, message)
|
||||||
event.LoginError(m.IP(), "api", name, m.UserAgent, message)
|
m.Status = http.StatusUnauthorized
|
||||||
m.Status = http.StatusUnauthorized
|
return authn.ProviderNone, i18n.Error(i18n.ErrInvalidCredentials)
|
||||||
}
|
|
||||||
return i18n.Error(i18n.ErrInvalidCredentials)
|
|
||||||
} else if m != nil {
|
} else if m != nil {
|
||||||
event.AuditInfo([]string{m.IP(), "session %s", "login as %s", "succeeded"}, m.RefID, clean.LogQuote(name))
|
event.AuditInfo([]string{clientIp, "session %s", "login as %s", "succeeded"}, m.RefID, clean.LogQuote(userName))
|
||||||
event.LoginInfo(m.IP(), "api", name, m.UserAgent)
|
event.LoginInfo(clientIp, "api", userName, m.UserAgent)
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return authn.ProviderLocal, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// LogIn performs authentication checks against the specified login form.
|
// LogIn performs authentication checks against the specified login form.
|
||||||
|
|||||||
@@ -5,78 +5,246 @@ import (
|
|||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/photoprism/photoprism/pkg/authn"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"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/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) {
|
func TestAuthLocal(t *testing.T) {
|
||||||
t.Run("Alice", func(t *testing.T) {
|
t.Run("Alice", func(t *testing.T) {
|
||||||
|
|
||||||
m := FindSessionByRefID("sessxkkcabch")
|
m := FindSessionByRefID("sessxkkcabch")
|
||||||
|
|
||||||
u := FindUserByName("alice")
|
u := FindUserByName("alice")
|
||||||
|
|
||||||
|
// Create test request form.
|
||||||
frm := form.Login{
|
frm := form.Login{
|
||||||
UserName: "alice",
|
UserName: "alice",
|
||||||
Password: "Alice123!",
|
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)
|
t.Fatal(err)
|
||||||
|
} else {
|
||||||
|
assert.Equal(t, authn.ProviderLocal, provider)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
t.Run("Wrong credentials", func(t *testing.T) {
|
t.Run("Wrong credentials", func(t *testing.T) {
|
||||||
|
|
||||||
m := FindSessionByRefID("sessxkkcabch")
|
m := FindSessionByRefID("sessxkkcabch")
|
||||||
|
|
||||||
u := FindUserByName("alice")
|
u := FindUserByName("alice")
|
||||||
|
|
||||||
|
// Create test request form.
|
||||||
frm := form.Login{
|
frm := form.Login{
|
||||||
UserName: "alice",
|
UserName: "alice",
|
||||||
Password: "photoprism",
|
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")
|
t.Fatal("auth should fail")
|
||||||
|
} else {
|
||||||
|
assert.Equal(t, authn.ProviderNone, provider)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
t.Run("No login rights", func(t *testing.T) {
|
t.Run("No login rights", func(t *testing.T) {
|
||||||
|
|
||||||
m := &Session{}
|
m := &Session{}
|
||||||
|
|
||||||
u := FindUserByName("friend")
|
u := FindUserByName("friend")
|
||||||
|
|
||||||
u.CanLogin = false
|
u.CanLogin = false
|
||||||
|
|
||||||
|
// Create test request form.
|
||||||
frm := form.Login{
|
frm := form.Login{
|
||||||
UserName: "friend",
|
UserName: "friend",
|
||||||
Password: "!Friend321",
|
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")
|
t.Fatal("auth should fail")
|
||||||
|
} else {
|
||||||
|
assert.Equal(t, authn.ProviderNone, provider)
|
||||||
}
|
}
|
||||||
|
|
||||||
u.CanLogin = true
|
u.CanLogin = true
|
||||||
})
|
})
|
||||||
t.Run("Authentication disabled", func(t *testing.T) {
|
t.Run("Authentication disabled", func(t *testing.T) {
|
||||||
|
|
||||||
m := &Session{}
|
m := &Session{}
|
||||||
|
|
||||||
u := FindUserByName("friend")
|
u := FindUserByName("friend")
|
||||||
|
|
||||||
u.SetProvider(authn.ProviderNone)
|
u.SetProvider(authn.ProviderNone)
|
||||||
|
|
||||||
|
// Create test request form.
|
||||||
frm := form.Login{
|
frm := form.Login{
|
||||||
UserName: "friend",
|
UserName: "friend",
|
||||||
Password: "!Friend321",
|
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")
|
t.Fatal("auth should fail")
|
||||||
|
} else {
|
||||||
|
assert.Equal(t, authn.ProviderNone, provider)
|
||||||
}
|
}
|
||||||
|
|
||||||
u.SetProvider(authn.ProviderLocal)
|
u.SetProvider(authn.ProviderLocal)
|
||||||
@@ -85,9 +253,7 @@ func TestAuthLocal(t *testing.T) {
|
|||||||
|
|
||||||
func TestSessionLogIn(t *testing.T) {
|
func TestSessionLogIn(t *testing.T) {
|
||||||
const clientIp = "1.2.3.4"
|
const clientIp = "1.2.3.4"
|
||||||
|
|
||||||
rec := httptest.NewRecorder()
|
rec := httptest.NewRecorder()
|
||||||
ctx, _ := gin.CreateTestContext(rec)
|
|
||||||
|
|
||||||
t.Run("Admin", func(t *testing.T) {
|
t.Run("Admin", func(t *testing.T) {
|
||||||
m := NewSession(UnixDay, UnixHour*6)
|
m := NewSession(UnixDay, UnixHour*6)
|
||||||
@@ -99,12 +265,13 @@ func TestSessionLogIn(t *testing.T) {
|
|||||||
Password: "photoprism",
|
Password: "photoprism",
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create HTTP request.
|
// Create test request context.
|
||||||
ctx.Request = httptest.NewRequest(http.MethodPost, "/api/v1/session", form.AsReader(frm))
|
c, _ := gin.CreateTestContext(rec)
|
||||||
ctx.Request.RemoteAddr = "1.2.3.4"
|
c.Request = httptest.NewRequest(http.MethodPost, "/api/v1/session", form.AsReader(frm))
|
||||||
|
c.Request.RemoteAddr = "1.2.3.4"
|
||||||
|
|
||||||
// Try to log in.
|
// Try to log in.
|
||||||
if err := m.LogIn(frm, ctx); err != nil {
|
if err := m.LogIn(frm, c); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -118,12 +285,13 @@ func TestSessionLogIn(t *testing.T) {
|
|||||||
Password: "wrong",
|
Password: "wrong",
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create HTTP request.
|
// Create test request context.
|
||||||
ctx.Request = httptest.NewRequest(http.MethodPost, "/api/v1/session", form.AsReader(frm))
|
c, _ := gin.CreateTestContext(rec)
|
||||||
ctx.Request.RemoteAddr = "1.2.3.4"
|
c.Request = httptest.NewRequest(http.MethodPost, "/api/v1/session", form.AsReader(frm))
|
||||||
|
c.Request.RemoteAddr = "1.2.3.4"
|
||||||
|
|
||||||
// Try to log in.
|
// 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")
|
t.Fatal("login should fail")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -137,12 +305,13 @@ func TestSessionLogIn(t *testing.T) {
|
|||||||
Password: "password",
|
Password: "password",
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create HTTP request.
|
// Create test request context.
|
||||||
ctx.Request = httptest.NewRequest(http.MethodPost, "/api/v1/session", form.AsReader(frm))
|
c, _ := gin.CreateTestContext(rec)
|
||||||
ctx.Request.RemoteAddr = "1.2.3.4"
|
c.Request = httptest.NewRequest(http.MethodPost, "/api/v1/session", form.AsReader(frm))
|
||||||
|
c.Request.RemoteAddr = "1.2.3.4"
|
||||||
|
|
||||||
// Try to log in.
|
// 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")
|
t.Fatal("login should fail")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -155,12 +324,13 @@ func TestSessionLogIn(t *testing.T) {
|
|||||||
ShareToken: "1jxf3jfn2k",
|
ShareToken: "1jxf3jfn2k",
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create HTTP request.
|
// Create test request context.
|
||||||
ctx.Request = httptest.NewRequest(http.MethodPost, "/api/v1/session", form.AsReader(frm))
|
c, _ := gin.CreateTestContext(rec)
|
||||||
ctx.Request.RemoteAddr = "1.2.3.4"
|
c.Request = httptest.NewRequest(http.MethodPost, "/api/v1/session", form.AsReader(frm))
|
||||||
|
c.Request.RemoteAddr = "1.2.3.4"
|
||||||
|
|
||||||
// Try to log in.
|
// Try to log in.
|
||||||
if err := m.LogIn(frm, ctx); err != nil {
|
if err := m.LogIn(frm, c); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -174,12 +344,13 @@ func TestSessionLogIn(t *testing.T) {
|
|||||||
ShareToken: "1jxf3jfxxx",
|
ShareToken: "1jxf3jfxxx",
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create HTTP request.
|
// Create test request context.
|
||||||
ctx.Request = httptest.NewRequest(http.MethodPost, "/api/v1/session", form.AsReader(frm))
|
c, _ := gin.CreateTestContext(rec)
|
||||||
ctx.Request.RemoteAddr = "1.2.3.4"
|
c.Request = httptest.NewRequest(http.MethodPost, "/api/v1/session", form.AsReader(frm))
|
||||||
|
c.Request.RemoteAddr = "1.2.3.4"
|
||||||
|
|
||||||
// Try to log in.
|
// 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")
|
t.Fatal("login should fail")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -193,12 +364,13 @@ func TestSessionLogIn(t *testing.T) {
|
|||||||
ShareToken: "1jxf3jfn2k",
|
ShareToken: "1jxf3jfn2k",
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create HTTP request.
|
// Create test request context.
|
||||||
ctx.Request = httptest.NewRequest(http.MethodPost, "/api/v1/session", form.AsReader(frm))
|
c, _ := gin.CreateTestContext(rec)
|
||||||
ctx.Request.RemoteAddr = "1.2.3.4"
|
c.Request = httptest.NewRequest(http.MethodPost, "/api/v1/session", form.AsReader(frm))
|
||||||
|
c.Request.RemoteAddr = "1.2.3.4"
|
||||||
|
|
||||||
// Try to log in.
|
// Try to log in.
|
||||||
if err := m.LogIn(frm, ctx); err != nil {
|
if err := m.LogIn(frm, c); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -212,12 +384,13 @@ func TestSessionLogIn(t *testing.T) {
|
|||||||
ShareToken: "1jxf3jfxxx",
|
ShareToken: "1jxf3jfxxx",
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create HTTP request.
|
// Create test request context.
|
||||||
ctx.Request = httptest.NewRequest(http.MethodPost, "/api/v1/session", form.AsReader(frm))
|
c, _ := gin.CreateTestContext(rec)
|
||||||
ctx.Request.RemoteAddr = "1.2.3.4"
|
c.Request = httptest.NewRequest(http.MethodPost, "/api/v1/session", form.AsReader(frm))
|
||||||
|
c.Request.RemoteAddr = "1.2.3.4"
|
||||||
|
|
||||||
// Try to log in.
|
// 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")
|
t.Fatal("login should fail")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package entity
|
package entity
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/http"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -130,13 +131,19 @@ func TestDeleteClientSessions(t *testing.T) {
|
|||||||
|
|
||||||
func TestSessionStatusUnauthorized(t *testing.T) {
|
func TestSessionStatusUnauthorized(t *testing.T) {
|
||||||
m := SessionStatusUnauthorized()
|
m := SessionStatusUnauthorized()
|
||||||
assert.Equal(t, 401, m.Status)
|
assert.Equal(t, http.StatusUnauthorized, m.Status)
|
||||||
assert.IsType(t, &Session{}, m)
|
assert.IsType(t, &Session{}, m)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSessionStatusForbidden(t *testing.T) {
|
func TestSessionStatusForbidden(t *testing.T) {
|
||||||
m := SessionStatusForbidden()
|
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)
|
assert.IsType(t, &Session{}, m)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
20
internal/server/limiter/auth.go
Normal file
20
internal/server/limiter/auth.go
Normal file
@@ -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)
|
||||||
@@ -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)
|
|
||||||
@@ -7,9 +7,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Middleware registers the IP rate limiter middleware.
|
// Middleware registers the IP rate limiter middleware.
|
||||||
func Middleware(ip *Limit) gin.HandlerFunc {
|
func Middleware(limiter *Limit) gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
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)
|
c.AbortWithStatus(http.StatusTooManyRequests)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ type MethodType string
|
|||||||
// Authentication methods.
|
// Authentication methods.
|
||||||
const (
|
const (
|
||||||
MethodDefault MethodType = "default"
|
MethodDefault MethodType = "default"
|
||||||
|
MethodSession MethodType = "session"
|
||||||
MethodAccessToken MethodType = "access_token"
|
MethodAccessToken MethodType = "access_token"
|
||||||
MethodOAuth2 MethodType = "oauth2"
|
MethodOAuth2 MethodType = "oauth2"
|
||||||
MethodOIDC MethodType = "oidc"
|
MethodOIDC MethodType = "oidc"
|
||||||
|
|||||||
@@ -2,6 +2,6 @@ package header
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
CacheControl = "Cache-Control"
|
CacheControl = "Cache-Control"
|
||||||
CacheControlNoCache = "no-cache"
|
|
||||||
CacheControlNoStore = "no-store"
|
CacheControlNoStore = "no-store"
|
||||||
|
CacheControlNoCache = "no-cache"
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user