Compare commits

..
Author SHA1 Message Date
Christy Jacob b5c5f76acc Merge branch '1.7.x' of github.com:appwrite/appwrite into feat-apps-module 2025-06-20 17:39:28 +04:00
Eldad Fux 6f861a91ee Updated auth lib 2025-04-29 21:33:59 +02:00
Eldad Fux 0483c7efb5 Merge fixes 2025-04-29 20:44:05 +02:00
Eldad Fux fb29fe7b3c Merge remote-tracking branch 'origin/1.7.x' into feat-apps-module 2025-04-29 20:12:31 +02:00
Eldad Fux 1bf1766822 Merge remote-tracking branch 'origin/feat-sites' into feat-apps-module 2025-03-28 08:20:24 +01:00
Eldad Fux 57edb4a385 Removed leftovers 2025-03-28 08:17:47 +01:00
Eldad Fux 17c555ab6c Expanded the identities collection 2025-03-28 02:13:17 +01:00
Eldad Fux d6bd72cfd3 formatting 2025-03-19 14:10:56 +01:00
Eldad Fux 8c9123beaa Fixed tests 2025-03-19 13:54:32 +01:00
Eldad Fux 3d967e695f Updated auth lib 2025-03-19 07:57:28 +01:00
Eldad Fux a7d7e39dfd Fixed tests 2025-03-18 22:47:17 +01:00
Eldad Fux 38cb95e940 Fixed tests 2025-03-18 22:28:19 +01:00
Eldad Fux b2b20c48b3 Fixed tests 2025-03-18 22:12:03 +01:00
Eldad Fux fed6579491 Update teams tests 2025-03-18 21:52:41 +01:00
Eldad Fux 8cb85cafbd Fixes realtime tests 2025-03-18 20:24:38 +01:00
Eldad Fux 55e560bb41 Fixed unit tests 2025-03-18 19:08:58 +01:00
Eldad Fux dd9a40221e Merge remote-tracking branch 'origin/feat-sites' into feat-apps-module 2025-03-18 17:38:32 +01:00
Eldad Fux 24300cd3bd tests 2025-03-18 17:22:58 +01:00
Eldad Fux 4d5961c3ab Fixed test 2025-03-18 16:20:37 +01:00
Eldad Fux afb40218d7 Fixed tests 2025-03-18 16:12:13 +01:00
Eldad Fux 3daa3821ef Fixed syntax error 2025-03-18 10:24:42 +01:00
Eldad Fux 84cec0e32c format 2025-03-18 10:18:29 +01:00
Eldad Fux 7bae15d4fe Merge remote-tracking branch 'origin/feat-sites' into feat-apps-module 2025-03-18 10:15:06 +01:00
Eldad Fux 0180f72067 Removed unsed methods and tests 2025-03-18 00:30:31 +01:00
Eldad Fux f537091eb2 Fixed tests 2025-03-17 23:57:44 +01:00
Eldad Fux c99c83a006 Merge remote-tracking branch 'origin/feat-sites' into feat-apps-module 2025-03-17 23:15:57 +01:00
Eldad Fux 113828cd00 Improved errors 2025-03-17 22:51:22 +01:00
Eldad Fux 477add3022 Formatting 2025-03-17 21:49:10 +01:00
Eldad Fux 8aa5714173 cleanups 2025-03-17 21:44:31 +01:00
Eldad Fux fdb83c51f7 formatting 2025-03-17 12:46:07 +01:00
Eldad Fux c15739bba7 Merge remote-tracking branch 'origin/feat-sites' into feat-apps-module 2025-03-17 12:44:49 +01:00
Eldad Fux 1ce84f1650 WIP 2025-03-17 12:39:35 +01:00
Eldad Fux f618a8b563 Fixed account test 2025-03-16 22:16:02 +01:00
Eldad Fux 9346efc24a Fixed some tests 2025-03-16 19:35:42 +01:00
Eldad Fux a5e57a9a67 Work in progress, moving to instance based auth 2025-03-16 18:54:55 +01:00
Eldad Fux cc724218b0 fixed formatting 2025-03-16 18:32:46 +01:00
Eldad Fux 706ce4b3b6 WIP - cleaning up auth managment 2025-03-16 18:30:48 +01:00
Eldad Fux c08355df8d Updated composer 2025-03-16 10:34:45 +01:00
Eldad Fux 2d5904e3d0 Merge remote-tracking branch 'origin/feat-sites' into feat-apps-module 2025-03-16 10:26:55 +01:00
Eldad Fux d1318cf016 Updated dependencies 2025-03-10 02:19:57 +01:00
Eldad Fux 91921859a2 Merge remote-tracking branch 'origin/1.6.x' into feat-apps-module 2025-03-10 02:14:35 +01:00
Eldad Fux a058925252 Code formatting 2025-03-10 00:23:22 +01:00
Eldad Fux fadb24f295 revert change to build 2025-03-10 00:21:49 +01:00
Eldad Fux 748119334d Merge remote-tracking branch 'origin/feat-sites' into feat-apps-module 2025-03-10 00:18:23 +01:00
Eldad Fux cb05b87dda Upgraded users to use utopia/auth 2025-03-10 00:08:04 +01:00
Eldad Fux aa36d944db Add teamId to project array in e2e test 2025-03-01 22:00:37 +01:00
153 changed files with 1050 additions and 2034 deletions
-1
View File
@@ -22,7 +22,6 @@ _APP_OPTIONS_FORCE_HTTPS=disabled
_APP_OPTIONS_ROUTER_FORCE_HTTPS=disabled
_APP_OPENSSL_KEY_V1=your-secret-key
_APP_DOMAIN=traefik
_APP_CONSOLE_DOMAIN=localhost
_APP_DOMAIN_FUNCTIONS=functions.localhost
_APP_DOMAIN_SITES=sites.localhost
_APP_DOMAIN_TARGET_CNAME=test.localhost
+4 -5
View File
@@ -1,6 +1,5 @@
<?php
use Appwrite\Auth\Auth;
use Utopia\Database\Database;
use Utopia\Database\Helpers\ID;
@@ -173,7 +172,7 @@ return [
'size' => 256,
'signed' => true,
'required' => false,
'default' => Auth::DEFAULT_ALGO,
'default' => '',
'array' => false,
'filters' => [],
],
@@ -184,7 +183,7 @@ return [
'size' => 65535,
'signed' => true,
'required' => false,
'default' => Auth::DEFAULT_ALGO_OPTIONS,
'default' => new \stdClass(),
'array' => false,
'filters' => ['json'],
],
@@ -1115,9 +1114,9 @@ return [
[
'$id' => ID::custom('expire'),
'type' => Database::VAR_DATETIME,
'format' => '',
'size' => 0,
'required' => false,
'required' => true,
'format' => '',
'signed' => false,
'default' => null,
'array' => false,
+1 -2
View File
@@ -4,7 +4,6 @@
* Initializes console project document.
*/
use Appwrite\Auth\Auth;
use Appwrite\Network\Validator\Origin;
use Utopia\Database\Helpers\ID;
use Utopia\System\System;
@@ -38,7 +37,7 @@ $console = [
'mockNumbers' => [],
'invites' => System::getEnv('_APP_CONSOLE_INVITES', 'enabled') === 'enabled',
'limit' => (System::getEnv('_APP_CONSOLE_WHITELIST_ROOT', 'enabled') === 'enabled') ? 1 : 0, // limit signup to 1 user
'duration' => Auth::TOKEN_EXPIRATION_LOGIN_LONG, // 1 Year in seconds
'duration' => TOKEN_EXPIRATION_LOGIN_LONG, // 1 Year in seconds
'sessionAlerts' => System::getEnv('_APP_CONSOLE_SESSION_ALERTS', 'disabled') === 'enabled'
],
'authWhitelistEmails' => (!empty(System::getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null))) ? \explode(',', System::getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null)) : [],
@@ -1,6 +1,6 @@
<p>{{hello}}</p>
<p>{{body}}</p>
<p><a href="{{redirect}}" target="_blank" style="font-size: 14px; font-family: Inter, sans-serif; color: #ffffff; text-decoration: none; background-color: #2D2D31; border-radius: 8px; padding: 9px 14px; border: 1px solid #414146; display: inline-block; text-align:center; box-sizing: border-box;">{{buttonText}}</a></p>
<p><a href="{{redirect}}" target="_blank">{{redirect}}</a></p>
<p>{{footer}}</p>
<p style="margin-bottom: 32px">
{{thanks}}
-3
View File
@@ -8,7 +8,6 @@
"emails.verification.body": "Volg hierdie skakel om u e-pos adres te bevestig.",
"emails.verification.footer": "Ignoreer gerus hierdie boodskap as u nie die versoek gestuur het om u adres te bevestig nie.",
"emails.verification.thanks": "Baie dankie,",
"emails.verification.buttonText": "Bevestig e-posadres",
"emails.verification.signature": "Die {{project}} span",
"emails.magicSession.subject": "Teken aan",
"emails.magicSession.hello": "Goeie dag,",
@@ -21,14 +20,12 @@
"emails.recovery.body": "Volg hierdie skakel om u {{project}} wagwoord te herstel.",
"emails.recovery.footer": "Ignoreer gerus hierdie boodskap as u nie die versoek gestuur het om u wagwoord te herstel nie.",
"emails.recovery.thanks": "Baie dankie,",
"emails.recovery.buttonText": "Stel wagwoord terug",
"emails.recovery.signature": "Die {{project}} span",
"emails.invitation.subject": "Uitnodiging om by die %s span aan te sluit by %s",
"emails.invitation.hello": "Goeie dag,",
"emails.invitation.body": "Hierdie boodskap is aan u gestuur omdat {{owner}} u uitnooi om 'n lid van die {{team}} groep by die {{project}} projek te wees.",
"emails.invitation.footer": "As u nie belang stel nie, kan u gerus hierdie boodskap ignoreer.",
"emails.invitation.thanks": "Baie dankie,",
"emails.invitation.buttonText": "Aanvaar uitnodiging na {{team}}",
"emails.invitation.signature": "Die {{project}} span",
"locale.country.unknown": "Onbekend",
"countries.af": "Afghanistan",
@@ -8,7 +8,6 @@
"emails.verification.body": "تبّع هاد الوصلة باش تيقّن لادريسة تاع ليميل ديالك.",
"emails.verification.footer": "إلا ماشي نتا اللي طلبتي تيقّن هاد لادريسة تاع ليميل، ممكن تنخّل هاد البرية.",
"emails.verification.thanks": "شكرا،",
"emails.verification.buttonText": "تأكيد عنوان البريد الإلكتروني",
"emails.verification.signature": "فرقة {{project}}",
"emails.magicSession.subject": "تكونيكطا",
"emails.magicSession.hello": "السلام،",
@@ -21,14 +20,12 @@
"emails.recovery.body": "تبّع هاد الوصلة باش تبدّل كلمة السر تاع {{project}}.",
"emails.recovery.footer": "إلا ماشي نتا اللي طلبتي تبدّل كلمة السر، ممكن تنخّل هاد البرية.",
"emails.recovery.thanks": "شكرا،",
"emails.recovery.buttonText": "إعادة تعيين كلمة السر",
"emails.recovery.signature": "فرقة {{project}}",
"emails.invitation.subject": "عراضة ل فرقة %s ف %s",
"emails.invitation.hello": "السلام،",
"emails.invitation.body": "هاد البرية تصيفطات ليك حيت {{owner}} بغى يعرض عليك تولّي عضو ف فرقة {{team}} عند {{project}}.",
"emails.invitation.footer": "إلا كنتي ما مسوّقش, ممكن تنخّل هاد البرية.",
"emails.invitation.thanks": "شكرا،",
"emails.invitation.buttonText": "اقبل الدعوة إلى {{team}}",
"emails.invitation.signature": "فرقة {{project}}",
"emails.certificate.subject": "السرتافيكة فشلات ل %s",
"emails.certificate.hello": "السلام،",
-3
View File
@@ -8,7 +8,6 @@
"emails.verification.body": "برجاء اتباع الرابط التالي لتأكيد بريدك الإلكتروني",
"emails.verification.footer": "لو لم تطلب تأكيد هذا البريد الإلكتروني، يمكنك تجاهل هذه الرسالة",
"emails.verification.thanks": "شكرا،",
"emails.verification.buttonText": "تأكيد عنوان البريد الإلكتروني",
"emails.verification.signature": "فريق {{project}}",
"emails.magicSession.subject": "تسجيل الدخول",
"emails.magicSession.hello": "أهلا،",
@@ -21,14 +20,12 @@
"emails.recovery.body": "برجاء اتباع الراط التالي لتغيير كلمة السر الخاصة بـ{{project}}",
"emails.recovery.footer": "لولم تطلب تغيير كلمة السر، يمكنك تجاهل هذه الرسالة",
"emails.recovery.thanks": "شكرا،",
"emails.recovery.buttonText": "إعادة تعيين كلمة المرور",
"emails.recovery.signature": "فريق {{project}}",
"emails.invitation.subject": "دعوة لفريق %s في %s",
"emails.invitation.hello": "أهلا،",
"emails.invitation.body": "هذة الرسالة تم ارسالها لك لأن {{owner}} ارسل لك دعوة لتكون عضوا بفريق {{team}} في {{project}}",
"emails.invitation.footer": "اذا كنت غير مهتم، يمكنك تجاهل هذه الرسالة",
"emails.invitation.thanks": "شكرا،",
"emails.invitation.buttonText": "قبول الدعوة إلى {{team}}",
"emails.invitation.signature": "فريق {{project}}",
"locale.country.unknown": "مجهول",
"countries.af": "أفغانستان",
-3
View File
@@ -8,7 +8,6 @@
"emails.verification.body": "আপোনাৰ ইমেইল ঠিকনা প্ৰমাণিত কৰিবলৈ এই লিংকটো অনুসৰণ কৰক।",
"emails.verification.footer": "যদি আপুনি এই ঠিকনাটো সত্যাপিত কৰিবলৈ কোৱা নাই, আপুনি এই বাৰ্তাটো উপেক্ষা কৰিব পাৰে।",
"emails.verification.thanks": "ধন্যবাদ,",
"emails.verification.buttonText": "ইমেইল ঠিকনা নিশ্চিত কৰক",
"emails.verification.signature": "{{project}} দল",
"emails.magicSession.subject": "লগইন",
"emails.magicSession.hello": "নমস্কাৰ,",
@@ -21,14 +20,12 @@
"emails.recovery.body": "আপোনাৰ {{project}} পাছৱৰ্ড ৰিছেট কৰিবলৈ এই লিংকটো অনুসৰণ কৰক।.",
"emails.recovery.footer": "যদি আপুনি আপোনাৰ পাছৱৰ্ড ৰিছেট কৰিবলৈ কোৱা নাছিল, আপুনি এই বাৰ্তাটো উপেক্ষা কৰিব পাৰে।",
"emails.recovery.thanks": "ধন্যবাদ,",
"emails.recovery.buttonText": "পাছৱৰ্ড ৰিছেট কৰক",
"emails.recovery.signature": "{{project}} দল",
"emails.invitation.subject": "%s বছৰত %s দললৈ নিমন্ত্ৰণ",
"emails.invitation.hello": "নমস্কাৰ,",
"emails.invitation.body": "এই মেইলটো আপোনালৈ প্ৰেৰণ কৰা হৈছিল কাৰণ {{owner}} জনে আপোনাক {{project}} বছৰবয়সত {{team}} দলৰ সদস্য হ'বলৈ আমন্ত্ৰণ জনাব বিচাৰিছিল।",
"emails.invitation.footer": "যদি আপুনি আগ্ৰহী নহয়, আপুনি এই বাৰ্তাটো উপেক্ষা কৰিব পাৰে।",
"emails.invitation.thanks": "ধন্যবাদ,",
"emails.invitation.buttonText": "{{team}}-লৈ নিমন্ত্ৰণ গ্ৰহণ কৰক",
"emails.invitation.signature": "{{project}} দল",
"locale.country.unknown": "অজ্ঞাত ",
"countries.af": "আফগানিস্তান ",
-3
View File
@@ -8,7 +8,6 @@
"emails.verification.body": "E-poçt ünvanınızı təsdiq etmək üçün bu linki izləyin.",
"emails.verification.footer": "Bu ünvanı doğrulamağı xahiş etməmisinizsə, bu mesajı gözardı edə bilərsiniz.",
"emails.verification.thanks": "Təşəkkürlər,",
"emails.verification.buttonText": "E-poçt ünvanını təsdiqlə",
"emails.verification.signature": "{{project}} komandası",
"emails.magicSession.subject": "Daxil Olmaq",
"emails.magicSession.hello": "Salam,",
@@ -21,14 +20,12 @@
"emails.recovery.body": "{{project}} şifrənizi sıfırlamaq üçün bu linki izləyin.",
"emails.recovery.footer": "Şifrənizi sıfırlamağı xahiş etməmisinizsə, bu mesajı gözardı edə bilərsiniz.",
"emails.recovery.thanks": "Təşəkkürlər,",
"emails.recovery.buttonText": "Şifrəni sıfırla",
"emails.recovery.signature": "{{project}} komandası",
"emails.invitation.subject": "%s Komandasına Dəvət %sdə",
"emails.invitation.hello": "Salam,",
"emails.invitation.body": "{{owner}}, {{project}}də {{team}} komandasına üzv olmağa dəvət etmək istədiyi üçün bu məktub sizə göndərildi.",
"emails.invitation.footer": "Əgər maraqlanmırsınızsa, bu mesajı gözardı edə bilərsiniz.",
"emails.invitation.thanks": "Təşəkkürlər,",
"emails.invitation.buttonText": "{{team}} dəvətini qəbul et",
"emails.invitation.signature": "{{project}} komandası",
"locale.country.unknown": "Naməlum",
"countries.af": "Əfqanıstan",
-3
View File
@@ -8,7 +8,6 @@
"emails.verification.body": "Перайдзіце па гэтай спасылцы, каб пацвердзіць свой адрас электроннай пошты",
"emails.verification.footer": "Калі вы не запытвалі пацвярджэнне гэтага адрасу, праігнаруйце гэтае паведамленне.",
"emails.verification.thanks": "Дзякуем,",
"emails.verification.buttonText": "Пацвердзіць адрас электроннай пошты",
"emails.verification.signature": "каманда {{project}}",
"emails.magicSession.subject": "Лагін",
"emails.magicSession.hello": "Прывітанне,",
@@ -21,14 +20,12 @@
"emails.recovery.body": "Перайдзіце па гэтай спасылцы, каб скінуць пароль для праекта {{project}}.",
"emails.recovery.footer": "Калі вы не прасілі скінуць пароль, вы можаце праігнараваць гэта паведамленне.",
"emails.recovery.thanks": "Дзякуем,",
"emails.recovery.buttonText": "Аднавіць пароль",
"emails.recovery.signature": "каманда {{project}}",
"emails.invitation.subject": "Запрошення до Команди %s у %s",
"emails.invitation.hello": "Прывітанне,",
"emails.invitation.body": "Гэта паведамленне было адпраўлена вам, таму што {{owner}} хацеў запрасіць вас стаць членам каманды {{team}} у {{project}}.",
"emails.invitation.footer": "Калі вам гэта не цікава, вы можаце праігнараваць гэтае паведамленне.",
"emails.invitation.thanks": "Дзякуем,",
"emails.invitation.buttonText": "Прыняць запрашэнне ў {{team}}",
"emails.invitation.signature": "каманда {{project}}",
"locale.country.unknown": "Невядомы",
"countries.af": "Афганістан",
-3
View File
@@ -8,7 +8,6 @@
"emails.verification.body": "ईमेल प्रमाणिकरण करे क लेल दिहल गइल लिंक फॉलो करें|",
"emails.verification.footer": "अगर ई पता को सत्यापित करे के लिए ना कहाले, तो आप ई संदेश क अनदेखा कर सकत अछि।",
"emails.verification.thanks": "धन्यवाद,",
"emails.verification.buttonText": "ईमेल पता के पुष्टि करीं",
"emails.verification.signature": "{{project}} टीम",
"emails.magicSession.subject": "लॉग इन करीं|",
"emails.magicSession.hello": "प्रणाम,",
@@ -21,14 +20,12 @@
"emails.recovery.body": "पासवर्ड बदल क लेल दिहल गइल लिंक फॉलो करें|",
"emails.recovery.footer": "अगर पासवर्ड बदल क लेल ना कहाले, तो आप ई संदेश क अनदेखा कर सकत अछि।",
"emails.recovery.thanks": "धन्यवाद,",
"emails.recovery.buttonText": "पासवर्ड रीसेट करीं",
"emails.recovery.signature": "{{project}} टीम",
"emails.invitation.subject": "%s टीम क %s पे न्योता देवे क लेल|",
"emails.invitation.hello": "प्रणाम,",
"emails.invitation.body": "ई मेल आपके एही लेल भेजल गईल रहल काहे क {{owner}} आपके {{project}} क {{team}} टीम का सदस्य बनावे चाहित रहे|",
"emails.invitation.footer": "अगर आवे क इच्छा ना होवत, तो आप ई संदेश क अनदेखा कर सकत अछि।",
"emails.invitation.thanks": "धन्यवाद,",
"emails.invitation.buttonText": "{{team}} में नेवता स्वीकार करीं",
"emails.invitation.signature": "{{project}} टीम",
"locale.country.unknown": "अनजान",
"countries.af": "अफ़ग़ानिस्तान",
-3
View File
@@ -8,7 +8,6 @@
"emails.verification.body": "এই লিঙ্কের মাধ্যমে ইমেইল যাচাই করুন।",
"emails.verification.footer": "আপনি যদি এই ঠিকানা যাচাই করতে না বলেন, তাহলে আপনি এই বার্তাটি উপেক্ষা করতে পারেন।",
"emails.verification.thanks": "ধন্যবাদ,",
"emails.verification.buttonText": "ইমেইল ঠিকানা নিশ্চিত করুন",
"emails.verification.signature": "{{project}} টীম",
"emails.magicSession.subject": "লগ ইন",
"emails.magicSession.hello": "নমস্কার,",
@@ -21,14 +20,12 @@
"emails.recovery.body": "এই লিঙ্কের মাধ্যমে আপনার {{project}} পাসওয়ার্ড পুনরায় সেট করুন।",
"emails.recovery.footer": "আপনি যদি আপনার পাসওয়ার্ড পুনরায় সেট করতে না বলেন, তাহলে আপনি এই বার্তাটি উপেক্ষা করতে পারেন।",
"emails.recovery.thanks": "ধন্যবাদ,",
"emails.recovery.buttonText": "পাসওয়ার্ড রিসেট করুন",
"emails.recovery.signature": "{{project}} টীম",
"emails.invitation.subject": "%s টিমকে %s তে আমন্ত্রণ জানান",
"emails.invitation.hello": "নমস্কার,",
"emails.invitation.body": "এই মেইলটি আপনাকে পাঠানো হয়েছে কারণ {{owner}} আপনাকে {{project}} এর সাথে যুক্ত {{team}} টিমের সদস্য হওয়ার জন্য আমন্ত্রণ জানাতে চেয়েছিলেন।",
"emails.invitation.footer": "যদি এটি আপনার জন্য প্রয়োজনীয় না হয়, আপনি এই বার্তাটি উপেক্ষা করতে পারেন।",
"emails.invitation.thanks": "ধন্যবাদ,",
"emails.invitation.buttonText": "{{team}}-এর আমন্ত্রণ গ্রহণ করুন",
"emails.invitation.signature": "{{project}} টীম",
"locale.country.unknown": "অজানা",
"countries.af": "আফগানিস্তান",
-3
View File
@@ -8,7 +8,6 @@
"emails.verification.body": "Accedeix a aquest enllaç per tal de verificar la teva adreça electrònica.",
"emails.verification.footer": "Si no has sol·licitat la verificació d'aquesta adreça electrònica, pots ignorar aquest missatge.",
"emails.verification.thanks": "Gràcies,",
"emails.verification.buttonText": "Confirma l'adreça electrònica",
"emails.verification.signature": "Equip {{project}}",
"emails.magicSession.subject": "Entrar",
"emails.magicSession.hello": "Hola,",
@@ -21,14 +20,12 @@
"emails.recovery.body": "Accedeix a aquest enllaç per a reinicialitzar la teva contrasenya de {{project}}.",
"emails.recovery.footer": "Si no has sol·licitat reinicialitzar la teva contrasenya, pots ignorar aquest missatge.",
"emails.recovery.thanks": "Gràcies,",
"emails.recovery.buttonText": "Restableix la contrasenya",
"emails.recovery.signature": "Equip {{project}}",
"emails.invitation.subject": "Invitació a l'equip %s a s%",
"emails.invitation.hello": "Hola,",
"emails.invitation.body": "Aquest correu se t'ha enviat perquè {{owner}} vol convidar-te a formar part de l'equip {{team}} al {{project}}.",
"emails.invitation.footer": "Si no és del teu interès, pots ignorar aquest missatge.",
"emails.invitation.thanks": "Gràcies,",
"emails.invitation.buttonText": "Accepta la invitació a {{team}}",
"emails.invitation.signature": "Equip {{project}}",
"locale.country.unknown": "Desconegut",
"countries.af": "Afganistan",
-3
View File
@@ -8,7 +8,6 @@
"emails.verification.body": "Følg dette link, for at verificere din email adresse.",
"emails.verification.footer": "Hvis du ikke har bedt om at verificere denne adresse, ignorer venligst denne besked.",
"emails.verification.thanks": "Tak,",
"emails.verification.buttonText": "Bekræft e-mailadresse",
"emails.verification.signature": "{{project}} team",
"emails.magicSession.subject": "Login",
"emails.magicSession.hello": "Hej,",
@@ -21,14 +20,12 @@
"emails.recovery.body": "Følg dette link for at nulstille koden til {{project}}.",
"emails.recovery.footer": "Hvis du ikke har bedt om at nulstille dit password, ignorer venligst denne besked.",
"emails.recovery.thanks": "Tak,",
"emails.recovery.buttonText": "Nulstil adgangskode",
"emails.recovery.signature": "{{project}} team",
"emails.invitation.subject": "Invitation til %s Team på %s",
"emails.invitation.hello": "Hej,",
"emails.invitation.body": "Denne mail blev sendt til dig, fordi {{owner}} vil invitere dig til at blive medlem af {{team}} teamet på {{project}}.",
"emails.invitation.footer": "Hvis du ikke er interesseret, ignorer venligst denne besked.",
"emails.invitation.thanks": "Tak,",
"emails.invitation.buttonText": "Accepter invitation til {{team}}",
"emails.invitation.signature": "{{project}} team",
"locale.country.unknown": "Ukendt",
"countries.af": "Afghanistan",
-3
View File
@@ -8,7 +8,6 @@
"emails.verification.body": "Folge diesem Link, um deine E-Mail-Adresse zu bestätigen.",
"emails.verification.footer": "Solltest du keine Verifizierung dieser E-Mail-Adresse angefordert haben, kannst du diese Nachricht ignorieren.",
"emails.verification.thanks": "Danke,",
"emails.verification.buttonText": "E-Mail-Adresse bestätigen",
"emails.verification.signature": "{{project}}-Team",
"emails.magicSession.subject": "Login",
"emails.magicSession.hello": "Hey,",
@@ -21,14 +20,12 @@
"emails.recovery.body": "Folge diesem Link, um dein {{project}}-Kennwort zurückzusetzen.",
"emails.recovery.footer": "Solltest du keine Kennwort-Zurücksetzung angefordert haben, kannst du diese Nachricht ignorieren.",
"emails.recovery.thanks": "Danke,",
"emails.recovery.buttonText": "Passwort zurücksetzen",
"emails.recovery.signature": "{{project}}-Team",
"emails.invitation.subject": "Einladung zum %s-Team auf %s",
"emails.invitation.hello": "Hello,",
"emails.invitation.body": "Du erhälst diese E-Mail, weil {{owner}} dich in das Team {{team}} auf {{project}} eingeladen hat.",
"emails.invitation.footer": "Wenn du nicht interessiert bist, kannst du diese Nachricht ignorieren.",
"emails.invitation.thanks": "Danke,",
"emails.invitation.buttonText": "Einladung zu {{team}} annehmen",
"emails.invitation.signature": "{{project}}-Team",
"locale.country.unknown": "Unbekannt",
"countries.af": "Afghanistan",
-3
View File
@@ -8,7 +8,6 @@
"emails.verification.body": "Ακολουθήστε αυτό το link για να επαληθεύσετε τη δ/νση του email σας",
"emails.verification.footer": "Εάν δεν ζητήσατε επαλήθευση αυτής της δ/νσης email, μπορείτε να αγνοήσετε αυτό το μήνυμα",
"emails.verification.thanks": "Ευχαριστούμε,",
"emails.verification.buttonText": "Επιβεβαιώστε διεύθυνση email",
"emails.verification.signature": "Η ομάδα του {{project}}",
"emails.magicSession.subject": "Είσοδος",
"emails.magicSession.hello": "Γεια σου,",
@@ -21,14 +20,12 @@
"emails.recovery.body": "Ακολουθήστε αυτό το link για να αλλάξετε τον {{project}} κωδικό σας",
"emails.recovery.footer": "Εάν δεν ζητήσατε αλλαγή του κωδικού σας πρόσβασης, μπορείτε να αγνοήσετε αυτό το μήνυμα",
"emails.recovery.thanks": "Ευχαριστούμε,",
"emails.recovery.buttonText": "Επαναφορά κωδικού πρόσβασης",
"emails.recovery.signature": "Η ομάδα του {{project}}",
"emails.invitation.subject": "Πρόσκληση στην %s Ομάδα στον %s",
"emails.invitation.hello": "Γεια σου,",
"emails.invitation.body": "Αυτό το email στάλθηκε επειδή ο/η {{owner}} θέλει να σας προσκαλέσει να γίνετε μέλος της ομάδας {{team}} του {{project}}.",
"emails.invitation.footer": "Εάν δεν ενδιαφέρεστε, μπορείτε να αγνοήσετε αυτό το μήνυμα.",
"emails.invitation.thanks": "Ευχαριστούμε,",
"emails.invitation.buttonText": "Αποδεχόμενος την πρόσκληση στην {{team}}",
"emails.invitation.signature": "Η ομάδα του {{project}}",
"locale.country.unknown": "Άγνωστο",
"countries.af": "Αφγανιστάν",
-3
View File
@@ -8,7 +8,6 @@
"emails.verification.body": "Follow this link to verify your email address to your {{b}}{{project}}{{/b}} account.",
"emails.verification.footer": "If you didnt ask to verify this address, you can ignore this message.",
"emails.verification.thanks": "Thanks,",
"emails.verification.buttonText": "Confirm email address",
"emails.verification.signature": "{{project}} team",
"emails.magicSession.subject": "{{project}} Login",
"emails.magicSession.hello": "Hello {{user}},",
@@ -46,14 +45,12 @@
"emails.recovery.body": "Follow this link to reset your {{b}}{{project}}{{/b}} password.",
"emails.recovery.footer": "If you didn't ask to reset your password, you can ignore this message.",
"emails.recovery.thanks": "Thanks,",
"emails.recovery.buttonText": "Reset password",
"emails.recovery.signature": "{{project}} team",
"emails.invitation.subject": "Invitation to %s Team at %s",
"emails.invitation.hello": "Hello {{user}},",
"emails.invitation.body": "This mail was sent to you because {{b}}{{owner}}{{/b}} wanted to invite you to become a member of the {{b}}{{team}}{{/b}} team at {{b}}{{project}}{{/b}}.",
"emails.invitation.footer": "If you are not interested, you can ignore this message.",
"emails.invitation.thanks": "Thanks,",
"emails.invitation.buttonText": "Accept invite to {{team}}",
"emails.invitation.signature": "{{project}} team",
"emails.certificate.subject": "Certificate failure for %s",
"emails.certificate.hello": "Hello,",
-3
View File
@@ -7,7 +7,6 @@
"emails.verification.body": "Alklaku ĉi tiun ligon por kontroli vian retpoŝtan adreson.",
"emails.verification.footer": "Se vi ne petis ĉi tiun konfirmon de ĉi tiu retpoŝto, vi povas ignori ĉi tiun mesaĝon.",
"emails.verification.thanks": "Dankegon.,",
"emails.verification.buttonText": "Konfirmi retadreson",
"emails.verification.signature": "Teamo {{project}}",
"emails.magicSession.subject": "Login",
"emails.magicSession.hello": "Saluton,",
@@ -20,14 +19,12 @@
"emails.recovery.body": "Alklaku ĉi tiun ligon por reagordi vian pasvorton. {{project}}",
"emails.recovery.footer": "Se vi ne petis reagordi vian pasvorton, vi povas ignori ĉi tiun mesaĝon.",
"emails.recovery.thanks": "Dankegon,",
"emails.recovery.buttonText": "Pasvorton restarigi",
"emails.recovery.signature": "Teamo {{project}}",
"emails.invitation.subject": "Invito al la Teamo %s em %s",
"emails.invitation.hello": "Dankegon,",
"emails.invitation.body": "Ĉi tiu retpoŝto estis sendita ĉar la {{owner}} volas inviti vin fariĝi membro de la Teamo {{team}} en {{project}}.",
"emails.invitation.footer": "Se vi ne interesiĝas, vi povas ignori ĉi tiun mesaĝon.",
"emails.invitation.thanks": "Dankegon,",
"emails.invitation.buttonText": "Akcepti inviton al {{team}}",
"emails.invitation.signature": "Teamo {{project}}",
"locale.country.unknown": "Unknown",
"countries.af": "Afghanistan",
-3
View File
@@ -8,7 +8,6 @@
"emails.verification.body": "Haz clic en este enlace para verificar tu correo:",
"emails.verification.footer": "Si no has solicitado verificar este correo, puedes ignorar este mensaje.",
"emails.verification.thanks": "Gracias.,",
"emails.verification.buttonText": "Confirmar dirección de correo",
"emails.verification.signature": "El equipo de {{project}}.",
"emails.magicSession.subject": "Inicio de sesión",
"emails.magicSession.hello": "Hola,",
@@ -21,14 +20,12 @@
"emails.recovery.body": "Haz clic en este enlace para restablecer la contraseña de {{project}}:",
"emails.recovery.footer": "Si no has solicitado restablecer la contraseña, puedes ignorar este mensaje.",
"emails.recovery.thanks": "Gracias.,",
"emails.recovery.buttonText": "Restablecer contraseña",
"emails.recovery.signature": "El equipo de {{project}}",
"emails.invitation.subject": "Invitación al equipo %s en %s",
"emails.invitation.hello": "Hola,",
"emails.invitation.body": "Este correo ha sido enviado a petición de {{owner}} quién quiere invitarte a formar parte del equipo {{team}} en {{project}}.",
"emails.invitation.footer": "Si no estás interesado, puedes ignorar este mensaje.",
"emails.invitation.thanks": "Gracias.,",
"emails.invitation.buttonText": "Aceptar invitación a {{team}}",
"emails.invitation.signature": "El equipo de {{project}}",
"locale.country.unknown": "Desconocido",
"countries.af": "Afganistán",
-3
View File
@@ -8,7 +8,6 @@
"emails.verification.body": "برای تأیید ایمیل‌تان پیوند زیر را دنبال کنید.",
"emails.verification.footer": "اگر شما درخواست تأیید حساب نداده‌اید، می‌توانید این پیام را نادیده بگیرید.",
"emails.verification.thanks": "سپاس فراوان،",
"emails.verification.buttonText": "آدرس ایمیل را تایید کنید",
"emails.verification.signature": "تیم {{user}}",
"emails.magicSession.subject": "ورود به حساب کاربری",
"emails.magicSession.hello": "سلام،",
@@ -21,14 +20,12 @@
"emails.recovery.body": "برای بازیابی گذرواژه‌تان پیوند زیر را دنبال کنید.",
"emails.recovery.footer": "اگر شما درخواست بازیابی گذرواژه نداده‌اید، می‌توانید این پیام را نادیده بگیرید.",
"emails.recovery.thanks": "سپاس فراوان،",
"emails.recovery.buttonText": "بازنشانی رمز عبور",
"emails.recovery.signature": "تیم {{user}}",
"emails.invitation.subject": "دعوت به تیم %s در %s",
"emails.invitation.hello": "سلام،",
"emails.invitation.body": "این ایمیل برای شما فرستاده شده‌است زیرا {{owner}} می‌خواهد شما را به تیم {{team}} در پروژه‌ی {{project}} بیفزاید.",
"emails.invitation.footer": "اگر علاقه ندارید، می‌توانید این پیام را نادیده بگیرید.",
"emails.invitation.thanks": "سپاس فراوان،",
"emails.invitation.buttonText": "دعوت را به {{team}} بپذیرید",
"emails.invitation.signature": "تیم {{user}}",
"locale.country.unknown": "ناشناخته",
"countries.af": "افغانستان",
-3
View File
@@ -8,7 +8,6 @@
"emails.verification.body": "Suivez ce lien pour vérifier votre adresse e-mail.",
"emails.verification.footer": "Si vous n'avez pas demandé à vérifier cette adresse, vous pouvez ignorer ce message.",
"emails.verification.thanks": "Merci,",
"emails.verification.buttonText": "Confirmez l'adresse e-mail",
"emails.verification.signature": "Équipe {{project}}",
"emails.magicSession.subject": "Connexion",
"emails.magicSession.hello": "Bonjour,",
@@ -21,14 +20,12 @@
"emails.recovery.body": "Suivez ce lien pour réinitialiser votre mot de passe pour {{project}}.",
"emails.recovery.footer": "Si vous n'avez pas demandé à réinitialiser votre mot de passe, vous pouvez ignorer ce message.",
"emails.recovery.thanks": "Merci,",
"emails.recovery.buttonText": "Réinitialisation du mot de passe",
"emails.recovery.signature": "L'équipe {{project}}",
"emails.invitation.subject": "Invitation à l'équipe %s de %s",
"emails.invitation.hello": "Bonjour,",
"emails.invitation.body": "Cet e-mail vous a été envoyé parce que {{owner}} souhaite vous inviter à devenir membre de l'équipe {{team}} pour {{project}}.",
"emails.invitation.footer": "Si vous n'êtes pas intéressé, vous pouvez ignorer ce message.",
"emails.invitation.thanks": "Merci,",
"emails.invitation.buttonText": "Accepter l'invitation à {{team}}",
"emails.invitation.signature": "L'équipe {{project}}",
"locale.country.unknown": "Inconnu",
"countries.af": "Afghanistan",
-3
View File
@@ -8,7 +8,6 @@
"emails.verification.body": "Lean an nasc seo chun do ríomhphost a fhíorú.",
"emails.verification.footer": "Mura ndearna tú iarratas an seoladh seo a fhíoru, déan neamhaird den teachtaireacht seo.",
"emails.verification.thanks": "Go raibh maith agat,",
"emails.verification.buttonText": "Deimhnigh seoladh ríomhphoist",
"emails.verification.signature": "{{project}} foireann",
"emails.magicSession.subject": "Logáil isteach",
"emails.magicSession.hello": "Haigh,",
@@ -21,14 +20,12 @@
"emails.recovery.body": "Lean an nasc seo chun do pasfhocal {{project}} a athshocrú.",
"emails.recovery.footer": "Mura ndearna tú iarratas do pasfhocal a athshocrú, déan neamhaird den teachtaireacht seo.",
"emails.recovery.thanks": "Go raibh maith agat,",
"emails.recovery.buttonText": "Athshocraigh focal faire",
"emails.recovery.signature": "{{project}} foireann",
"emails.invitation.subject": "Cuireadh do %s foireann ag %s",
"emails.invitation.hello": "Haigh,",
"emails.invitation.body": "Seoladh an ríomhphost seo chugat mar ba mhaith le {{owner}} cuireadh a thabhairt duit bheith mar bhall den fhoireann {{team}} ag obair ar {{project}}.",
"emails.invitation.footer": "Is cuma leat? Déan neamhaird den teachtaireacht seo.",
"emails.invitation.thanks": "Go raibh maith agat,",
"emails.invitation.buttonText": "Glac le cuireadh chuig {{team}}",
"emails.invitation.signature": "{{project}} foireann",
"locale.country.unknown": "Neamhaithnid",
"countries.af": "An Afganastáin",
-3
View File
@@ -8,7 +8,6 @@
"emails.verification.body": "તમારું ઇમેઇલ સરનામું ચકાસવા માટે આ લિંકને અનુસરો.",
"emails.verification.footer": "જો તમે આ સરનામાંની ચકાસણી કરવાનું ન કહ્યું હોય, તો તમે આ સંદેશને અવગણી શકો છો.",
"emails.verification.thanks": "આભાર,",
"emails.verification.buttonText": "ઇમેઇલ સરનામું ખાતરી કરો",
"emails.verification.signature": "{{project}} ટીમ",
"emails.magicSession.subject": "પ્રવેશ કરો",
"emails.magicSession.hello": "નમસ્કાર,",
@@ -21,14 +20,12 @@
"emails.recovery.body": "તમારો {{project}} પાસવર્ડ ફરીથી સેટ કરવા માટે આ લિંકને અનુસરો.",
"emails.recovery.footer": "જો તમે તમારો પાસવર્ડ ફરીથી સેટ કરવાનું ન કહ્યું હોય, તો તમે આ સંદેશને અવગણી શકો છો.",
"emails.recovery.thanks": "આભાર,",
"emails.recovery.buttonText": "પાસવર્ડ રીસેટ કરો",
"emails.recovery.signature": "{{project}} ટીમ",
"emails.invitation.subject": "%s ટીમને %s પર આમંત્રણ",
"emails.invitation.hello": "નમસ્કાર,",
"emails.invitation.body": "આ મેઇલ તમને મોકલવામાં આવ્યો હતો કારણ કે {{owner}} તમને {{project}} માં {{team}} ટીમના સભ્ય બનવા માટે આમંત્રિત કરવા માંગતા હતો.",
"emails.invitation.footer": "જો તમને રસ નથી, તો તમે આ સંદેશને અવગણી શકો છો.",
"emails.invitation.thanks": "આભાર,",
"emails.invitation.buttonText": "{{team}} નું આમંત્રણ સ્વીકારો",
"emails.invitation.signature": "{{project}} ટીમ",
"locale.country.unknown": "અજાણ",
"countries.af": "અફઘાનિસ્તાન",
-3
View File
@@ -8,7 +8,6 @@
"emails.verification.body": "לחץ על קישור זה כדי לאמת את כתובת הדוא\"ל שלך.",
"emails.verification.footer": "אם לא ביקשת לאמת כתובת זו, תוכל להתעלם מהודעה זו.",
"emails.verification.thanks": "תודה,",
"emails.verification.buttonText": "אשר כתובת דוא\"ל",
"emails.verification.signature": "צוות {{project}}",
"emails.magicSession.subject": "כניסה למערכת",
"emails.magicSession.hello": "שלום,",
@@ -21,14 +20,12 @@
"emails.recovery.body": "עקוב אחר קישור זה כדי לאפס את סיסמתך ב-{{project}}.",
"emails.recovery.footer": "אם לא ביקשת לאפס את הסיסמה, תוכל להתעלם מהודעה זו.",
"emails.recovery.thanks": "תודה,",
"emails.recovery.buttonText": "סיסמא איפוס",
"emails.recovery.signature": "צוות {{project}}",
"emails.invitation.subject": "הזמנה לצוות %s ב- %s",
"emails.invitation.hello": "שלום,",
"emails.invitation.body": "דואר זה נשלח אליך מכיוון ש {{owner}} רצה להזמין אותך להיות חבר בצוות {{team}} ב-{{project}}.",
"emails.invitation.footer": "אם אינך מעוניין, תוכל להתעלם מהודעה זו.",
"emails.invitation.thanks": "תודה,",
"emails.invitation.buttonText": "אשר הזמנה ל-{{team}}",
"emails.invitation.signature": "צוות {{project}}",
"locale.country.unknown": "לא ידוע",
"countries.af": "אפגניסטן",
-3
View File
@@ -8,7 +8,6 @@
"emails.verification.body": "इस लिंक के माध्यम से अपने ईमेल को सत्यापित कीजिये।",
"emails.verification.footer": "यदि आप इस पते को सत्यापित नहीं करना चाहते हैं, तो आप इस संदेश को नज़रअंदाज़ कर सकते हैं।",
"emails.verification.thanks": "धन्यवाद,",
"emails.verification.buttonText": "ईमेल पता सत्यापित करें",
"emails.verification.signature": "{{project}} टीम",
"emails.magicSession.subject": "लॉग इन",
"emails.magicSession.hello": "नमस्ते,",
@@ -21,14 +20,12 @@
"emails.recovery.body": "इस लिंक के माध्यम से अपना {{project}} पासवर्ड रीसेट करें।",
"emails.recovery.footer": "यदि आप अपना पासवर्ड रीसेट नहीं करना चाहते हैं, तो आप इस संदेश को नज़रअंदाज़ कर सकते हैं।",
"emails.recovery.thanks": "धन्यवाद,",
"emails.recovery.buttonText": "पासवर्ड रीसेट करें",
"emails.recovery.signature": "{{project}} टीम",
"emails.invitation.subject": "%s टीम का यहाँ %s पर आमंत्रण",
"emails.invitation.hello": "नमस्ते,",
"emails.invitation.body": "यह मेल आपको इसलिए भेजा गया है क्योंकि {{owner}} आपको {{team}} टीम का सदस्य बनाना चाहते है, जो {{project}} से जुड़ा हुआ है।",
"emails.invitation.footer": "यदि आप इसमें रूचि नहीं रखते, तो आप इस संदेश को नज़रअंदाज़ कर सकते हैं।",
"emails.invitation.thanks": "धन्यवाद,",
"emails.invitation.buttonText": "{{team}} का निमंत्रण स्वीकार करें",
"emails.invitation.signature": "{{project}} टीम",
"locale.country.unknown": "अज्ञात",
"countries.af": "अफ़ग़ानिस्तान",
-3
View File
@@ -8,7 +8,6 @@
"emails.verification.body": "Slijedite ovu poveznicu da biste potvrdili svoju adresu e-pošte.",
"emails.verification.footer": "Ukoliko niste zatražili potvrdu ove adrese, možete zanemariti ovu poruku.",
"emails.verification.thanks": "Hvala,",
"emails.verification.buttonText": "Potvrdi adresu e-pošte",
"emails.verification.signature": "{{project}} tim",
"emails.magicSession.subject": "Prijavite se",
"emails.magicSession.hello": "Pozdrav,",
@@ -21,14 +20,12 @@
"emails.recovery.body": "Slijedite ovu poveznicu za ponovno postavljanje {{project}} lozinke.",
"emails.recovery.footer": "Ako niste zatražili ponovno postavljanje Vaše lozinke, možete zanemariti ovu poruku.",
"emails.recovery.thanks": "Hvala,",
"emails.recovery.buttonText": "Resetiraj lozinku",
"emails.recovery.signature": "{{project}} tim",
"emails.invitation.subject": "Pozivnica za %s tim na %s",
"emails.invitation.hello": "Pozdrav,",
"emails.invitation.body": "Ova poruka Vam je poslana jer Vas je {{owner}} htio pozvati da postanete član {{team}} tima na {{project}}.",
"emails.invitation.footer": "Ukoliko niste zainteresirani, možete zanemariti ovu poruku.",
"emails.invitation.thanks": "Hvala,",
"emails.invitation.buttonText": "Prihvati pozivnicu za {{team}}",
"emails.invitation.signature": "{{project}} tim",
"locale.country.unknown": "Nepoznato",
"countries.af": "Afganistan",
-3
View File
@@ -8,7 +8,6 @@
"emails.verification.body": "Kattints a linkre, hogy megerősítsd az email címedet.",
"emails.verification.footer": "Ha nem te kérted a címed megerősítését, akkor nyugodtan hagyd figyelmen kívül ezt az üzenetet.",
"emails.verification.thanks": "Köszönettel,",
"emails.verification.buttonText": "E-mail-cím megerősítése",
"emails.verification.signature": "a {{project}} csapat",
"emails.magicSession.subject": "Bejelentkezés",
"emails.magicSession.hello": "Szia,",
@@ -21,14 +20,12 @@
"emails.recovery.body": "Kattints a linkre a {{project}} jelszavad visszaállításához.",
"emails.recovery.footer": "Ha nem te kezdeményezted a jelszavad visszaállítását, akkor nyugodtan hagyd figyelmen kívül ezt az üzenetet.",
"emails.recovery.thanks": "Köszönettel,",
"emails.recovery.buttonText": "Jelszó visszaállítása",
"emails.recovery.signature": "a {{project}} csapat",
"emails.invitation.subject": "Meghívó a(z) %s csapatba, a(z) %s projektbe",
"emails.invitation.hello": "Szia,",
"emails.invitation.body": "Ezt a levelet azért kaptad, mert {{owner}} meghívott, hogy légy a {{team}} csapat tagja a {{project}} projektben.",
"emails.invitation.footer": "Ha nem érdekel a lehetőség, nyugodtan hagyd figyelmen kívül ezt az üzenetet.",
"emails.invitation.thanks": "Köszönettel,",
"emails.invitation.buttonText": "Elfogadni meghívást a {{team}-re",
"emails.invitation.signature": "a {{project}} csapat",
"locale.country.unknown": "Ismeretlen",
"countries.af": "Afganisztán",
-3
View File
@@ -8,7 +8,6 @@
"emails.verification.body": "Ikuti tautan ini untuk memverifikasi alamat email Anda.",
"emails.verification.footer": "Jika Anda tidak meminta untuk memverifikasi alamat email ini, Anda dapat mengabaikan pesan ini.",
"emails.verification.thanks": "Terima kasih,",
"emails.verification.buttonText": "Konfirmasi alamat email",
"emails.verification.signature": "Tim {{project}}",
"emails.magicSession.subject": "Masuk",
"emails.magicSession.hello": "Hai,",
@@ -21,14 +20,12 @@
"emails.recovery.body": "Ikuti tautan ini untuk menyetel ulang kata sandi {{project}} Anda.",
"emails.recovery.footer": "Jika Anda tidak meminta untuk menyetel ulang kata sandi, Anda dapat mengabaikan pesan ini.",
"emails.recovery.thanks": "Terima kasih,",
"emails.recovery.buttonText": "Atur ulang kata sandi",
"emails.recovery.signature": "Tim {{project}}",
"emails.invitation.subject": "Undangan ke Tim %s di %s",
"emails.invitation.hello": "Halo,",
"emails.invitation.body": "Email ini dikirimkan kepada Anda karena {{owner}} ingin mengundang Anda untuk menjadi anggota tim {{team}} di {{project}}.",
"emails.invitation.footer": "Jika Anda tidak tertarik, Anda dapat mengabaikan pesan ini.",
"emails.invitation.thanks": "Terima kasih,",
"emails.invitation.buttonText": "Terima undangan ke {{team}}",
"emails.invitation.signature": "Tim {{project}}",
"locale.country.unknown": "Tidak diketahui",
"countries.af": "Afganistan",
-3
View File
@@ -8,7 +8,6 @@
"emails.verification.body": "Clicca questo link per verificare il tuo indirizzo email.",
"emails.verification.footer": "Se non hai richiesto la verifica dellindirizzo email, puoi ignorare questo messaggio.",
"emails.verification.thanks": "Grazie,",
"emails.verification.buttonText": "Confermare l'indirizzo email",
"emails.verification.signature": "Il team {{project}}",
"emails.magicSession.subject": "Login",
"emails.magicSession.hello": "Ciao,",
@@ -21,14 +20,12 @@
"emails.recovery.body": "Clicca questo link per reimpostare la tua password di {{project}}.",
"emails.recovery.footer": "Se non hai richiesto la reimpostazione della password, puoi ignorare questo messaggio.",
"emails.recovery.thanks": "Grazie,",
"emails.recovery.buttonText": "Reimposta password",
"emails.recovery.signature": "Il team {{project}}",
"emails.invitation.subject": "Invito al Team %s per %s",
"emails.invitation.hello": "Ciao,",
"emails.invitation.body": "Hai ricevuto questa email perché {{owner}} ti ha invitato a diventare un membro del team {{team}} di {{project}}.",
"emails.invitation.footer": "Ignora questo messaggio se non sei interessatə.",
"emails.invitation.thanks": "Grazie,",
"emails.invitation.buttonText": "Accetta invito a {{team}}",
"emails.invitation.signature": "Il team {{project}}",
"locale.country.unknown": "Sconosciuto",
"countries.af": "Afghanistan",
-3
View File
@@ -8,7 +8,6 @@
"emails.verification.body": "メールアドレスを有効化するためには下記リンクをクリックして下さい。",
"emails.verification.footer": "このメールに心当たりが無い場合は破棄をお願いいたします。",
"emails.verification.thanks": "ご利用いただきありがとうございます。、",
"emails.verification.buttonText": "メールアドレスを確認する",
"emails.verification.signature": "{{project}}チーム",
"emails.magicSession.subject": "ログイン",
"emails.magicSession.hello": "こんにちは、",
@@ -21,14 +20,12 @@
"emails.recovery.body": "パスワードをリセットするためには下記リンクをクリックしてください。",
"emails.recovery.footer": "このメールに心当たりが無い場合は破棄をお願いいたします。",
"emails.recovery.thanks": "ご利用いただきありがとうございます。、",
"emails.recovery.buttonText": "パスワードをリセット",
"emails.recovery.signature": "{{project}}チーム",
"emails.invitation.subject": "%sチームへの招待が%sから来ました。",
"emails.invitation.hello": "こんにちは、",
"emails.invitation.body": "{{owner}}さんが{{project}}の{{team}}チームにあなたを招待しています。",
"emails.invitation.footer": "このメールに心当たりが無い場合は破棄をお願いいたします。",
"emails.invitation.thanks": "ご利用いただきありがとうございます。、",
"emails.invitation.buttonText": "{{team}}への招待を承諾する",
"emails.invitation.signature": "{{project}}チーム",
"locale.country.unknown": "不明",
"countries.af": "アフガニスタン",
-3
View File
@@ -8,7 +8,6 @@
"emails.verification.body": "Klik link iki kanggo verifikasi alamat email sampeyan.",
"emails.verification.footer": "Yen sampeyan ora njaluk verifikasi alamat iki, sampeyan iso nglirwakake pesen iki.",
"emails.verification.thanks": "Matur nuwun,",
"emails.verification.buttonText": "Konfirmasi alamat email",
"emails.verification.signature": "Tim {{project}}",
"emails.magicSession.subject": "Masuk",
"emails.magicSession.hello": "Hai,",
@@ -21,14 +20,12 @@
"emails.recovery.body": "Klik link iki kanggo setel ulang sandi {{project}}.",
"emails.recovery.footer": "Yen sampeyan ora njaluk setel ulang sandi, sampeyan iso nglirwakake pesen iki.",
"emails.recovery.thanks": "Matur nuwun,",
"emails.recovery.buttonText": "Reset sandhi",
"emails.recovery.signature": "Tim {{project}}",
"emails.invitation.subject": "Undangan ke Tim %s di %s",
"emails.invitation.hello": "Halo,",
"emails.invitation.body": "Email iki dikirim menyang sampeyan amarga {{owner}} pengin ngajak sampeyan dadi anggota tim {{team}} di {{project}}.",
"emails.invitation.footer": "Yen sampeyan ora tertarik, sampeyan iso nglirwakake pesen iki.",
"emails.invitation.thanks": "Matur nuwun,",
"emails.invitation.buttonText": "Tampa undhangan menyang {{team}}",
"emails.invitation.signature": "Tim {{project}}",
"locale.country.unknown": "Ora dingerteni",
"countries.af": "Afghanistan",
-3
View File
@@ -8,7 +8,6 @@
"emails.verification.body": "ನಿಮ್ಮ ಇಮೇಲ್ ವಿಳಾಸ ಪರಿಶೀಲನೆಗೆ ಈ ಲಿಂಕನ್ನು ಅನುಸರಿಸಿ",
"emails.verification.footer": "ನೀವು ಇಮೇಲ್ ವಿಳಾಸ ಪರಿಶೀಲನೆಗೆ ಕೇಳದಿದ್ದರೆ, ಈ ಸಂದೇಶವನ್ನು ನಿರ್ಲಕ್ಷಿಸಿ",
"emails.verification.thanks": "ಧನ್ಯವಾದಗಳು,",
"emails.verification.buttonText": "ಇಮೇಲ್ ವಿಳಾಸವನ್ನು ದೃಢೀಕರಿಸಿ",
"emails.verification.signature": "{{project}} ತಂಡ",
"emails.magicSession.subject": "ಲಾಗಿನ್",
"emails.magicSession.hello": "ನಮಸ್ಕಾರ,",
@@ -21,14 +20,12 @@
"emails.recovery.body": "ನಿಮ್ಮ {{project}} ಗುಪ್ತಪದವನ್ನು ಮರುಹೊಂದಿಸಲು ಈ ಲಿಂಕನ್ನು ಅನುಸರಿಸಿ",
"emails.recovery.footer": "ನೀವು ಗುಪ್ತಪದವನ್ನು ಮರುಹೊಂದಿಸಲು ಕೇಳದಿದ್ದರೆ, ಈ ಸಂದೇಶವನ್ನು ನಿರ್ಲಕ್ಷಿಸಿ",
"emails.recovery.thanks": "ಧನ್ಯವಾದಗಳು,",
"emails.recovery.buttonText": "ಗುಪ್ತಪದವನ್ನು ಮರುಸೆಟ್ ಮಾಡಿ",
"emails.recovery.signature": "{{project}} ತಂಡ",
"emails.invitation.subject": "%s ತಂಡಕ್ಕೆ %s ರಲ್ಲಿ ಆಹ್ವಾನ",
"emails.invitation.hello": "ನಮಸ್ಕಾರ,",
"emails.invitation.body": "ಈ ಇಮೇಲ್ ನಿಮಗೆ ಬಂದಿದೆ ಏಕೆಂದರೆ {{owner}} ನಿಮ್ಮನ್ನು {{team}} ತಂಡದ {{project}}ರಲ್ಲಿ ಸದಸ್ಯ ಆಗಲಿಕ್ಕೆ ಆಹ್ವಾನಿಸಿದ್ದಾರೆ",
"emails.invitation.footer": "ನಿಮಗೆ ಆಸಕ್ತಿಯಿಲ್ಲದಿದ್ದರೆ, ಈ ಸಂದೇಶವನ್ನು ನಿರ್ಲಕ್ಷಿಸಿ",
"emails.invitation.thanks": "ಧನ್ಯವಾದಗಳು,",
"emails.invitation.buttonText": "{{team}} ಗೆ ಆಹ್ವಾನವನ್ನು ಸ್ವೀಕರಿಸಿ",
"emails.invitation.signature": "{{project}} ತಂಡ",
"locale.country.unknown": "Unknown",
"countries.af": "ಅಫ್ಘಾನಿಸ್ತಾನ",
-3
View File
@@ -8,7 +8,6 @@
"emails.verification.body": "이메일 인증을 위해 링크를 클릭하여주세요.",
"emails.verification.footer": "이메일 인증을 부탁하지 않으셨다면 이 메시지를 무시하여주세요.",
"emails.verification.thanks": "감사합니다、",
"emails.verification.buttonText": "이메일 주소를 확인합니다",
"emails.verification.signature": "{{project}} 팀",
"emails.magicSession.subject": "로그인",
"emails.magicSession.hello": "안녕하세요、",
@@ -21,14 +20,12 @@
"emails.recovery.body": "{{project}}의 비밀번호 재설정을 위해 링크를 클릭하여주세요.",
"emails.recovery.footer": "비밀번호 재설정 신청을 하지 않으셨다면 이 메세지를 무시하여주세요.",
"emails.recovery.thanks": "감사합니다、",
"emails.recovery.buttonText": "비밀번호 재설정",
"emails.recovery.signature": "{{project}} 팀",
"emails.invitation.subject": "초대장 %s 팀 - %s",
"emails.invitation.hello": "안녕하세요、",
"emails.invitation.body": "{{owner}}님이 귀하를 {{project}}의 {{team}} 팀으로 초대합니다.",
"emails.invitation.footer": "팀에 합류할 의사가 없으시면 이 메세지를 무시하여주세요.",
"emails.invitation.thanks": "감사합니다、",
"emails.invitation.buttonText": "{{team}} 초대를 수락하기",
"emails.invitation.signature": "{{project}} 팀",
"locale.country.unknown": "알려지지 않은",
"countries.af": "아프가니스탄",
-3
View File
@@ -8,7 +8,6 @@
"emails.verification.body": "Sequere hanc nexum ut quin inscriptionem tuum.",
"emails.verification.footer": "Si verificationem huius inscriptionis non postulasti, nuntium hunc ignorare potes.",
"emails.verification.thanks": "Gratias,",
"emails.verification.buttonText": "Confirma inscriptionem electronicam",
"emails.verification.signature": "{{project}} Team",
"emails.magicSession.subject": "Log in",
"emails.magicSession.hello": "Salve ibi,",
@@ -21,14 +20,12 @@
"emails.recovery.body": "Sequere hanc conjunctionem ut recipias project password {{project}}",
"emails.recovery.footer": "Si tesseram tuam recuperare non petis, nuntium hunc ignorare potes",
"emails.recovery.thanks": "Gratias,",
"emails.recovery.buttonText": "Reset password",
"emails.recovery.signature": "{{project}} team",
"emails.invitation.subject": "Invitatio pro %s in quadrigis %s",
"emails.invitation.hello": "Salve ibi,",
"emails.invitation.body": "Haec inscriptio ad te missa est quia dominus incepto {{owner}} te invitare vult ut membrum {{team}} quadrigis fias ad {{project}}",
"emails.invitation.footer": "Si non quaero, potes hunc nuntium ignorare",
"emails.invitation.thanks": "Gratias,",
"emails.invitation.buttonText": "Accipe invitare ad {{team}}",
"emails.invitation.signature": "{{project}} team",
"locale.country.unknown": "Ignotum",
"countries.af": "Afghanistan",
-3
View File
@@ -8,7 +8,6 @@
"emails.verification.body": "Follegt dëse Link fir Är E -Mail Adress z'iwwerpréiwen.",
"emails.verification.footer": "Wann Dir net gefrot hutt dës Adress z'iwwerpréiwen, kënnt Dir dëse Message ignoréieren.",
"emails.verification.thanks": "Merci,",
"emails.verification.buttonText": "E-Mail-Adress bestätegen",
"emails.verification.signature": "{{project}} équipe",
"emails.magicSession.subject": "Login",
"emails.magicSession.hello": "Hey,",
@@ -21,14 +20,12 @@
"emails.recovery.body": "Follegt dëse Link fir Äert {{project}} Passwuert zréckzesetzen.",
"emails.recovery.footer": "Wann Dir net gefrot hutt Äert Passwuert zréckzesetzen, kënnt Dir dëse Message ignoréieren.",
"emails.recovery.thanks": "Merci,",
"emails.recovery.buttonText": "Passwuert zrécksetzen",
"emails.recovery.signature": "{{project}} équipe",
"emails.invitation.subject": "Invitatioun un %s équipe bei %s",
"emails.invitation.hello": "Hallo,",
"emails.invitation.body": "Dës E -Mail gouf un Iech geschéckt well {{owner}} Iech invitéiere wëllt fir Member vum {{team}} Team bei {{project}} ze ginn.",
"emails.invitation.footer": "Wann Dir net interesséiert sidd, kënnt Dir dëse Message ignoréieren.",
"emails.invitation.thanks": "Merci,",
"emails.invitation.buttonText": "Invitatioun bei {{team}} akzeptéieren",
"emails.invitation.signature": "{{project}} équipe",
"locale.country.unknown": "Onbekannt",
"countries.af": "Afghanistan",
-3
View File
@@ -8,7 +8,6 @@
"emails.verification.body": "Spauskite šią nuorodą, kad patvirtintumėte savo el. paštą.",
"emails.verification.footer": "Jei neprašėte patvirtinti šio el. pašto, galite ignoruoti šį pranešimą.",
"emails.verification.thanks": "Ačiū,",
"emails.verification.buttonText": "Patvirtinti el. pašto adresą",
"emails.verification.signature": "{{project}} komanda",
"emails.magicSession.subject": "Prisijungti",
"emails.magicSession.hello": "Labas,",
@@ -21,14 +20,12 @@
"emails.recovery.body": "Spauskite šią nuorodą, kad atkurtumėte projekto {{project}} slaptažodį.",
"emails.recovery.footer": "Jei neprašėte atkurti savo slaptažodzio, galite ignoruoti šį pranešimą.",
"emails.recovery.thanks": "Ačiū,",
"emails.recovery.buttonText": "Atstatyti slaptažodį",
"emails.recovery.signature": "{{project}} komanda",
"emails.invitation.subject": "Pakvietimas į %s komandą %s projekte",
"emails.invitation.hello": "Labas,",
"emails.invitation.body": "Šis el. laiškas buvo atsiųstas jums, nes {{owner}} norėjo jus pakviesti tapti projekto {{project}} dalimi {{team}} komandoje.",
"emails.invitation.footer": "Jei jūsų tai nedomina, galite ignoruoti šį pranešimą.",
"emails.invitation.thanks": "Ačiū,",
"emails.invitation.buttonText": "Priimti kvietimą į {{team}}",
"emails.invitation.signature": "{{project}} komanda",
"locale.country.unknown": "Nežinoma",
"countries.af": "Afganistanas",
-3
View File
@@ -8,7 +8,6 @@
"emails.verification.body": "Sekojiet saitei, lai apstiprinātu savu e-pasta adresi.",
"emails.verification.footer": "Ja Jūs nepieprasījāt šīs adreses apstiprinājumu, lūdzu, ignorējiet šo ziņu.",
"emails.verification.thanks": "Paldies,",
"emails.verification.buttonText": "Apstiprināt e-pasta adresi",
"emails.verification.signature": "{{project}} komanda",
"emails.magicSession.subject": "Ieiet",
"emails.magicSession.hello": "Sveicināti,",
@@ -21,14 +20,12 @@
"emails.recovery.body": "Sekojiet saitei, lai atjauninātu {{project}} paroli.",
"emails.recovery.footer": "Ja Jūs nepieprasījāt paroles atjaunināšanu, lūdzu, ignorējiet šo ziņu.",
"emails.recovery.thanks": "Paldies,",
"emails.recovery.buttonText": "Atiestatīt paroli",
"emails.recovery.signature": "{{project}} komanda",
"emails.invitation.subject": "Ielūgums piebiedroties %s komandai %s projektā.",
"emails.invitation.hello": "Labdien,",
"emails.invitation.body": "Šis e-pasts tika nosūtīts Jums, jo {{owner}} vēlējās Jūs ielūgt kļūt par {{team}} komandas biedru {{project}} projektā.",
"emails.invitation.footer": "Ja Jūs neesat ieinteresēts, lūdzu, ignorējiet šo ziņu.",
"emails.invitation.thanks": "Paldies,",
"emails.invitation.buttonText": "Pieņemt ielūgumu uz {{team}}",
"emails.invitation.signature": "{{project}} komanda",
"locale.country.unknown": "Nav zināms",
"countries.af": "Afganistāna",
-3
View File
@@ -8,7 +8,6 @@
"emails.verification.body": "നിങ്ങളുടെ ഇമെയിൽ വിലാസം സ്ഥിരീകരിക്കുന്നതിനായി ഈ ലിങ്ക് പിന്തുടരുക.",
"emails.verification.footer": "ഈ വിലാസം സ്ഥിരീകരിക്കാന്‍ നിങ്ങൾ ആവശ്യപ്പെട്ടില്ലെങ്കിൽ, നിങ്ങൾക്ക് ഈ സന്ദേശം അവഗണിക്കാവുന്നതാണ്.",
"emails.verification.thanks": "നന്ദി,",
"emails.verification.buttonText": "ഇമെയിൽ വിലാസം സ്ഥിരീകരിക്കുക",
"emails.verification.signature": "{{project}} ടീം",
"emails.magicSession.subject": "ലോഗിൻ",
"emails.magicSession.hello": "നമസ്കാരം,",
@@ -21,14 +20,12 @@
"emails.recovery.body": "നിങ്ങളുടെ {{Project}} രഹസ്യവാക്ക് പുനക്രമീകരിക്കുന്നതിന് ഈ ലിങ്ക് പിന്തുടരുക.",
"emails.recovery.footer": "നിങ്ങളുടെ രഹസ്യവാക്ക് പുനക്രമീകരിക്കാന്‍ നിങ്ങൾ ആവശ്യപ്പെട്ടില്ലെങ്കിൽ, ഈ സന്ദേശം അവഗണിക്കാവുന്നതാണ്.",
"emails.recovery.thanks": "നന്ദി,",
"emails.recovery.buttonText": "പാസ്‌വേഡ് റീസെറ്റ് ചെയ്യുക",
"emails.recovery.signature": "{{project}} ടീം",
"emails.invitation.subject": "%s -ലെ %s ടീമിലേക്കുള്ള ക്ഷണം",
"emails.invitation.hello": "നമസ്കാരം,",
"emails.invitation.body": "നിങ്ങളെ {{project}} -ലെ {{team}} ടീമിലെ അംഗമാകുവാന്‍ ക്ഷണിക്കാൻ {{owner}} ആഗ്രഹിക്കുന്നതിനാലാണ് ഈ മെയിൽ നിങ്ങൾക്ക് അയക്കുന്നത്.",
"emails.invitation.footer": "നിങ്ങൾക്ക് താൽപ്പര്യമില്ലെങ്കിൽ, ഈ സന്ദേശം അവഗണിക്കാവുന്നതാണ്.",
"emails.invitation.thanks": "നന്ദി,",
"emails.invitation.buttonText": "{{team}} ലേക്കുള്ള ക്ഷണം സ്വീകരിക്കുക",
"emails.invitation.signature": "{{project}} ടീം",
"locale.country.unknown": "Unknown",
"countries.af": "അഫ്ഗാനിസ്ഥാൻ",
-3
View File
@@ -8,7 +8,6 @@
"emails.verification.body": "आपला ईमेल पत्ता सत्यापित करण्यासाठी या दुव्याचे अनुसरण करा.",
"emails.verification.footer": "आपण या पत्त्याची पडताळणी करण्यास सांगितले नसल्यास, आपण या संदेशाकडे दुर्लक्ष करू शकता.",
"emails.verification.thanks": "धन्यवाद,",
"emails.verification.buttonText": "ईमेल पत्ता सत्यापित करा",
"emails.verification.signature": "{{project}} संघ",
"emails.magicSession.subject": "लॉगिन करा",
"emails.magicSession.hello": "नमस्कार ,",
@@ -21,14 +20,12 @@
"emails.recovery.body": "आपला {{project}}चे पासवर्ड रीसेट करण्यासाठी या लिंकचे अनुसरण करा",
"emails.recovery.footer": "आपण आपला पासवर्ड रीसेट करण्यास सांगितले नसल्यास, आपण या संदेशाकडे दुर्लक्ष करू शकता.",
"emails.recovery.thanks": "धन्यवाद,",
"emails.recovery.buttonText": "पासवर्ड रीसेट करा",
"emails.recovery.signature": "{{project}} संघ",
"emails.invitation.subject": "%s संघ %s येथे सामील होण्यासाठी आमंत्रण",
"emails.invitation.hello": "नमस्कार,",
"emails.invitation.body": "हा मेल तुम्हाला पाठवला होता कारण {{owner}} तुम्हाला {{project}} येथे {{team}} टीमचे सदस्य होण्यासाठी आमंत्रित करू इच्छित होते.",
"emails.invitation.footer": "आपल्याला स्वारस्य नसल्यास, आपण या संदेशाकडे दुर्लक्ष करू शकता.",
"emails.invitation.thanks": "धन्यवाद,",
"emails.invitation.buttonText": "{{team}} साठी आमंत्रण स्वीकारा",
"emails.invitation.signature": "{{project}} संघ",
"locale.country.unknown": "अज्ञात",
"countries.af": "अफगानिस्तान",
-3
View File
@@ -8,7 +8,6 @@
"emails.verification.body": "Tekan pautan ini untuk mengesahkan alamat email anda.",
"emails.verification.footer": "Sekiranya anda tidak membuat permintaan untuk mengesahkan email ini, sila abaikan mesej ini.",
"emails.verification.thanks": "Terima kasih,",
"emails.verification.buttonText": "Sahkan alamat email",
"emails.verification.signature": "{{project}} team",
"emails.magicSession.subject": "Log masuk",
"emails.magicSession.hello": "Hey,",
@@ -21,14 +20,12 @@
"emails.recovery.body": "Tekan pautan ini untuk menetapkan semula kata laluan {{project}}.",
"emails.recovery.footer": "Sekiranya anda tidak membuat permintaan menetap semula kata laluan, sila abaikan mesej ini.",
"emails.recovery.thanks": "Terima kasih,",
"emails.recovery.buttonText": "Tetapkan semula kata laluan",
"emails.recovery.signature": "{{project}} team",
"emails.invitation.subject": "Jemputan ke pasukan %s di %s",
"emails.invitation.hello": "Hello,",
"emails.invitation.body": "Anda menerima mel ini kerana {{owner}} ingin menjemput anda untuk menjadi ahli pasukan {{team}} di {{project}}.",
"emails.invitation.footer": "Sekiranya anda tidak berminat, sila abaikan mesej ini.",
"emails.invitation.thanks": "Terima kasih,",
"emails.invitation.buttonText": "Terima jemputan ke {{team}}",
"emails.invitation.signature": "{{project}} team",
"locale.country.unknown": "Tidak Diketahui",
"countries.af": "Afghanistan",
-3
View File
@@ -8,7 +8,6 @@
"emails.verification.body": "Følg denne lenken for å bekrefte din e-postadresse.",
"emails.verification.footer": "Dersom du ikke ba om å bekrefte e-postadressen, kan du se bort fra denne meldingen.",
"emails.verification.thanks": "Takk,",
"emails.verification.buttonText": "Bekreft e-postadresse",
"emails.verification.signature": "{{project}} team",
"emails.magicSession.subject": "Pålogging",
"emails.magicSession.hello": "Hei,",
@@ -21,14 +20,12 @@
"emails.recovery.body": "Følg denne lenken for å nullstille ditt {{project}} passord.",
"emails.recovery.footer": "Dersom du ikke ba om å nullstille passordet ditt, kan du se bort fra denne meldingen.",
"emails.recovery.thanks": "Takk,",
"emails.recovery.buttonText": "Tilbakestill passord",
"emails.recovery.signature": "{{project}} team",
"emails.invitation.subject": "Invitasjon til %s Team ved %s",
"emails.invitation.hello": "Hei,",
"emails.invitation.body": "Denne meldingen ble sendt til deg fordi {{owner}} ønsket å invitere deg til å bli medlem av {{team}} team ved {{project}}.",
"emails.invitation.footer": "Dersom du ikke er interessert, kan du se bort fra denne meldingen.",
"emails.invitation.thanks": "Takk,",
"emails.invitation.buttonText": "Godta invitasjon til {{team}}",
"emails.invitation.signature": "{{project}} team",
"locale.country.unknown": "Ukjent",
"countries.af": "Afghanistan",
-3
View File
@@ -8,7 +8,6 @@
"emails.verification.body": "इमेल ठेगाना प्रमाणित गर्नको लागी यो लिंकमा जानुहोस।",
"emails.verification.footer": "यदि तपाइँले आफ्नो खाता प्रमाणित गर्न सोध्नु भएको छैन भने तपाइँले यो सन्देश लाई बेवास्ता गर्न सक्नुहुन्छ।",
"emails.verification.thanks": "धन्यवाद,",
"emails.verification.buttonText": "इमेल ठेगाना पुष्टि गर्नुहोस्",
"emails.verification.signature": "{{project}} समूह",
"emails.magicSession.subject": "लगइन",
"emails.magicSession.hello": "नमस्ते,",
@@ -21,14 +20,12 @@
"emails.recovery.body": "{{project}}को पासवर्ड रिसेट गर्नको लागी यो लिंकमा जानुहोस।",
"emails.recovery.footer": "यदि तपाइँले आफ्नो पासवर्ड रिसेट गर्न सोध्नु भएको छैन भने तपाइँले यो सन्देश लाई बेवास्ता गर्न सक्नुहुन्छ।",
"emails.recovery.thanks": "धन्यवाद,",
"emails.recovery.buttonText": "रिसेट पासवर्ड",
"emails.recovery.signature": "{{project}} समूह",
"emails.invitation.subject": "%s समूहको लागि %s मा निमन्त्रणा",
"emails.invitation.hello": "नमस्ते,",
"emails.invitation.body": "{{owner}}ले तपाइँलाई {{project}}मा {{team}}को सदस्य बन्न आमन्त्रित गर्न चाहनु भएको छ। त्येसैले तपाइँलाई यो सन्देश पठाइएको हो।",
"emails.invitation.footer": "यदि तपाइँ इच्छुक हुनुहुन्न भने, तपाइँले यो सन्देशलाई बेवास्ता गर्न सक्नुहुन्छ।",
"emails.invitation.thanks": "धन्यवाद,",
"emails.invitation.buttonText": "{{team}} मा निमन्त्रणा स्वीकार गर्नुहोस्",
"emails.invitation.signature": "{{project}} समूह",
"locale.country.unknown": "अज्ञात",
"countries.af": "अफगानिस्तान",
-3
View File
@@ -8,7 +8,6 @@
"emails.verification.body": "Volg deze link om uw e-mail te verifieren",
"emails.verification.footer": "Als u geen aanvraag voor verificatie heeft gemaakt, kan u deze mail negeren",
"emails.verification.thanks": "Bedankt,",
"emails.verification.buttonText": "Bevestig e-mailadres",
"emails.verification.signature": "{{project}} team",
"emails.magicSession.subject": "Login",
"emails.magicSession.hello": "Hoi,",
@@ -21,14 +20,12 @@
"emails.recovery.body": "Volg deze link om het wachtwoord van uw project {{project}} te wijzigen",
"emails.recovery.footer": "Als u geen aanvraag heeft gemaakt om uw wachtwoord te wijzigen, kan u deze mail negeren",
"emails.recovery.thanks": "Bedankt,",
"emails.recovery.buttonText": "Wachtwoord opnieuw instellen",
"emails.recovery.signature": "{{project}} team",
"emails.invitation.subject": "Uitnodiging van %s Team uit %s",
"emails.invitation.hello": "Hallo,",
"emails.invitation.body": "U ontvangt deze mail want u was uitgenodig door {{owner}} om lid van het {{team}} team te worden in {{project}} ",
"emails.invitation.footer": "Als u niet geintereseerd bent, kan u deze mail negeren.",
"emails.invitation.thanks": "Bedankt,",
"emails.invitation.buttonText": "Uitnodiging voor {{team}} accepteren",
"emails.invitation.signature": "{{project}} team",
"locale.country.unknown": "Onbekend",
"countries.af": "Afghanistan",
-3
View File
@@ -8,7 +8,6 @@
"emails.verification.body": "Følg denne lenkja for å bekrefta din e-postadresse.",
"emails.verification.footer": "Om du ikkje bad om å bekrefta e-postadressa, kan du ignorera denne meldinga.",
"emails.verification.thanks": "Takk,",
"emails.verification.buttonText": "Stadfest e-postadresse",
"emails.verification.signature": "{{project}} team",
"emails.magicSession.subject": "Pålogging",
"emails.magicSession.hello": "Hei,",
@@ -21,14 +20,12 @@
"emails.recovery.body": "Følg denne lenkja for å nullstilla ditt {{project}} passord.",
"emails.recovery.footer": "Om du ikkje ba om å nullstilla passordet ditt, kan du ignorera denne meldinga.",
"emails.recovery.thanks": "Takk,",
"emails.recovery.buttonText": "Nullstill passord",
"emails.recovery.signature": "{{project}} team",
"emails.invitation.subject": "Innbyding til %s Team ved %s",
"emails.invitation.hello": "Hallo,",
"emails.invitation.body": "Denne meldinga ble sendt til deg fordi {{owner}} ynskja å invitera deg til å bli medlem av {{team}} team i {{project}}.",
"emails.invitation.footer": "Om du ikkje er interessert, kan du ignorera denne meldinga.",
"emails.invitation.thanks": "Takk,",
"emails.invitation.buttonText": "Godta invitasjon til {{team}}",
"emails.invitation.signature": "{{project}} team",
"locale.country.unknown": "Ukjend",
"countries.af": "Afghanistan",
-3
View File
@@ -8,7 +8,6 @@
"emails.verification.body": "ଆପଣଙ୍କର ଇମେଲ୍ ଠିକଣା ଯାଞ୍ଚ କରିବାକୁ ଏହି ଲିଙ୍କ୍ ଅନୁସରଣ କରନ୍ତୁ |",
"emails.verification.footer": "ଯଦି ଆପଣ ଏହି ଠିକଣା ଯାଞ୍ଚ କରିବାକୁ କହି ନାହାଁନ୍ତି, ତେବେ ଆପଣ ଏହି ସନ୍ଦେଶକୁ ଉପେକ୍ଷା କରିପାରିବେ |",
"emails.verification.thanks": "ଧନ୍ୟବାଦ,",
"emails.verification.buttonText": "ଇମେଲ ଠିକଣା ନିଶ୍ଚିତ କରନ୍ତୁ",
"emails.verification.signature": "{{project}} ଦଳ",
"emails.magicSession.subject": "ଲଗଇନ୍ କରନ୍ତୁ",
"emails.magicSession.hello": "ନମସ୍କାର,",
@@ -21,14 +20,12 @@
"emails.recovery.body": "ଆପଣଙ୍କର {{project}} ପାସୱାର୍ଡ ପୁନଃ ସେଟ୍ କରିବାକୁ ଏହି ଲିଙ୍କକୁ ଅନୁସରଣ କରନ୍ତୁ |",
"emails.recovery.footer": "ଯଦି ଆପଣ ଆପଣଙ୍କର ପାସୱାର୍ଡ ପୁନଃ ସେଟ୍ କରିବାକୁ କହି ନାହାଁନ୍ତି, ତେବେ ଆପଣ ଏହି ସନ୍ଦେଶକୁ ଉପେକ୍ଷା କରିପାରିବେ |",
"emails.recovery.thanks": "ଧନ୍ୟବାଦ,",
"emails.recovery.buttonText": "ପାସୱାର୍ଡ ପୁନଃସେଟ୍ କରନ୍ତୁ",
"emails.recovery.signature": "{{project}} ଦଳ",
"emails.invitation.subject": "%s ରେ %s ଦଳକୁ ନିମନ୍ତ୍ରଣ |",
"emails.invitation.hello": "ନମସ୍କାର,",
"emails.invitation.body": "ଏହି ମେଲ୍ ଆପଣଙ୍କୁ ପଠାଯାଇଥିଲା କାରଣ {{owner}} ଆପଣଙ୍କୁ {{project} ରେ {{team}} ଦଳର ସଦସ୍ୟ ହେବାକୁ ଆମନ୍ତ୍ରଣ କରିବାକୁ ଚାହୁଁଥିଲେ |",
"emails.invitation.footer": "ଯଦି ଆପଣ ଆଗ୍ରହୀ ନୁହଁନ୍ତି, ଆପଣ ଏହି ସନ୍ଦେଶକୁ ଅଣଦେଖା କରିପାରିବେ |",
"emails.invitation.thanks": "ଧନ୍ୟବାଦ,",
"emails.invitation.buttonText": "{{team}} ପାଇଁ ଆମନ୍ତ୍ରଣ ଗ୍ରହଣ କରନ୍ତୁ",
"emails.invitation.signature": "{{project}} ଦଳ",
"locale.country.unknown": "ଅଜ୍ଞାତ",
"countries.af": "ଆଫଗାନିସ୍ତାନ",
-3
View File
@@ -8,7 +8,6 @@
"emails.verification.body": "Przejdź do tego linku, aby zweryfikować swój adres e-mail.",
"emails.verification.footer": "Jeśli to nie Ty prosiłeś o zweryfikowanie tego adresu, zignoruj tę wiadomość.",
"emails.verification.thanks": "Dziękujemy,",
"emails.verification.buttonText": "Potwierdź adres e-mail",
"emails.verification.signature": "Zespół {{project}}",
"emails.magicSession.subject": "Logowanie",
"emails.magicSession.hello": "Cześć,",
@@ -21,14 +20,12 @@
"emails.recovery.body": "Przejdź do tego linku, aby zresetować hasło dla {{project}}.",
"emails.recovery.footer": "Jeśli to nie Ty prosiłeś o zresetowanie swojego hasła, zignoruj tę wiadomość.",
"emails.recovery.thanks": "Dziękujemy,",
"emails.recovery.buttonText": "Zresetuj hasło",
"emails.recovery.signature": "Zespół {{project}}",
"emails.invitation.subject": "Zaproszenie do zespołu %s w %s",
"emails.invitation.hello": "Cześć,",
"emails.invitation.body": "Otrzymujesz tę wiadomość, ponieważ {{owner}} zaprasza Cię do grona członków zespołu {{team}} w projekcie {{project}}.",
"emails.invitation.footer": "Jeśli nie jesteś zainteresowany, zignoruj tę wiadomość.",
"emails.invitation.thanks": "Dziękujemy,",
"emails.invitation.buttonText": "Zaakceptuj zaproszenie do {{team}}",
"emails.invitation.signature": "Zespół {{project}}",
"locale.country.unknown": "Nieznany",
"countries.af": "Afganistan",
@@ -8,7 +8,6 @@
"emails.verification.body": "Clique neste link para verificar o seu endereço de e-mail.",
"emails.verification.footer": "Se você não solicitou a verificação deste e-mail, ignore essa mensagem.",
"emails.verification.thanks": "Muito obrigado,",
"emails.verification.buttonText": "Confirmar endereço de e-mail",
"emails.verification.signature": "Time {{project}}",
"emails.magicSession.subject": "Login",
"emails.magicSession.hello": "Olá,",
@@ -21,14 +20,12 @@
"emails.recovery.body": "Clique neste link para redefinir sua senha do {{project}}.",
"emails.recovery.footer": "Se você não solicitou a redefinição da sua senha, você pode ignorar essa mensagem.",
"emails.recovery.thanks": "Muito obrigado,",
"emails.recovery.buttonText": "Redefinir senha",
"emails.recovery.signature": "Time {{project}}",
"emails.invitation.subject": "Convite para o Time %s em %s",
"emails.invitation.hello": "Olá,",
"emails.invitation.body": "Este e-mail foi enviado porque {{owner}} deseja convidar você a se tornar membro do Time {{team}} em {{project}}.",
"emails.invitation.footer": "Caso não tenha interesse, ignore essa mensagem.",
"emails.invitation.thanks": "Muito obrigado,",
"emails.invitation.buttonText": "Aceitar convite para {{team}}",
"emails.invitation.signature": "Time {{project}}",
"locale.country.unknown": "Desconhecido",
"countries.af": "Afeganistão",
@@ -8,7 +8,6 @@
"emails.verification.body": "Siga esta ligação para verificar o seu endereço de correio electrónico.",
"emails.verification.footer": "Se não pediu para verificar este endereço, pode ignorar esta mensagem.",
"emails.verification.thanks": "Obrigado,",
"emails.verification.buttonText": "Confirmar endereço de email",
"emails.verification.signature": "Equipa {{project}}",
"emails.magicSession.subject": "Login",
"emails.magicSession.hello": "Olá ,",
@@ -21,14 +20,12 @@
"emails.recovery.body": "Utilize este link para redefinir a palavra-passe do seu projecto {{project}}",
"emails.recovery.footer": "Se não pediu para redefinir a sua palavra-passe, pode ignorar esta mensagem.",
"emails.recovery.thanks": "Obrigado,",
"emails.recovery.buttonText": "Repor palavra-passe",
"emails.recovery.signature": "Equipa {{project}}",
"emails.invitation.subject": "Convite à equipa de %s às %s",
"emails.invitation.hello": "Olá,",
"emails.invitation.body": "Este correio foi-lhe enviado porque {{owner}} queria convidá-lo a tornar-se membro da equipa {{team}} da {{project}}.",
"emails.invitation.footer": "Se não estiver interessado, pode ignorar esta mensagem.",
"emails.invitation.thanks": "Obrigado,",
"emails.invitation.buttonText": "Aceitar convite para o {{team}}",
"emails.invitation.signature": "Equipa {{project}}",
"locale.country.unknown": "Desconhecido",
"countries.af": "Afeganistão",
-3
View File
@@ -8,7 +8,6 @@
"emails.verification.body": "Click pe acest link pentru a valida adresa de email.",
"emails.verification.footer": "Dacă nu ai cerut validarea adresei de email, poți ignora acest mesaj.",
"emails.verification.thanks": "Mulțumim,",
"emails.verification.buttonText": "Confirmă adresa de email",
"emails.verification.signature": "Echipa {{project}}",
"emails.magicSession.subject": "Login",
"emails.magicSession.hello": "Bună ziua,",
@@ -21,14 +20,12 @@
"emails.recovery.body": "Click aici pentru a reseta parola pentru {{project}}",
"emails.recovery.footer": "Dacă nu ai cerut să îți schimbi parola, ignoră acest mesaj.",
"emails.recovery.thanks": "Mulțumim,",
"emails.recovery.buttonText": "Resetează parola",
"emails.recovery.signature": "Echipa {{project}}",
"emails.invitation.subject": "Invitatie catre %s Echipa la %s",
"emails.invitation.hello": "Bună ziua,",
"emails.invitation.body": "Acest email a fost trimis pentru că {{owner}} a vrut ca tu să devii membru al echipei {{team}} la {{project}}.",
"emails.invitation.footer": "Dacă nu esti interesat, poți ignora acest email.",
"emails.invitation.thanks": "Mulțumim,",
"emails.invitation.buttonText": "Acceptă invitația la {{team}}",
"emails.invitation.signature": "Echipa {{project}}",
"locale.country.unknown": "Necunoscut",
"countries.af": "Afghanistan",
-3
View File
@@ -8,7 +8,6 @@
"emails.verification.body": "Перейдите по ссылке, чтобы подтвердить свой адрес электронной почты.",
"emails.verification.footer": "Если вы не запрашивали подтверждение этого адреса, проигнорируйте это сообщение.",
"emails.verification.thanks": "Спасибо,",
"emails.verification.buttonText": "Подтвердить адрес электронной почты",
"emails.verification.signature": "команда {{project}}",
"emails.magicSession.subject": "Логин",
"emails.magicSession.hello": "Здравствуйте,",
@@ -21,14 +20,12 @@
"emails.recovery.body": "Перейдите по этой ссылке для того чтобы сбросить свой пароль для проекта {{project}}",
"emails.recovery.footer": "Если вы не запрашивали сброс пароля, проигнорируйте это сообщение.",
"emails.recovery.thanks": "Спасибо,",
"emails.recovery.buttonText": "Сбросить пароль",
"emails.recovery.signature": "команда {{project}}",
"emails.invitation.subject": "Приглашение в команду %s по проекту %s",
"emails.invitation.hello": "Здравствуйте,",
"emails.invitation.body": "Это письмо отправлено вам, потому что {{owner}} приглашает стать членом команды {{team}} в проекте {{project}}.",
"emails.invitation.footer": "Если вы не заинтересованы, проигнорируйте это сообщение.",
"emails.invitation.thanks": "Спасибо,",
"emails.invitation.buttonText": "Принять приглашение в {{team}}",
"emails.invitation.signature": "команда {{project}}",
"locale.country.unknown": "Неизвестно",
"countries.af": "Афганистан",
-3
View File
@@ -8,7 +8,6 @@
"emails.verification.body": "ई-पत्रनिर्णायनार्थमिदं संयोगसूत्रमनुसरतु।",
"emails.verification.footer": "यदि अस्य संकेतस्य निर्णायनं नेष्यते तर्हि वात्र्तामिमामुपेक्षताम्‌।",
"emails.verification.thanks": "धन्यवादः,",
"emails.verification.buttonText": "ईमेल-पत्त्रं सुनिश्चित करें",
"emails.verification.signature": "{{project}} गणः",
"emails.magicSession.subject": "संप्रवेशः",
"emails.magicSession.hello": "अयि,",
@@ -21,14 +20,12 @@
"emails.recovery.body": "{{project}} कूटशब्दपुनयाेजनाय संयोगमेनमनुसरतु।",
"emails.recovery.footer": "यदि कूटशब्दस्य पुनयाेजनं नेष्यते तर्हि वात्र्तामिमामुपेक्षताम्‌।",
"emails.recovery.thanks": "धन्यवादः,",
"emails.recovery.buttonText": "गुप्तशब्दं पुनः स्थापित करें",
"emails.recovery.signature": "{{project}} गणः",
"emails.invitation.subject": "गणस्य आमन्त्रणम्‌ %s इति %s",
"emails.invitation.hello": "अयि भो,",
"emails.invitation.body": "{{owner}} {{team}} गणे {{project}} मध्ये भवद्योगदानमच्छितीति हेतोः पत्रमदिं भवत्सकाशं प्रेषतिम्।",
"emails.invitation.footer": "यदि भवदनिच्छा तर्हि वात्र्तामिमामुपेक्षताम्‌।",
"emails.invitation.thanks": "धन्यवादः,",
"emails.invitation.buttonText": "{{team}} निमन्त्रणं स्वीकुरुत",
"emails.invitation.signature": "{{project}} गणः",
"locale.country.unknown": "अज्ञातम्‌ ",
"countries.af": "आफगानिस्थानम्‌",
-3
View File
@@ -8,7 +8,6 @@
"emails.verification.body": "پنھنجي اي ميل ايڊريس جي تصديق ڪرڻ لاءِ ھن لنڪ تي عمل ڪريو.",
"emails.verification.footer": "جيڪڏھن توھان نه پ askيا ھئا ھن ايڊريس جي تصديق ڪرڻ لاءِ ، توھان نظر انداز ڪري سگھوٿا ھن پيغام کي.",
"emails.verification.thanks": "مهرباني,",
"emails.verification.buttonText": "اي ميل پتو تصديق ڪريو",
"emails.verification.signature": "{{project}} ٽيم",
"emails.magicSession.subject": "لاگ ان",
"emails.magicSession.hello": "هي ,",
@@ -21,14 +20,12 @@
"emails.recovery.body": "ھن لنڪ تي عمل ڪريو پنھنجو {{project}} پاسورڊ ري سيٽ ڪرڻ لاءِ.",
"emails.recovery.footer": "جيڪڏھن توھان نه پ پيو ھو پنھنجي پاسورڊ کي ري سيٽ ڪرڻ لاءِ ، توھان نظر انداز ڪري سگھوٿا ھن پيغام کي.",
"emails.recovery.thanks": "مهرباني,",
"emails.recovery.buttonText": "پاسورڊ ري سيٽ ڪريو",
"emails.recovery.signature": "{{project}} ٽيم",
"emails.invitation.subject": "%s ٽيم %s تيجي دعوت",
"emails.invitation.hello": "هيلو,",
"emails.invitation.body": "ھي اي ميل توھان ڏانھن موڪليو ويو آھي {اڪاڻ ته {{owner}} توھان کي دعوت ڏيڻ چاھي ٿو ته توھان {{team}} ٽيم جو ميمبر بڻجي {{project}} تي.",
"emails.invitation.footer": "جيڪڏھن توھان دلچسپي نٿا رکو ، توھان نظر انداز ڪري سگھوٿا ھن پيغام کي.",
"emails.invitation.thanks": "مهرباني,",
"emails.invitation.buttonText": "{{team}} جي دعوت قبول ڪريو",
"emails.invitation.signature": "{{project}} ٽيم",
"locale.country.unknown": "نامعلوم",
"countries.af": "افغانستان",
-3
View File
@@ -8,7 +8,6 @@
"emails.verification.body": "ඔබගේ විද්‍යුත් තැපැල් ලිපිනය සත්‍යාපනය කිරීමට මෙම සම්බන්ධකය අනුගමනය කරන්න.",
"emails.verification.footer": "මෙම ලිපිනය සත්‍යාපනය කරන ලෙස ඔබ ඉල්ලුවේ නැත්නම්, ඔබට මෙම පණිවිඩය නොසලකා හැරිය හැක.",
"emails.verification.thanks": "ස්තුතියි,",
"emails.verification.buttonText": "ඊමේල් ලිපිනය තහවුරු කරන්න",
"emails.verification.signature": "{{project}} කණ්ඩායම",
"emails.magicSession.subject": "ප්‍රවේශ වන්න",
"emails.magicSession.hello": "හේයි,",
@@ -21,14 +20,12 @@
"emails.recovery.body": "ඔබගේ {{project}} මුරපදය නැවත සැකසීමට මෙම සම්බන්ධකය අනුගමනය කරන්න.",
"emails.recovery.footer": "ඔබගේ මුරපදය නැවත සකසන ලෙස ඔබ ඉල්ලුවේ නැත්නම්, ඔබට මෙම පණිවිඩය නොසලකා හැරිය හැක.",
"emails.recovery.thanks": "ස්තුතියි,",
"emails.recovery.buttonText": "මුරපදය යළි පිහිටුවන්න",
"emails.recovery.signature": "{{project}} කණ්ඩායම",
"emails.invitation.subject": "%s කණ්ඩායමට ආරාධනා %s හි",
"emails.invitation.hello": "ආයුබෝවන්,",
"emails.invitation.body": "මෙම තැපැල් ඔබට එව්වේ, {{owner}} හට {{project}} හි {{team}} කණ්ඩායමේ සාමාජිකයෙකු වීමට ඔබට ආරාධනා කිරීමට අවශ්‍ය වූ බැවිනි.",
"emails.invitation.footer": "ඔබ උනන්දුවක් නොදක්වන්නේ නම්, ඔබට මෙම පණිවිඩය නොසලකා හැරිය හැක.",
"emails.invitation.thanks": "ස්තුතියි,",
"emails.invitation.buttonText": "{{team}} සඳහා ආරාධනය පිළිගෙනින්න",
"emails.invitation.signature": "{{project}} කණ්ඩායම",
"locale.country.unknown": "නොදන්නා",
"countries.af": "ඇෆ්ගනිස්ථානය",
-3
View File
@@ -8,7 +8,6 @@
"emails.verification.body": "Použi tento link pre overenie svojej emailovej adresy.",
"emails.verification.footer": "Ak si nepožiadal o overenie tejto adresy, môžeš túto správu ignorovať.",
"emails.verification.thanks": "Ďakujeme.,",
"emails.verification.buttonText": "Potvrďte e-mailovú adresu",
"emails.verification.signature": "{{project}} tím",
"emails.magicSession.subject": "Prihlásenie",
"emails.magicSession.hello": "Ahoj,",
@@ -21,14 +20,12 @@
"emails.recovery.body": "Použi tento link pre obnovenie svojho {{project}} hesla.",
"emails.recovery.footer": "Ak si nepožiadal o obnovu svojho hesla, túto správu môžeš ignorovať.",
"emails.recovery.thanks": "Ďakujeme,",
"emails.recovery.buttonText": "Obnoviť heslo",
"emails.recovery.signature": "{{project}} tím",
"emails.invitation.subject": "Pozvánka do %s Tímu v %s",
"emails.invitation.hello": "Ahoj,",
"emails.invitation.body": "Tento email ti bol zaslaný, pretože {{owner}} ťa pozval, aby si sa stal členom {{team}} tímu v projekte {{project}}.",
"emails.invitation.footer": "Ak nemáš záujem, môžeš túto správu ignorovať.",
"emails.invitation.thanks": "Ďakujeme,",
"emails.invitation.buttonText": "Prijať pozvánku do {{team}}",
"emails.invitation.signature": "{{project}} tím",
"locale.country.unknown": "Neznámy",
"countries.af": "Afganistan",
-3
View File
@@ -8,7 +8,6 @@
"emails.verification.body": "Tevedza chinongedzo ichi kuti uratidze kuti kero iyi ndeyako.",
"emails.verification.footer": "Kana usina kukumbira kuti uratidze kuti kero iyi ndeyako, unogona kufuratira meseji iyi.",
"emails.verification.thanks": "Ndatenda,",
"emails.verification.buttonText": "Simbisa kero yeemail",
"emails.verification.signature": "Chikwata che{{project}}",
"emails.magicSession.subject": "Pinda",
"emails.magicSession.hello": "Hesi,",
@@ -21,14 +20,12 @@
"emails.recovery.body": "Baya chinongedzo ichi kuti uchinje pasiwedhi yako ye{{project}}.",
"emails.recovery.footer": "Kana usina kukumbira kuchinja pasiwedhi yako, unogona kufuratira meseji iyi.",
"emails.recovery.thanks": "Ndatenda,",
"emails.recovery.buttonText": "Gadzirisa password",
"emails.recovery.signature": "Chikwata che{{project}}",
"emails.invitation.subject": "Kukokwa kuchikwata che%s ku%s",
"emails.invitation.hello": "Mhoro,",
"emails.invitation.body": "Tsamba iyi yatumirwa kwauri nekuti {{owner}} anga achida kuti uve nhengo yechikwata che{{team}} pachirongwa che{{project}}.",
"emails.invitation.footer": "Kana usiri kufarira kuve nhengo yechikwata ichi, unogona kufuratira meseji iyi.",
"emails.invitation.thanks": "Ndatenda,",
"emails.invitation.buttonText": "Gamuchira kukokwa ku {{team}}",
"emails.invitation.signature": "Chikwata che{{project}}",
"locale.country.unknown": "Haizivikanwe",
"countries.af": "Afuganisitani",
-3
View File
@@ -8,7 +8,6 @@
"emails.verification.body": "Klicka på denna länk för att verifiera din email",
"emails.verification.footer": "Om du inte bad om att verifiera den här e-postadressen kan du ignorera detta mail.",
"emails.verification.thanks": "Tack,",
"emails.verification.buttonText": "Bekräfta e-postadress",
"emails.verification.signature": "{{project}} teamet",
"emails.magicSession.subject": "Logga in",
"emails.magicSession.hello": "Hej,",
@@ -21,14 +20,12 @@
"emails.recovery.body": "Klicka på denna länk för att återställa lösenordet på {{project}}",
"emails.recovery.footer": "Om du inte bad om att återställa ditt lösenord kan du ignorera detta mail.",
"emails.recovery.thanks": "Tack,",
"emails.recovery.buttonText": "Återställ lösenord",
"emails.recovery.signature": "{{project}} teamet",
"emails.invitation.subject": "Inbjudan till %s teamet på %s",
"emails.invitation.hello": "Hej,",
"emails.invitation.body": "Detta mail skickades till dig eftersom {{owner}} ville bjuda in dig att bli medlem i teamet {{team}} på {{project}}.",
"emails.invitation.footer": "Om du inte är intresserad kan du ignorera detta mail.",
"emails.invitation.thanks": "Tack,",
"emails.invitation.buttonText": "Acceptera inbjudan till {{team}}",
"emails.invitation.signature": "{{project}} teamet",
"locale.country.unknown": "Okänt",
"countries.af": "Afghanistan",
-3
View File
@@ -8,7 +8,6 @@
"emails.verification.body": "உங்கள் மின்னஞ்சல் முகவரியைச் சரிபார்க்க இந்த இணைப்பைப் பின்தொடரவும்.",
"emails.verification.footer": "இந்த முகவரியைச் சரிபார்க்கும்படி உங்களிடம் கேட்கப்படவில்லை என்றால், இந்தச் செய்தியை நீங்கள் புறக்கணிக்கலாம்.",
"emails.verification.thanks": "நன்றி,",
"emails.verification.buttonText": "மின்னஞ்சல் முகவரியை உறுதிப்படுத்தவும்",
"emails.verification.signature": "{{project}} குழு ",
"emails.magicSession.subject": "உள்நுழைய",
"emails.magicSession.hello": "ஏய்,",
@@ -21,14 +20,12 @@
"emails.recovery.body": "மீட்டமைக்க இந்த இணைப்பைப் பின்தொடரவும் {{project}} கடவுச்சொல்.",
"emails.recovery.footer": "உங்கள் கடவுச்சொல்லை மீட்டமைக்கும்படி உங்களிடம் கேட்கப்படவில்லை என்றால், இந்தச் செய்தியை நீங்கள் புறக்கணிக்கலாம்.",
"emails.recovery.thanks": "நன்றி,",
"emails.recovery.buttonText": "கடவுச்சொல்லை மீட்டமைக்கவும்",
"emails.recovery.signature": "{{project}} குழு",
"emails.invitation.subject": "அழைப்பிதழ் %s குழு %s ",
"emails.invitation.hello": "வணக்கம்,",
"emails.invitation.body": "{{project}} இல் {{team}} குழுவில் உறுப்பினராக உங்களை {{owner}} அழைக்க விரும்புவதால், இந்த அஞ்சல் உங்களுக்கு அனுப்பப்பட்டது.",
"emails.invitation.footer": "உங்களுக்கு ஆர்வம் இல்லை என்றால், இந்த செய்தியை நீங்கள் புறக்கணிக்கலாம்.",
"emails.invitation.thanks": "நன்றி,",
"emails.invitation.buttonText": "{{team}} அழைப்பை ஏற்கவும்",
"emails.invitation.signature": "{{project}} குழு",
"locale.country.unknown": "அறியவில்லை",
"countries.af": "ஆப்கானித்தான்",
-3
View File
@@ -8,7 +8,6 @@
"emails.verification.body": "ఈ లింక్ ద్వారా ఇమెయిల్ ని ధృవీకరించండి",
"emails.verification.footer": "మీరు ఈ చిరునామాను ధృవీకరించమని అడగనట్లయితే, మీరు ఈ సందేశాన్ని విస్మరించవచ్చు",
"emails.verification.thanks": "ధన్యవాదాలు,",
"emails.verification.buttonText": "ఇమెయిల్ చిరునామాను నిర్ధారించండి",
"emails.verification.signature": "{{project}} జట్",
"emails.magicSession.subject": "లాగిన్",
"emails.magicSession.hello": "నమస్కారము,",
@@ -21,14 +20,12 @@
"emails.recovery.body": "మీ {{project}} పాస్వర్డ్ ని రీసెట్ చేయడానికి ఈ లింక్ ని అనుసరించండి",
"emails.recovery.footer": "మీరు మీ పాస్వర్డ్ ని రీసెట్ చేయమని అడగనట్లయితే, మీరు ఈ సందేశాన్ని విస్మరించవచ్చు",
"emails.recovery.thanks": "ధన్యవాదాల,",
"emails.recovery.buttonText": "పాస్‌వర్డ్‌ను రీసెట్ చేయండి",
"emails.recovery.signature": "{{project}} జట్",
"emails.invitation.subject": "%s వద్ద %s బృందానికి ఆహ్వానం",
"emails.invitation.hello": "నమస్కారమ,",
"emails.invitation.body": "{{owner}} మిమ్మల్ని {{project}} లో {{team}} బృందంలో సభ్యునిగా ఉండమని ఆహ్వానించాలనుకుంటున్నందున ఈ మెయిల్ మీకు పంపబడింది.",
"emails.invitation.footer": "మీకు ఆసక్తి లేకుంటే, మీరు ఈ సందేశాన్ని విస్మరించవచ్చు.",
"emails.invitation.thanks": "ధన్యవాదాల,",
"emails.invitation.buttonText": "{{team}} కు ఆహ్వానాన్ని ఆమోదించండి",
"emails.invitation.signature": "{{project}} జట్",
"locale.country.unknown": "తెలియని",
"countries.af": "ఆఫ్ఘనిస్తాన్",
-3
View File
@@ -8,7 +8,6 @@
"emails.verification.body": "กดเข้าไปที่ลิงก์นี้เพื่อยืนยันอีเมลของท่าน",
"emails.verification.footer": "หากท่านไม่ได้ต้องการที่จะยืนยันอีเมลนี้ ท่านสามารถเพิกเฉยข้อความนี้ได้",
"emails.verification.thanks": "ขอบคุณ",
"emails.verification.buttonText": "ยืนยันที่อยู่อีเมล",
"emails.verification.signature": "ทีม {{project}}",
"emails.magicSession.subject": "เข้าสู่ระบบ",
"emails.magicSession.hello": "เรียนผู้ใช้งาน",
@@ -21,14 +20,12 @@
"emails.recovery.body": "กดเข้าไปที่ลิงก์นี้เพื่อรีเซ็ตรหัสผ่านสำหรับโปรเจกต์ {{project}} ของท่าน",
"emails.recovery.footer": "หากท่านไม่ได้ต้องการที่จะรีเซ็ตรหัสผ่านของท่าน ท่านสามารถเพิกเฉยข้อความนี้ได้",
"emails.recovery.thanks": "ขอบคุณ",
"emails.recovery.buttonText": "รีเซ็ตรหัสผ่าน",
"emails.recovery.signature": "ทีม {{project}}",
"emails.invitation.subject": "เรียนเชิญเข้าร่วม ทีม %s จากโปรเจกต์ %s",
"emails.invitation.hello": "สวัสดี",
"emails.invitation.body": "ท่านได้รับอีเมลฉบับนี้เนื่องจาก {{owner}} ต้องการที่จะเชิญชวนคุณเข้าร่วมเป็นส่วนหนึ่งของ ทีม {{team}} จากโปรเจกต์ {{project}}",
"emails.invitation.footer": "หากท่านไม่ได้สนใจที่จะเข้าร่วม ท่านสามารถเพิกเฉยข้อความนี้ได้",
"emails.invitation.thanks": "ขอบคุณ",
"emails.invitation.buttonText": "ยอมรับคำเชิญเข้าร่วม {{team}}",
"emails.invitation.signature": "ทีม {{project}}",
"locale.country.unknown": "ไม่ทราบ",
"countries.af": "อัฟกานิสถาน",
-3
View File
@@ -8,7 +8,6 @@
"emails.verification.body": "Sundin ang link na ito upang ma-verify ang iyong email address.",
"emails.verification.footer": "Kung hindi mo hiningi na i-verify ang address na ito, maaari mong balewalain ang mensahe na ito.",
"emails.verification.thanks": "Salamat,",
"emails.verification.buttonText": "Kumpirmahin ang email address",
"emails.verification.signature": "Pangkat ng {{project}}",
"emails.magicSession.subject": "Mag log in",
"emails.magicSession.hello": "Kamusta ,",
@@ -21,14 +20,12 @@
"emails.recovery.body": "Sundin ang link na ito upang i-reset ang password ng iyong {{project}}.",
"emails.recovery.footer": "Kung hindi mo hiningi na i-reset ang iyong password, maaari mong balewalain ang mensahe na ito.",
"emails.recovery.thanks": "Salamat,",
"emails.recovery.buttonText": "I-reset ang password",
"emails.recovery.signature": "Pangkat ng {{project}}",
"emails.invitation.subject": "Imbitasyon para sa Pangkat %s sa %s",
"emails.invitation.hello": "Kamusta,",
"emails.invitation.body": "Ipinadala sa iyo ang mail na ito dahil gusto kang imbitahan ni {{owner}} na maging miyembro ng Pangkat {{team}} sa ilalim ng proyektong {{project}}.",
"emails.invitation.footer": "Kung ikaw ay hindi interesado, maaari mong balewalain ang mensaheng ito.",
"emails.invitation.thanks": "Salamat,",
"emails.invitation.buttonText": "Tanggapin ang paanyaya sa {{team}}",
"emails.invitation.signature": "Pangkat ng {{project}}",
"locale.country.unknown": "Hindi kilala",
"countries.af": "Apganistan",
-3
View File
@@ -8,7 +8,6 @@
"emails.verification.body": "Eposta adresini doğrulamak için bu bağlantıyı kullanın.",
"emails.verification.footer": "Eğer bu eposta adresini doğrulamak isteyen siz değilseniz devam etmeyin.",
"emails.verification.thanks": "Teşekkürler,",
"emails.verification.buttonText": "E-posta adresini doğrula",
"emails.verification.signature": "{{project}} takımı",
"emails.magicSession.subject": "Giriş",
"emails.magicSession.hello": "Merhaba,",
@@ -21,14 +20,12 @@
"emails.recovery.body": "{{project}} şifrenizi sıfırlamak için bu bağlantıyı kullanın.",
"emails.recovery.footer": "Eğer şifre sıfırlama talebinde bulunmadıysanız devam etmeyin.",
"emails.recovery.thanks": "Teşekkürler,",
"emails.recovery.buttonText": "Şifreyi sıfırla",
"emails.recovery.signature": "{{project}} takımı",
"emails.invitation.subject": "%s üzerinde %s Takımına Davet",
"emails.invitation.hello": "Merhaba,",
"emails.invitation.body": "Bu epostayı aldınız, çünkü {{owner}} sizi {{project}} üzerinde {{team}} takımının üyesi olmaya davet etti.",
"emails.invitation.footer": "Eğer ilgilenmiyorsanız devam etmeyin.",
"emails.invitation.thanks": "Teşekkürler,",
"emails.invitation.buttonText": "{{team}}'e daveti kabul et",
"emails.invitation.signature": "{{project}} takımı",
"locale.country.unknown": "Bilinmeyen",
"countries.af": "Afganistan",
-3
View File
@@ -8,7 +8,6 @@
"emails.verification.body": "Перейдіть за цим посиланням, щоб підтвердити свою електронну адресу.",
"emails.verification.footer": "Якщо ви не запитували підтвердження цієї адреси, ви можете ігнорувати це повідомлення.",
"emails.verification.thanks": "Дякуємо,",
"emails.verification.buttonText": "Підтвердити адресу електронної пошти",
"emails.verification.signature": "команда {{project}}",
"emails.magicSession.subject": "Логін",
"emails.magicSession.hello": "Вітаємо,",
@@ -21,14 +20,12 @@
"emails.recovery.body": "Перейдіть за цим посиланням для того щоб скинути свій пароль для проекту {{project}}",
"emails.recovery.footer": "Якщо ви не запитували скидання паролю, проігноруйте це повідомлення.",
"emails.recovery.thanks": "Дякуємо,",
"emails.recovery.buttonText": "Скинути пароль",
"emails.recovery.signature": "команда {{project}}",
"emails.invitation.subject": "Запрошення до %s Команди у %s",
"emails.invitation.hello": "Вітаємо,",
"emails.invitation.body": "Цей лист був надісланий вам тому що {{owner}} запрошує вас стати членом команди {{team}} у проекті {{project}}.",
"emails.invitation.footer": "Якщо ви не зацікавлені, проігноруйте це повідомлення.",
"emails.invitation.thanks": "Дякуємо,",
"emails.invitation.buttonText": "Прийняти запрошення до {{team}}",
"emails.invitation.signature": "команда {{project}}",
"locale.country.unknown": "Невідомо",
"countries.af": "Афганістан",
+1 -4
View File
@@ -8,7 +8,6 @@
"emails.verification.body": "براہ کرم اپنے ای میل کی تصدیق کے لیے درج ذیل لنک پر عمل کریں۔",
"emails.verification.footer": "اگر آپ نے اس پتے کی تصدیق کے لیے نہیں کہا تو آپ اس پیغام کو نظر انداز کر سکتے ہیں۔",
"emails.verification.thanks": "شکریہ،",
"emails.verification.buttonText": "ای میل پتہ کی تصدیق کریں",
"emails.verification.signature": "ٹیم۔ {{project}}",
"emails.magicSession.subject": "اگ ان کریں",
"emails.magicSession.hello": "خوش آمدید،",
@@ -21,15 +20,13 @@
"emails.recovery.body": "{{project}} کا پاس ورڈ تبدیل کرنے کے لیے درج ذیل لنک پر عمل کریں",
"emails.recovery.footer": "اگر آپ نے اپنا پاس ورڈ دوبارہ ترتیب دینے کے لیے نہیں کہا تو آپ اس پیغام کو نظر انداز کر سکتے ہیں۔",
"emails.recovery.thanks": "شکریہ،",
"emails.recovery.buttonText": "پاس ورڈ ری سیٹ کریں",
"emails.recovery.signature": "ٹیم۔ {{project}}",
"emails.invitation.subject": "%s پر %s ٹیم کو دعوت",
"emails.invitation.hello": "خوش آمدید،",
"emails.invitation.body": "یہ پیغام آپ کو اس لیے بھیجا گیا تھا کہ {{owner}} نے آپ کو {{project}} میں {{team}} ٹیم کا رکن بننے کی دعوت بھیجی",
"emails.invitation.footer": "اگر آپ دلچسپی نہیں رکھتے تو آپ اس پیغام کو نظر انداز کر سکتے ہیں۔",
"emails.invitation.thanks": "شکریہ،",
"emails.invitation.buttonText": "{{team}} کی دعوت قبول کریں",
"emails.invitation.signature": "ٹیم۔ {{project}}",
"emails.invitation.signature": "ٹیم۔ {{project}",
"locale.country.unknown": "نامعلوم",
"countries.af": "افغانستان",
"countries.ao": "انگولا",
-3
View File
@@ -8,7 +8,6 @@
"emails.verification.body": "Nhấn vào đường dẫn sau để xác minh địa chỉ email của bạn.",
"emails.verification.footer": "Nếu bạn không yêu cầu xác minh tài khoản, bạn có thể bỏ qua email này.",
"emails.verification.thanks": "Cảm ơn",
"emails.verification.buttonText": "Xác nhận địa chỉ email",
"emails.verification.signature": "Nhóm {{project}}",
"emails.magicSession.subject": "Đăng nhập",
"emails.magicSession.hello": "Chào",
@@ -21,14 +20,12 @@
"emails.recovery.body": "Nhấn vào đường dẫn sau để thiết lập lại mật khẩu {{project}} của bạn.",
"emails.recovery.footer": "Nếu bạn không yêu cầu thiết lập lại mật khẩu, bạn có thể bỏ qua email này.",
"emails.recovery.thanks": "Cảm ơn",
"emails.recovery.buttonText": "Đặt lại mật khẩu",
"emails.recovery.signature": "Nhóm {{project}}",
"emails.invitation.subject": "Lời mời tham gia nhóm %s tại %s",
"emails.invitation.hello": "Xin chào",
"emails.invitation.body": "Email này được gửi cho bạn vì {{owner}} muốn mời bạn trở thành một thành viên của nhóm {{team}} tại {{project}}.",
"emails.invitation.footer": "Nếu bạn không quan tâm, bạn có thể bỏ qua email này.",
"emails.invitation.thanks": "Cảm ơn",
"emails.invitation.buttonText": "Chấp nhận lời mời vào {{team}}",
"emails.invitation.signature": "Nhóm {{project}}",
"locale.country.unknown": "Không xác định",
"countries.af": "Afghanistan",
@@ -8,7 +8,6 @@
"emails.verification.body": "点此链接验证您的电子邮件地址。",
"emails.verification.footer": "如果您没有要求验证此地址,则可忽略此消息。",
"emails.verification.thanks": "谢谢、",
"emails.verification.buttonText": "确认邮箱地址",
"emails.verification.signature": "{{project}} 团队",
"emails.magicSession.subject": "登录",
"emails.magicSession.hello": "你好、",
@@ -21,14 +20,12 @@
"emails.recovery.body": "点此链接重置您的 {{project}} 密码。",
"emails.recovery.footer": "如果您没有要求重置密码,则可以忽略此消息。",
"emails.recovery.thanks": "谢谢、",
"emails.recovery.buttonText": "重置密码",
"emails.recovery.signature": "{{project}} 团队",
"emails.invitation.subject": "邀请 %s 团队在 %s",
"emails.invitation.hello": "你好、",
"emails.invitation.body": "这封邮件发送给您是因为 {{owner}} 想邀请您成为 {{team}} 团队在 {{project}}.",
"emails.invitation.footer": "如果您不感兴趣,可以忽略此消息。",
"emails.invitation.thanks": "谢谢、",
"emails.invitation.buttonText": "接受加入 {{team}} 的邀请",
"emails.invitation.signature": "{{project}} 团队",
"locale.country.unknown": "未知",
"countries.af": "阿富汗",
@@ -8,7 +8,6 @@
"emails.verification.body": "按照此連結驗證您的電子郵件地址。",
"emails.verification.footer": "如果您沒有要求驗證此地址,則可以忽略此消息。",
"emails.verification.thanks": "謝謝、",
"emails.verification.buttonText": "確認電子郵件地址",
"emails.verification.signature": "{{project}} 團隊",
"emails.magicSession.subject": "登入",
"emails.magicSession.hello": "嗨、",
@@ -21,14 +20,12 @@
"emails.recovery.body": "按照此連結重置您的 {{project}} 密碼。",
"emails.recovery.footer": "如果您沒有要求重置密碼,則可以忽略此消息。",
"emails.recovery.thanks": "謝謝、",
"emails.recovery.buttonText": "重設密碼",
"emails.recovery.signature": "{{project}} 團隊",
"emails.invitation.subject": "邀請 %s 團隊在 %s",
"emails.invitation.hello": "您好、",
"emails.invitation.body": "發送這封郵件給您是因為 {{owner}} 想邀請您成為 {{team}} 團隊在 {{project}}。",
"emails.invitation.footer": "如果您不感興趣,可以忽略此消息。",
"emails.invitation.thanks": "謝謝、",
"emails.invitation.buttonText": "接受加入 {{team}} 的邀請",
"emails.invitation.signature": "{{project}} 團隊",
"locale.country.unknown": "未知",
"countries.af": "阿富汗",
+6 -7
View File
@@ -1,6 +1,5 @@
<?php
use Appwrite\Auth\Auth;
$member = [
'global',
@@ -86,7 +85,7 @@ $admins = [
];
return [
Auth::USER_ROLE_GUESTS => [
USER_ROLE_GUESTS => [
'label' => 'Guests',
'scopes' => [
'global',
@@ -104,23 +103,23 @@ return [
'execution.write',
],
],
Auth::USER_ROLE_USERS => [
USER_ROLE_USERS => [
'label' => 'Users',
'scopes' => \array_merge($member),
],
Auth::USER_ROLE_ADMIN => [
USER_ROLE_ADMIN => [
'label' => 'Admin',
'scopes' => \array_merge($admins),
],
Auth::USER_ROLE_DEVELOPER => [
USER_ROLE_DEVELOPER => [
'label' => 'Developer',
'scopes' => \array_merge($admins),
],
Auth::USER_ROLE_OWNER => [
USER_ROLE_OWNER => [
'label' => 'Owner',
'scopes' => \array_merge($member, $admins),
],
Auth::USER_ROLE_APPS => [
USER_ROLE_APPS => [
'label' => 'Applications',
'scopes' => ['global', 'health.read', 'graphql'],
],
+7 -2
View File
@@ -6,8 +6,13 @@ use Utopia\System\System;
* List of Appwrite Sites templates
*/
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
$hostname = System::getEnv('_APP_CONSOLE_DOMAIN', System::getEnv('_APP_DOMAIN', ''));
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https';
$hostname = System::getEnv('_APP_DOMAIN');
// TODO: Development override
if (System::getEnv('_APP_ENV') === 'development') {
$hostname = 'localhost';
}
$url = $protocol . '://' . $hostname;
File diff suppressed because it is too large Load Diff
+1 -3
View File
@@ -1,7 +1,6 @@
<?php
use Ahc\Jwt\JWT;
use Appwrite\Auth\Auth;
use Appwrite\Auth\Validator\MockNumber;
use Appwrite\Event\Delete;
use Appwrite\Event\Mail;
@@ -120,7 +119,7 @@ App::post('/v1/projects')
'maxSessions' => APP_LIMIT_USER_SESSIONS_DEFAULT,
'passwordHistory' => 0,
'passwordDictionary' => false,
'duration' => Auth::TOKEN_EXPIRATION_LOGIN_LONG,
'duration' => TOKEN_EXPIRATION_LOGIN_LONG,
'personalDataCheck' => false,
'mockNumbers' => [],
'sessionAlerts' => false,
@@ -2274,7 +2273,6 @@ App::get('/v1/projects/:projectId/templates/email/:type/:locale')
->setParam('{{footer}}', $localeObj->getText("emails.{$type}.footer"))
->setParam('{{body}}', $localeObj->getText('emails.' . $type . '.body'), escapeHtml: false)
->setParam('{{thanks}}', $localeObj->getText("emails.{$type}.thanks"))
->setParam('{{buttonText}}', $localeObj->getText("emails.{$type}.buttonText"))
->setParam('{{signature}}', $localeObj->getText("emails.{$type}.signature"))
->setParam('{{direction}}', $localeObj->getText('settings.direction'));
$message = $message->render();
+34 -22
View File
@@ -27,6 +27,9 @@ use MaxMind\Db\Reader;
use Utopia\Abuse\Abuse;
use Utopia\App;
use Utopia\Audit\Audit;
use Utopia\Auth\Proofs\Password;
use Utopia\Auth\Proofs\Token;
use Utopia\Auth\Store;
use Utopia\Config\Config;
use Utopia\Database\Database;
use Utopia\Database\DateTime;
@@ -460,7 +463,7 @@ App::post('/v1/teams/:teamId/memberships')
;
$roles = array_keys(Config::getParam('roles', []));
array_filter($roles, function ($role) {
return !in_array($role, [Auth::USER_ROLE_APPS, Auth::USER_ROLE_GUESTS, Auth::USER_ROLE_USERS]);
return !in_array($role, [USER_ROLE_APPS, USER_ROLE_GUESTS, USER_ROLE_USERS]);
});
return new ArrayList(new WhiteList($roles), APP_LIMIT_ARRAY_PARAMS_SIZE);
}
@@ -479,7 +482,9 @@ App::post('/v1/teams/:teamId/memberships')
->inject('timelimit')
->inject('queueForStatsUsage')
->inject('plan')
->action(function (string $teamId, string $email, string $userId, string $phone, array $roles, string $url, string $name, Response $response, Document $project, Document $user, Database $dbForProject, Locale $locale, Mail $queueForMails, Messaging $queueForMessaging, Event $queueForEvents, callable $timelimit, StatsUsage $queueForStatsUsage, array $plan) {
->inject('proofForPassword')
->inject('proofForToken')
->action(function (string $teamId, string $email, string $userId, string $phone, array $roles, string $url, string $name, Response $response, Document $project, Document $user, Database $dbForProject, Locale $locale, Mail $queueForMails, Messaging $queueForMessaging, Event $queueForEvents, callable $timelimit, StatsUsage $queueForStatsUsage, array $plan, Password $proofForPassword, Token $proofForToken) {
$isAppUser = Auth::isAppUser(Authorization::getRoles());
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
@@ -552,6 +557,7 @@ App::post('/v1/teams/:teamId/memberships')
try {
$userId = ID::unique();
$hash = $proofForPassword->hash($proofForPassword->generate());
$invitee = Authorization::skip(fn () => $dbForProject->createDocument('users', new Document([
'$id' => $userId,
'$permissions' => [
@@ -565,9 +571,9 @@ App::post('/v1/teams/:teamId/memberships')
'emailVerification' => false,
'status' => true,
// TODO: Set password empty?
'password' => Auth::passwordHash(Auth::passwordGenerator(), Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS),
'hash' => Auth::DEFAULT_ALGO,
'hashOptions' => Auth::DEFAULT_ALGO_OPTIONS,
'password' => $hash,
'hash' => $proofForPassword->getHash()->getName(),
'hashOptions' => $proofForPassword->getHash()->getOptions(),
/**
* Set the password update time to 0 for users created using
* team invite and OAuth to allow password updates without an
@@ -599,7 +605,7 @@ App::post('/v1/teams/:teamId/memberships')
Query::equal('teamInternalId', [$team->getSequence()]),
]);
$secret = Auth::tokenGenerator();
$secret = $proofForToken->generate();
if ($membership->isEmpty()) {
$membershipId = ID::unique();
$membership = new Document([
@@ -619,7 +625,7 @@ App::post('/v1/teams/:teamId/memberships')
'invited' => DateTime::now(),
'joined' => ($isPrivilegedUser || $isAppUser) ? DateTime::now() : null,
'confirm' => ($isPrivilegedUser || $isAppUser),
'secret' => Auth::hash($secret),
'secret' => $proofForToken->hash($secret),
'search' => implode(' ', [$membershipId, $invitee->getId()])
]);
@@ -632,7 +638,7 @@ App::post('/v1/teams/:teamId/memberships')
}
} elseif ($membership->getAttribute('confirm') === false) {
$membership->setAttribute('secret', Auth::hash($secret));
$membership->setAttribute('secret', $proofForToken->hash($secret));
$membership->setAttribute('invited', DateTime::now());
if ($isPrivilegedUser || $isAppUser) {
@@ -666,7 +672,6 @@ App::post('/v1/teams/:teamId/memberships')
->setParam('{{hello}}', $locale->getText("emails.invitation.hello"))
->setParam('{{footer}}', $locale->getText("emails.invitation.footer"))
->setParam('{{thanks}}', $locale->getText("emails.invitation.thanks"))
->setParam('{{buttonText}}', $locale->getText("emails.invitation.buttonText"))
->setParam('{{signature}}', $locale->getText("emails.invitation.signature"));
$body = $message->render();
@@ -1054,7 +1059,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId')
if ($project->getId() === 'console') {
$roles = array_keys(Config::getParam('roles', []));
array_filter($roles, function ($role) {
return !in_array($role, [Auth::USER_ROLE_APPS, Auth::USER_ROLE_GUESTS, Auth::USER_ROLE_USERS]);
return !in_array($role, [USER_ROLE_APPS, USER_ROLE_GUESTS, USER_ROLE_USERS]);
});
return new ArrayList(new WhiteList($roles), APP_LIMIT_ARRAY_PARAMS_SIZE);
}
@@ -1169,7 +1174,9 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
->inject('project')
->inject('geodb')
->inject('queueForEvents')
->action(function (string $teamId, string $membershipId, string $userId, string $secret, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Reader $geodb, Event $queueForEvents) {
->inject('store')
->inject('proofForToken')
->action(function (string $teamId, string $membershipId, string $userId, string $secret, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Reader $geodb, Event $queueForEvents, Store $store, Token $proofForToken) {
$protocol = $request->getProtocol();
$membership = $dbForProject->getDocument('memberships', $membershipId);
@@ -1188,7 +1195,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
throw new Exception(Exception::TEAM_MEMBERSHIP_MISMATCH);
}
if (Auth::hash($secret) !== $membership->getAttribute('secret')) {
if (!$proofForToken->verify($secret, $membership->getAttribute('secret'))) {
throw new Exception(Exception::TEAM_INVALID_SECRET);
}
@@ -1222,9 +1229,9 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
$detector = new Detector($request->getUserAgent('UNKNOWN'));
$record = $geodb->get($request->getIP());
$authDuration = $project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG;
$authDuration = $project->getAttribute('auths', [])['duration'] ?? TOKEN_EXPIRATION_LOGIN_LONG;
$expire = DateTime::addSeconds(new \DateTime(), $authDuration);
$secret = Auth::tokenGenerator();
$secret = $proofForToken->generate();
$session = new Document(array_merge([
'$id' => ID::unique(),
'$permissions' => [
@@ -1233,10 +1240,10 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
Permission::delete(Role::user($user->getId())),
],
'userId' => $user->getId(),
'userInternalId' => $user->getSequence(),
'provider' => Auth::SESSION_PROVIDER_EMAIL,
'userInternalId' => $user->getInternalId(),
'provider' => SESSION_PROVIDER_EMAIL,
'providerUid' => $user->getAttribute('email'),
'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak
'secret' => $proofForToken->hash($secret), // One way hash encryption to protect DB leak
'userAgent' => $request->getUserAgent('UNKNOWN'),
'ip' => $request->getIP(),
'factors' => ['email'],
@@ -1249,13 +1256,18 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
Authorization::setRole(Role::user($userId)->toString());
if (!Config::getParam('domainVerification')) {
$response->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($user->getId(), $secret)]));
$encoded = $store
->setProperty('id', $user->getId())
->setProperty('secret', $secret)
->encode();
$response->addHeader('X-Fallback-Cookies', \json_encode([$store->getKey() => $encoded]));
}
$response
->addCookie(
name: Auth::$cookieName . '_legacy',
value: Auth::encodeSession($user->getId(), $secret),
name: $store->getKey() . '_legacy',
value: $encoded,
expire: (new \DateTime($expire))->getTimestamp(),
path: '/',
domain: Config::getParam('cookieDomain'),
@@ -1263,8 +1275,8 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
httponly: true
)
->addCookie(
name: Auth::$cookieName,
value: Auth::encodeSession($user->getId(), $secret),
name: $store->getKey(),
value: $encoded,
expire: (new \DateTime($expire))->getTimestamp(),
path: '/',
domain: Config::getParam('cookieDomain'),
+108 -46
View File
@@ -1,7 +1,6 @@
<?php
use Ahc\Jwt\JWT;
use Appwrite\Auth\Auth;
use Appwrite\Auth\MFA\Type;
use Appwrite\Auth\MFA\Type\TOTP;
use Appwrite\Auth\Validator\Password;
@@ -31,6 +30,18 @@ use Appwrite\Utopia\Response;
use MaxMind\Db\Reader;
use Utopia\App;
use Utopia\Audit\Audit;
use Utopia\Auth\Hash;
use Utopia\Auth\Hashes\Argon2;
use Utopia\Auth\Hashes\Bcrypt;
use Utopia\Auth\Hashes\MD5;
use Utopia\Auth\Hashes\PHPass;
use Utopia\Auth\Hashes\Plaintext;
use Utopia\Auth\Hashes\Scrypt;
use Utopia\Auth\Hashes\ScryptModified;
use Utopia\Auth\Hashes\Sha;
use Utopia\Auth\Proofs\Password as ProofsPassword;
use Utopia\Auth\Proofs\Token;
use Utopia\Auth\Store;
use Utopia\Config\Config;
use Utopia\Database\Database;
use Utopia\Database\DateTime;
@@ -59,10 +70,9 @@ use Utopia\Validator\Text;
use Utopia\Validator\WhiteList;
/** TODO: Remove function when we move to using utopia/platform */
function createUser(string $hash, mixed $hashOptions, string $userId, ?string $email, ?string $password, ?string $phone, string $name, Document $project, Database $dbForProject, Hooks $hooks): Document
function createUser(Hash $hash, string $userId, ?string $email, ?string $password, ?string $phone, string $name, Document $project, Database $dbForProject, Hooks $hooks): Document
{
$plaintextPassword = $password;
$hashOptionsObject = (\is_string($hashOptions)) ? \json_decode($hashOptions, true) : $hashOptions; // Cast to JSON array
$passwordHistory = $project->getAttribute('auths', [])['passwordHistory'] ?? 0;
if (!empty($email)) {
@@ -96,7 +106,18 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e
}
}
$password = (!empty($password)) ? ($hash === 'plaintext' ? Auth::passwordHash($password, $hash, $hashOptionsObject) : $password) : null;
$hashedPassword = null;
if (!empty($password)) {
if ($hash instanceof Plaintext) { // Password was never hashed, hash it with the default hash
$defaultHash = new ProofsPassword();
$hashedPassword = $defaultHash->hash($password);
$hash = $defaultHash->getHash();
} else {
$hashedPassword = $password;
}
}
$user = new Document([
'$id' => $userId,
'$permissions' => [
@@ -110,11 +131,11 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e
'phoneVerification' => false,
'status' => true,
'labels' => [],
'password' => $password,
'passwordHistory' => is_null($password) || $passwordHistory === 0 ? [] : [$password],
'passwordUpdate' => (!empty($password)) ? DateTime::now() : null,
'hash' => $hash === 'plaintext' ? Auth::DEFAULT_ALGO : $hash,
'hashOptions' => $hash === 'plaintext' ? Auth::DEFAULT_ALGO_OPTIONS : $hashOptionsObject + ['type' => $hash],
'password' => $hashedPassword,
'passwordHistory' => is_null($hashedPassword) || $passwordHistory === 0 ? [] : [$hashedPassword],
'passwordUpdate' => (!empty($hashedPassword)) ? DateTime::now() : null,
'hash' => $hash->getName(),
'hashOptions' => $hash->getOptions(),
'registration' => DateTime::now(),
'reset' => false,
'name' => $name,
@@ -125,7 +146,7 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e
'search' => implode(' ', [$userId, $email, $phone, $name]),
]);
if ($hash === 'plaintext') {
if ($hash instanceof Plaintext) {
$hooks->trigger('passwordValidator', [$dbForProject, $project, $plaintextPassword, &$user, true]);
}
@@ -216,7 +237,9 @@ App::post('/v1/users')
->inject('dbForProject')
->inject('hooks')
->action(function (string $userId, ?string $email, ?string $phone, ?string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) {
$user = createUser('plaintext', '{}', $userId, $email, $password, $phone, $name, $project, $dbForProject, $hooks);
$plaintext = new Plaintext();
$user = createUser($plaintext, $userId, $email, $password, $phone, $name, $project, $dbForProject, $hooks);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($user, Response::MODEL_USER);
@@ -250,7 +273,10 @@ App::post('/v1/users/bcrypt')
->inject('dbForProject')
->inject('hooks')
->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) {
$user = createUser('bcrypt', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $hooks);
$bcrypt = new Bcrypt();
$bcrypt->setCost(8); // Default cost
$user = createUser($bcrypt, $userId, $email, $password, null, $name, $project, $dbForProject, $hooks);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
@@ -285,7 +311,9 @@ App::post('/v1/users/md5')
->inject('dbForProject')
->inject('hooks')
->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) {
$user = createUser('md5', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $hooks);
$md5 = new MD5();
$user = createUser($md5, $userId, $email, $password, null, $name, $project, $dbForProject, $hooks);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
@@ -320,7 +348,13 @@ App::post('/v1/users/argon2')
->inject('dbForProject')
->inject('hooks')
->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) {
$user = createUser('argon2', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $hooks);
$argon2 = new Argon2();
$argon2
->setMemoryCost(2048)
->setTimeCost(4)
->setThreads(3);
$user = createUser($argon2, $userId, $email, $password, null, $name, $project, $dbForProject, $hooks);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
@@ -356,13 +390,12 @@ App::post('/v1/users/sha')
->inject('dbForProject')
->inject('hooks')
->action(function (string $userId, string $email, string $password, string $passwordVersion, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) {
$options = '{}';
$sha = new Sha();
if (!empty($passwordVersion)) {
$options = '{"version":"' . $passwordVersion . '"}';
$sha->setVersion($passwordVersion);
}
$user = createUser('sha', $options, $userId, $email, $password, null, $name, $project, $dbForProject, $hooks);
$user = createUser($sha, $userId, $email, $password, null, $name, $project, $dbForProject, $hooks);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
@@ -397,7 +430,9 @@ App::post('/v1/users/phpass')
->inject('dbForProject')
->inject('hooks')
->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) {
$user = createUser('phpass', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $hooks);
$phpass = new PHPass();
$user = createUser($phpass, $userId, $email, $password, null, $name, $project, $dbForProject, $hooks);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
@@ -437,15 +472,15 @@ App::post('/v1/users/scrypt')
->inject('dbForProject')
->inject('hooks')
->action(function (string $userId, string $email, string $password, string $passwordSalt, int $passwordCpu, int $passwordMemory, int $passwordParallel, int $passwordLength, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) {
$options = [
'salt' => $passwordSalt,
'costCpu' => $passwordCpu,
'costMemory' => $passwordMemory,
'costParallel' => $passwordParallel,
'length' => $passwordLength
];
$scrypt = new Scrypt();
$scrypt
->setSalt($passwordSalt)
->setCpuCost($passwordCpu)
->setMemoryCost($passwordMemory)
->setParallelCost($passwordParallel)
->setLength($passwordLength);
$user = createUser('scrypt', \json_encode($options), $userId, $email, $password, null, $name, $project, $dbForProject, $hooks);
$user = createUser($scrypt, $userId, $email, $password, null, $name, $project, $dbForProject, $hooks);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
@@ -483,7 +518,13 @@ App::post('/v1/users/scrypt-modified')
->inject('dbForProject')
->inject('hooks')
->action(function (string $userId, string $email, string $password, string $passwordSalt, string $passwordSaltSeparator, string $passwordSignerKey, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) {
$user = createUser('scryptMod', '{"signerKey":"' . $passwordSignerKey . '","saltSeparator":"' . $passwordSaltSeparator . '","salt":"' . $passwordSalt . '"}', $userId, $email, $password, null, $name, $project, $dbForProject, $hooks);
$scryptModified = new ScryptModified();
$scryptModified
->setSalt($passwordSalt)
->setSaltSeparator($passwordSaltSeparator)
->setSignerKey($passwordSignerKey);
$user = createUser($scryptModified, $userId, $email, $password, null, $name, $project, $dbForProject, $hooks);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
@@ -1061,7 +1102,6 @@ App::get('/v1/users/identities')
} catch (QueryException $e) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
if (!empty($search)) {
$queries[] = Query::search('search', $search);
}
@@ -1328,12 +1368,20 @@ App::patch('/v1/users/:userId/password')
$hooks->trigger('passwordValidator', [$dbForProject, $project, $password, &$user, true]);
$newPassword = Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS);
// Create Argon2 hasher with default settings
$hasher = new Argon2();
$hasher->setMemoryCost(2048);
$hasher->setTimeCost(4);
$hasher->setThreads(3);
$newPassword = $hasher->hash($password);
$hash = ProofsPassword::createHash($user->getAttribute('hash'), $user->getAttribute('hashOptions'));
$historyLimit = $project->getAttribute('auths', [])['passwordHistory'] ?? 0;
$history = $user->getAttribute('passwordHistory', []);
if ($historyLimit > 0) {
$validator = new PasswordHistory($history, $user->getAttribute('hash'), $user->getAttribute('hashOptions'));
$validator = new PasswordHistory($history, $hash);
if (!$validator->isValid($password)) {
throw new Exception(Exception::USER_PASSWORD_RECENTLY_USED);
}
@@ -1346,8 +1394,12 @@ App::patch('/v1/users/:userId/password')
->setAttribute('password', $newPassword)
->setAttribute('passwordHistory', $history)
->setAttribute('passwordUpdate', DateTime::now())
->setAttribute('hash', Auth::DEFAULT_ALGO)
->setAttribute('hashOptions', Auth::DEFAULT_ALGO_OPTIONS);
->setAttribute('hash', 'argon2')
->setAttribute('hashOptions', [
'memoryCost' => 65536,
'timeCost' => 4,
'threads' => 3
]);
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
@@ -2034,26 +2086,28 @@ App::post('/v1/users/:userId/sessions')
->inject('locale')
->inject('geodb')
->inject('queueForEvents')
->action(function (string $userId, Request $request, Response $response, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $queueForEvents) {
->inject('store')
->inject('proofForToken')
->action(function (string $userId, Request $request, Response $response, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $queueForEvents, Store $store, Token $proofForToken) {
$user = $dbForProject->getDocument('users', $userId);
if ($user->isEmpty()) {
throw new Exception(Exception::USER_NOT_FOUND);
}
$secret = Auth::tokenGenerator(Auth::TOKEN_LENGTH_SESSION);
$secret = $proofForToken->generate();
$detector = new Detector($request->getUserAgent('UNKNOWN'));
$record = $geodb->get($request->getIP());
$duration = $project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG;
$duration = $project->getAttribute('auths', [])['duration'] ?? TOKEN_EXPIRATION_LOGIN_LONG;
$expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), $duration));
$session = new Document(array_merge(
[
'$id' => ID::unique(),
'userId' => $user->getId(),
'userInternalId' => $user->getSequence(),
'provider' => Auth::SESSION_PROVIDER_SERVER,
'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak
'userInternalId' => $user->getInternalId(),
'provider' => SESSION_PROVIDER_SERVER,
'secret' => $proofForToken->hash($secret), // One way hash encryption to protect DB leak
'userAgent' => $request->getUserAgent('UNKNOWN'),
'factors' => ['server'],
'ip' => $request->getIP(),
@@ -2077,8 +2131,13 @@ App::post('/v1/users/:userId/sessions')
$dbForProject->purgeCachedDocument('users', $user->getId());
$encoded = $store
->setProperty('id', $user->getId())
->setProperty('secret', $secret)
->encode();
$session
->setAttribute('secret', Auth::encodeSession($user->getId(), $secret))
->setAttribute('secret', $encoded)
->setAttribute('countryName', $countryName);
$queueForEvents
@@ -2113,27 +2172,30 @@ App::post('/v1/users/:userId/tokens')
))
->param('userId', '', new UID(), 'User ID.')
->param('length', 6, new Range(4, 128), 'Token length in characters. The default length is 6 characters', true)
->param('expire', Auth::TOKEN_EXPIRATION_GENERIC, new Range(60, Auth::TOKEN_EXPIRATION_LOGIN_LONG), 'Token expiration period in seconds. The default expiration is 15 minutes.', true)
->param('expire', TOKEN_EXPIRATION_GENERIC, new Range(60, TOKEN_EXPIRATION_LOGIN_LONG), 'Token expiration period in seconds. The default expiration is 15 minutes.', true)
->inject('request')
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
->action(function (string $userId, int $length, int $expire, Request $request, Response $response, Database $dbForProject, Event $queueForEvents) {
->inject('proofForToken')
->action(function (string $userId, int $length, int $expire, Request $request, Response $response, Database $dbForProject, Event $queueForEvents, Token $proofForToken) {
$user = $dbForProject->getDocument('users', $userId);
if ($user->isEmpty()) {
throw new Exception(Exception::USER_NOT_FOUND);
}
$secret = Auth::tokenGenerator($length);
$secret = $proofForToken
->setLength($length)
->generate();
$expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), $expire));
$token = new Document([
'$id' => ID::unique(),
'userId' => $user->getId(),
'userInternalId' => $user->getSequence(),
'type' => Auth::TOKEN_TYPE_GENERIC,
'secret' => Auth::hash($secret),
'userInternalId' => $user->getInternalId(),
'type' => TOKEN_TYPE_GENERIC,
'secret' => $proofForToken->hash($secret),
'expire' => $expire,
'userAgent' => $request->getUserAgent('UNKNOWN'),
'ip' => $request->getIP()
+5 -11
View File
@@ -116,10 +116,8 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
}
$commentStatus = $isAuthorized ? 'waiting' : 'failed';
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
$hostname = System::getEnv('_APP_CONSOLE_DOMAIN', System::getEnv('_APP_DOMAIN', ''));
$authorizeUrl = $protocol . '://' . $hostname . "/console/git/authorize-contributor?projectId={$projectId}&installationId={$installationId}&repositoryId={$repositoryId}&providerPullRequestId={$providerPullRequestId}";
$authorizeUrl = $request->getProtocol() . '://' . $request->getHostname() . "/console/git/authorize-contributor?projectId={$projectId}&installationId={$installationId}&repositoryId={$repositoryId}&providerPullRequestId={$providerPullRequestId}";
$action = $isAuthorized ? ['type' => 'logs'] : ['type' => 'authorize', 'url' => $authorizeUrl];
@@ -380,7 +378,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
}
$owner = $github->getOwnerName($providerInstallationId);
$providerTargetUrl = $protocol . '://' . $hostname . "/console/project-$region-$projectId/$resourceCollection/$resourceType-$resourceId";
$providerTargetUrl = $request->getProtocol() . '://' . $request->getHostname() . "/console/project-$region-$projectId/$resourceCollection/$resourceType-$resourceId";
$github->updateCommitStatus($repositoryName, $providerCommitHash, $owner, 'pending', $message, $providerTargetUrl, $name);
}
@@ -439,8 +437,6 @@ App::get('/v1/vcs/github/authorize')
]);
$appName = System::getEnv('_APP_VCS_GITHUB_APP_NAME');
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
$hostname = System::getEnv('_APP_CONSOLE_DOMAIN', System::getEnv('_APP_DOMAIN', ''));
if (empty($appName)) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'GitHub App name is not configured. Please configure VCS (Version Control System) variables in .env file.');
@@ -448,7 +444,7 @@ App::get('/v1/vcs/github/authorize')
$url = "https://github.com/apps/$appName/installations/new?" . \http_build_query([
'state' => $state,
'redirect_uri' => $protocol . '://' . $hostname . "/v1/vcs/github/callback"
'redirect_uri' => $request->getProtocol() . '://' . $request->getHostname() . "/v1/vcs/github/callback"
]);
$response
@@ -498,12 +494,10 @@ App::get('/v1/vcs/github/callback')
}
$region = $project->getAttribute('region', 'default');
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
$hostname = System::getEnv('_APP_CONSOLE_DOMAIN', System::getEnv('_APP_DOMAIN', ''));
$defaultState = [
'success' => $protocol . '://' . $hostname . "/console/project-$region-$projectId/settings/git-installations",
'failure' => $protocol . '://' . $hostname . "/console/project-$region-$projectId/settings/git-installations",
'success' => $request->getProtocol() . '://' . $request->getHostname() . "/console/project-$region-$projectId/settings/git-installations",
'failure' => $request->getProtocol() . '://' . $request->getHostname() . "/console/project-$region-$projectId/settings/git-installations",
];
$state = \array_merge($defaultState, $state ?? []);
+23 -11
View File
@@ -76,7 +76,7 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
}
$errorView = __DIR__ . '/../views/general/error.phtml';
$url = (System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https') . '://' . System::getEnv('_APP_CONSOLE_DOMAIN', System::getEnv('_APP_DOMAIN', ''));
$url = (System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https') . '://' . System::getEnv('_APP_DOMAIN', '');
if ($rule->isEmpty()) {
$appDomainFunctionsFallback = System::getEnv('_APP_DOMAIN_FUNCTIONS_FALLBACK', '');
@@ -262,7 +262,7 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
}
if (!$authorized) {
$url = (System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https') . "://" . System::getEnv('_APP_CONSOLE_DOMAIN', System::getEnv('_APP_DOMAIN', ''));
$url = (System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https') . "://" . System::getEnv('_APP_DOMAIN');
$response
->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
->addHeader('Pragma', 'no-cache')
@@ -448,7 +448,7 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
$vars[$var->getAttribute('key')] = $var->getAttribute('value', '');
}
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https';
$hostname = System::getEnv('_APP_DOMAIN');
$endpoint = $protocol . '://' . $hostname . "/v1";
@@ -1146,17 +1146,29 @@ App::error()
$trace = $error->getTrace();
if (php_sapi_name() === 'cli') {
Console::error('[Error] Timestamp: ' . date('c', time()));
$logLevel = $code >= 500 || $code == 0 ? 'error' : 'warning';
$logPrefix = $code >= 500 || $code == 0 ? '[Error]' : '[Warning]';
Console::$logLevel($logPrefix . ' Timestamp: ' . date('c', time()));
if ($route) {
Console::error('[Error] Method: ' . $route->getMethod());
Console::error('[Error] URL: ' . $route->getPath());
Console::$logLevel($logPrefix . ' Status Code: ' . $code);
Console::$logLevel($logPrefix . ' URL: ' . $route->getMethod() . ' ' . $route->getPath());
}
Console::error('[Error] Type: ' . get_class($error));
Console::error('[Error] Message: ' . $message);
Console::error('[Error] File: ' . $file);
Console::error('[Error] Line: ' . $line);
Console::$logLevel($logPrefix . ' Type: ' . get_class($error));
Console::$logLevel($logPrefix . ' Message: ' . $message);
Console::$logLevel($logPrefix . ' File: ' . $file);
Console::$logLevel($logPrefix . ' Line: ' . $line);
Console::$logLevel($logPrefix . ' Trace:');
foreach ($trace as $index => $entry) {
$traceFile = $entry['file'] ?? 'unknown';
$traceLine = $entry['line'] ?? 0;
$traceFunction = $entry['function'] ?? 'unknown';
$traceClass = $entry['class'] ?? '';
$traceType = $entry['type'] ?? '';
Console::$logLevel(" #{$index} {$traceFile}({$traceLine}): {$traceClass}{$traceType}{$traceFunction}()");
}
Console::$logLevel('');
}
switch ($class) {
+69 -10
View File
@@ -214,38 +214,93 @@ App::init()
->action(function (App $utopia, Request $request, Database $dbForPlatform, Database $dbForProject, Audit $queueForAudits, Document $project, Document $user, ?Document $session, array $servers, string $mode, Document $team, ?Key $apiKey) {
$route = $utopia->getRoute();
/**
* Handle user authentication and session validation.
*
* This function follows a series of steps to determine the appropriate user session
* based on cookies, headers, and JWT tokens.
*
* Process:
*
* Project & Role Validation:
* 1. Check if the project is empty. If so, throw an exception.
* 2. Get the roles configuration.
* 3. Determine the role for the user based on the user document.
* 4. Get the scopes for the role.
*
* API Key Authentication:
* 5. If there is an API key:
* - Verify no user session exists simultaneously
* - Check if key is expired
* - Set role and scopes from API key
* - Handle special app role case
* - For standard keys, update last accessed time
*
* User Activity:
* 6. If the project is not the console and user is not admin:
* - Update user's last activity timestamp
*
* Access Control:
* 7. Get the method from the route
* 8. Validate namespace permissions
* 9. Validate scope permissions
* 10. Check if user is blocked
*
* Security Checks:
* 11. Verify password status (check if reset required)
* 12. Validate MFA requirements:
* - Check if MFA is enabled
* - Verify email status
* - Verify phone status
* - Verify authenticator status
* 13. Handle Multi-Factor Authentication:
* - Check remaining required factors
* - Validate factor completion
* - Throw exception if factors incomplete
*/
// Step 1: Check if project is empty
if ($project->isEmpty()) {
throw new Exception(Exception::PROJECT_NOT_FOUND);
}
// Step 2: Get roles configuration
$roles = Config::getParam('roles', []);
// Step 3: Determine role for user
// TODO get scopes from the identity instead of the user roles config. The identity will containn the scopes the user authorized for the access token.
$role = $user->isEmpty()
? Role::guests()->toString()
: Role::users()->toString();
// Step 4: Get scopes for the role
$scopes = $roles[$role]['scopes'];
// API Key authentication
// Step 5: API Key Authentication
if (!empty($apiKey)) {
// Verify no user session exists simultaneously
if (!$user->isEmpty()) {
throw new Exception(Exception::USER_API_KEY_AND_SESSION_SET);
}
// Check if key is expired
if ($apiKey->isExpired()) {
throw new Exception(Exception::PROJECT_KEY_EXPIRED);
}
// Set role and scopes from API key
$role = $apiKey->getRole();
$scopes = $apiKey->getScopes();
// Disable authorization checks for API keys
Authorization::setDefaultStatus(false);
if ($apiKey->getRole() === Auth::USER_ROLE_APPS) {
// Handle special app role case
if ($apiKey->getRole() === USER_ROLE_APPS) {
$user = new Document([
'$id' => '',
'status' => true,
'type' => Auth::ACTIVITY_TYPE_APP,
'type' => ACTIVITY_TYPE_APP,
'email' => 'app.' . $project->getId() . '@service.' . $request->getHostname(),
'password' => '',
'name' => $apiKey->getName(),
@@ -254,6 +309,7 @@ App::init()
$queueForAudits->setUser($user);
}
// For standard keys, update last accessed time
if ($apiKey->getType() === API_KEY_STANDARD) {
$dbKey = $project->find(
key: 'secret',
@@ -323,7 +379,7 @@ App::init()
Authorization::setRole($authRole);
}
// Update project last activity
// Step 6: Update project and user last activity
if (!$project->isEmpty() && $project->getId() !== 'console') {
$accessedAt = $project->getAttribute('accessedAt', 0);
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_PROJECT_ACCESS)) > $accessedAt) {
@@ -332,7 +388,6 @@ App::init()
}
}
// Update user last activity
if (!empty($user->getId())) {
$accessedAt = $user->getAttribute('accessedAt', 0);
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_USER_ACCESS)) > $accessedAt) {
@@ -346,6 +401,7 @@ App::init()
}
}
// Steps 7-9: Access Control - Method, Namespace and Scope Validation
/**
* @var ?Method $method
*/
@@ -369,21 +425,23 @@ App::init()
}
}
// Do now allow access if scope is not allowed
// Step 9: Validate scope permissions
$scope = $route->getLabel('scope', 'none');
if (!\in_array($scope, $scopes)) {
throw new Exception(Exception::GENERAL_UNAUTHORIZED_SCOPE, $user->getAttribute('email', 'User') . ' (role: ' . \strtolower($roles[$role]['label']) . ') missing scope (' . $scope . ')');
}
// Do not allow access to blocked accounts
// Step 10: Check if user is blocked
if (false === $user->getAttribute('status')) { // Account is blocked
throw new Exception(Exception::USER_BLOCKED);
}
// Step 11: Verify password status
if ($user->getAttribute('reset')) {
throw new Exception(Exception::USER_PASSWORD_RESET_REQUIRED);
}
// Step 12: Validate MFA requirements
$mfaEnabled = $user->getAttribute('mfa', false);
$hasVerifiedEmail = $user->getAttribute('emailVerification', false);
$hasVerifiedPhone = $user->getAttribute('phoneVerification', false);
@@ -391,6 +449,7 @@ App::init()
$hasMoreFactors = $hasVerifiedEmail || $hasVerifiedPhone || $hasVerifiedAuthenticator;
$minimumFactors = ($mfaEnabled && $hasMoreFactors) ? 2 : 1;
// Step 13: Handle Multi-Factor Authentication
if (!in_array('mfa', $route->getGroups())) {
if ($session && \count($session->getAttribute('factors', [])) < $minimumFactors) {
throw new Exception(Exception::USER_MORE_FACTORS_REQUIRED);
@@ -514,7 +573,7 @@ App::init()
if (!$user->isEmpty()) {
$userClone = clone $user;
// $user doesn't support `type` and can cause unintended effects.
$userClone->setAttribute('type', Auth::ACTIVITY_TYPE_USER);
$userClone->setAttribute('type', ACTIVITY_TYPE_USER);
$queueForAudits->setUser($userClone);
}
@@ -752,7 +811,7 @@ App::shutdown()
if (!$user->isEmpty()) {
$userClone = clone $user;
// $user doesn't support `type` and can cause unintended effects.
$userClone->setAttribute('type', Auth::ACTIVITY_TYPE_USER);
$userClone->setAttribute('type', ACTIVITY_TYPE_USER);
$queueForAudits->setUser($userClone);
} elseif ($queueForAudits->getUser() === null || $queueForAudits->getUser()->isEmpty()) {
/**
@@ -766,7 +825,7 @@ App::shutdown()
$user = new Document([
'$id' => '',
'status' => true,
'type' => Auth::ACTIVITY_TYPE_GUEST,
'type' => ACTIVITY_TYPE_GUEST,
'email' => 'guest.' . $project->getId() . '@service.' . $request->getHostname(),
'password' => '',
'name' => 'Guest',
+1 -1
View File
@@ -20,7 +20,7 @@ App::init()
$lastUpdate = $session->getAttribute('mfaUpdatedAt');
if (!empty($lastUpdate)) {
$now = DateTime::now();
$maxAllowedDate = DateTime::addSeconds(new \DateTime($lastUpdate), Auth::MFA_RECENT_DURATION); // Maximum date until session is considered safe before asking for another challenge
$maxAllowedDate = DateTime::addSeconds(new \DateTime($lastUpdate), MFA_RECENT_DURATION); // Maximum date until session is considered safe before asking for another challenge
$isSessionFresh = DateTime::formatTz($maxAllowedDate) >= DateTime::formatTz($now);
}
+66 -1
View File
@@ -81,6 +81,72 @@ const APP_PLATFORM_SERVER = 'server';
const APP_PLATFORM_CLIENT = 'client';
const APP_PLATFORM_CONSOLE = 'console';
// User Roles
const USER_ROLE_ANY = 'any';
const USER_ROLE_GUESTS = 'guests';
const USER_ROLE_USERS = 'users';
const USER_ROLE_ADMIN = 'admin';
const USER_ROLE_DEVELOPER = 'developer';
const USER_ROLE_OWNER = 'owner';
const USER_ROLE_APPS = 'apps';
const USER_ROLE_SYSTEM = 'system';
/**
* Token Expiration times.
*/
const TOKEN_EXPIRATION_LOGIN_LONG = 31536000; /* 1 year */
const TOKEN_EXPIRATION_LOGIN_SHORT = 3600; /* 1 hour */
const TOKEN_EXPIRATION_RECOVERY = 3600; /* 1 hour */
const TOKEN_EXPIRATION_CONFIRM = 3600 * 1; /* 1 hour */
const TOKEN_EXPIRATION_OTP = 60 * 15; /* 15 minutes */
const TOKEN_EXPIRATION_GENERIC = 60 * 15; /* 15 minutes */
/**
* Token Lengths.
*/
const TOKEN_LENGTH_MAGIC_URL = 64;
const TOKEN_LENGTH_VERIFICATION = 256;
const TOKEN_LENGTH_RECOVERY = 256;
const TOKEN_LENGTH_OAUTH2 = 64;
const TOKEN_LENGTH_SESSION = 256;
/**
* Token Types.
*/
const TOKEN_TYPE_LOGIN = 1; // Deprecated
const TOKEN_TYPE_VERIFICATION = 2;
const TOKEN_TYPE_RECOVERY = 3;
const TOKEN_TYPE_INVITE = 4;
const TOKEN_TYPE_MAGIC_URL = 5;
const TOKEN_TYPE_PHONE = 6;
const TOKEN_TYPE_OAUTH2 = 7;
const TOKEN_TYPE_GENERIC = 8;
const TOKEN_TYPE_EMAIL = 9; // OTP
/**
* Session Providers.
*/
const SESSION_PROVIDER_EMAIL = 'email';
const SESSION_PROVIDER_ANONYMOUS = 'anonymous';
const SESSION_PROVIDER_MAGIC_URL = 'magic-url';
const SESSION_PROVIDER_PHONE = 'phone';
const SESSION_PROVIDER_OAUTH2 = 'oauth2';
const SESSION_PROVIDER_TOKEN = 'token';
const SESSION_PROVIDER_SERVER = 'server';
/**
* Activity associated with user or the app.
*/
const ACTIVITY_TYPE_APP = 'app';
const ACTIVITY_TYPE_USER = 'user';
const ACTIVITY_TYPE_GUEST = 'guest';
/**
* MFA
*/
const MFA_RECENT_DURATION = 1800; // 30 mins
// Database Reconnect
const DATABASE_RECONNECT_SLEEP = 2;
const DATABASE_RECONNECT_MAX_ATTEMPTS = 10;
@@ -255,7 +321,6 @@ const METRIC_SITES_ID_INBOUND = 'sites.{siteInternalId}.inbound';
const METRIC_SITES_ID_OUTBOUND = 'sites.{siteInternalId}.outbound';
// Resource types
const RESOURCE_TYPE_PROJECTS = 'projects';
const RESOURCE_TYPE_FUNCTIONS = 'functions';
const RESOURCE_TYPE_SITES = 'sites';
+76 -22
View File
@@ -24,6 +24,12 @@ use Appwrite\Utopia\Request;
use Executor\Executor;
use Utopia\Abuse\Adapters\TimeLimit\Redis as TimeLimitRedis;
use Utopia\App;
use Utopia\Auth\Hashes\Argon2;
use Utopia\Auth\Hashes\Sha;
use Utopia\Auth\Proofs\Code;
use Utopia\Auth\Proofs\Password;
use Utopia\Auth\Proofs\Token;
use Utopia\Auth\Store;
use Utopia\Cache\Adapter\Pool as CachePool;
use Utopia\Cache\Adapter\Sharding;
use Utopia\Cache\Cache;
@@ -173,72 +179,89 @@ App::setResource('clients', function ($request, $console, $project) {
return \array_unique($clients);
}, ['request', 'console', 'project']);
App::setResource('user', function ($mode, $project, $console, $request, $response, $dbForProject, $dbForPlatform) {
App::setResource('user', function ($mode, $project, $console, $request, $response, $dbForProject, $dbForPlatform, Store $store, Token $proofForToken) {
/** @var Appwrite\Utopia\Request $request */
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Document $project */
/** @var Utopia\Database\Database $dbForProject */
/** @var Utopia\Database\Database $dbForPlatform */
/** @var string $mode */
/** @var Utopia\Auth\Store $store */
/**
* Handles user authentication and session validation.
*
* This function follows a series of steps to determine the appropriate user session
* based on cookies, headers, and JWT tokens.
*
* Process:
* 1. Checks the cookie based on mode:
* - If in admin mode, redirects to the console.
* - Otherwise, retrieves the project ID from the cookie.
* 2. If no cookie is found, attempts to retrieve the fallback header `x-fallback-cookies`.
* - If this method is used, returns the header: `X-Debug-Fallback: true`.
* 3. Fetches the user document from the appropriate database based on the mode.
* 4. If the user document is empty or the session key cannot be verified, sets an empty user document.
* 5. Regardless of the results from steps 1-4, attempts to fetch the JWT token.
* 6. If the JWT user has a valid session ID, updates the user variable with the user from `projectDB`,
* overwriting the previous value.
*/
Authorization::setDefaultStatus(true);
Auth::setCookieName('a_session_' . $project->getId());
$store->setKey('a_session_' . $project->getId());
if (APP_MODE_ADMIN === $mode) {
Auth::setCookieName('a_session_' . $console->getId());
$store->setKey('a_session_' . $console->getId());
}
$session = Auth::decodeSession(
$store->decode(
$request->getCookie(
Auth::$cookieName, // Get sessions
$request->getCookie(Auth::$cookieName . '_legacy', '')
$store->getKey(), // Get sessions
$request->getCookie($store->getKey() . '_legacy', '')
)
);
// Get session from header for SSR clients
if (empty($session['id']) && empty($session['secret'])) {
if (empty($store->getProperty('id', '')) && empty($store->getProperty('secret', ''))) {
$sessionHeader = $request->getHeader('x-appwrite-session', '');
if (!empty($sessionHeader)) {
$session = Auth::decodeSession($sessionHeader);
$store->decode($sessionHeader);
}
}
// Get fallback session from old clients (no SameSite support) or clients who block 3rd-party cookies
if ($response) {
if ($response) { // if in http context - add debug header
$response->addHeader('X-Debug-Fallback', 'false');
}
if (empty($session['id']) && empty($session['secret'])) {
if (empty($store->getProperty('id', '')) && empty($store->getProperty('secret', ''))) {
if ($response) {
$response->addHeader('X-Debug-Fallback', 'true');
}
$fallback = $request->getHeader('x-fallback-cookies', '');
$fallback = \json_decode($fallback, true);
$session = Auth::decodeSession(((isset($fallback[Auth::$cookieName])) ? $fallback[Auth::$cookieName] : ''));
$store->decode(((isset($fallback[$store->getKey()])) ? $fallback[$store->getKey()] : ''));
}
Auth::$unique = $session['id'] ?? '';
Auth::$secret = $session['secret'] ?? '';
if (APP_MODE_ADMIN !== $mode) {
if ($project->isEmpty()) {
$user = new Document([]);
} else {
if ($project->getId() === 'console') {
$user = $dbForPlatform->getDocument('users', Auth::$unique);
$user = $dbForPlatform->getDocument('users', $store->getProperty('id', ''));
} else {
$user = $dbForProject->getDocument('users', Auth::$unique);
$user = $dbForProject->getDocument('users', $store->getProperty('id', ''));
}
}
} else {
$user = $dbForPlatform->getDocument('users', Auth::$unique);
$user = $dbForPlatform->getDocument('users', $store->getProperty('id', ''));
}
if (
$user->isEmpty() // Check a document has been found in the DB
|| !Auth::sessionVerify($user->getAttribute('sessions', []), Auth::$secret)
|| !Auth::sessionVerify($user->getAttribute('sessions', []), $store->getProperty('secret', ''), $proofForToken)
) { // Validate user has valid login token
$user = new Document([]);
}
@@ -279,7 +302,7 @@ App::setResource('user', function ($mode, $project, $console, $request, $respons
$dbForPlatform->setMetadata('user', $user->getId());
return $user;
}, ['mode', 'project', 'console', 'request', 'response', 'dbForProject', 'dbForPlatform']);
}, ['mode', 'project', 'console', 'request', 'response', 'dbForProject', 'dbForPlatform', 'store', 'proofForToken']);
App::setResource('project', function ($dbForPlatform, $request, $console) {
/** @var Appwrite\Utopia\Request $request */
@@ -297,13 +320,13 @@ App::setResource('project', function ($dbForPlatform, $request, $console) {
return $project;
}, ['dbForPlatform', 'request', 'console']);
App::setResource('session', function (Document $user) {
App::setResource('session', function (Document $user, Store $store, Token $proofForToken) {
if ($user->isEmpty()) {
return;
}
$sessions = $user->getAttribute('sessions', []);
$sessionId = Auth::sessionVerify($user->getAttribute('sessions'), Auth::$secret);
$sessionId = Auth::sessionVerify($user->getAttribute('sessions'), $store->getProperty('secret', ''), $proofForToken);
if (!$sessionId) {
return;
@@ -316,7 +339,7 @@ App::setResource('session', function (Document $user) {
}
return;
}, ['user']);
}, ['user', 'store', 'proofForToken']);
App::setResource('console', function () {
return new Document(Config::getParam('console'));
@@ -880,6 +903,37 @@ App::setResource('apiKey', function (Request $request, Document $project): ?Key
return Key::decode($project, $key);
}, ['request', 'project']);
App::setResource('store', function (): Store {
return new Store();
});
App::setResource('proofForPassword', function (): Password {
$hash = new Argon2();
$hash
->setMemoryCost(2048)
->setTimeCost(4)
->setThreads(3);
$password = new Password();
$password
->setHash($hash);
return $password;
});
App::setResource('proofForToken', function (): Token {
$token = new Token();
$token->setHash(new Sha());
return $token;
});
App::setResource('proofForCode', function (): Code {
$code = new Code();
$code->setHash(new Sha());
return $code;
});
App::setResource('executor', fn () => new Executor());
App::setResource('resourceToken', function ($project, $dbForProject, $request) {
+17 -5
View File
@@ -16,6 +16,9 @@ use Swoole\Timer;
use Utopia\Abuse\Abuse;
use Utopia\Abuse\Adapters\TimeLimit\Redis as TimeLimitRedis;
use Utopia\App;
use Utopia\Auth\Hashes\Sha;
use Utopia\Auth\Proofs\Token;
use Utopia\Auth\Store;
use Utopia\Cache\Adapter\Pool as CachePool;
use Utopia\Cache\Adapter\Sharding;
use Utopia\Cache\Cache;
@@ -671,15 +674,24 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re
throw new Exception(Exception::REALTIME_MESSAGE_FORMAT_INVALID, 'Payload is not valid.');
}
$session = Auth::decodeSession($message['data']['session']);
Auth::$unique = $session['id'] ?? '';
Auth::$secret = $session['secret'] ?? '';
$store = new Store();
$user = $database->getDocument('users', Auth::$unique);
$store->decode($message['data']['session']);
$user = $database->getDocument('users', $store->getProperty('id', ''));
/**
* TODO:
* Moving forward, we should try to use our dependency injection container
* to inject the proof for token.
* This way we will have one source of truth for the proof for token.
*/
$proofForToken = new Token();
$proofForToken->setHash(new Sha());
if (
empty($user->getId()) // Check a document has been found in the DB
|| !Auth::sessionVerify($user->getAttribute('sessions', []), Auth::$secret) // Validate user has valid login token
|| !Auth::sessionVerify($user->getAttribute('sessions', []), $store->getProperty('secret', ''), $proofForToken) // Validate user has valid login token
) {
// cookie not valid
throw new Exception(Exception::REALTIME_MESSAGE_FORMAT_INVALID, 'Session is not valid.');
+1 -1
View File
@@ -15,7 +15,7 @@ $labelClass = '';
$buttons = [];
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https';
$hostname = System::getEnv('_APP_CONSOLE_DOMAIN', System::getEnv('_APP_DOMAIN', ''));
$hostname = System::getEnv('_APP_DOMAIN');
// TODO: remove this later
if (System::getEnv('_APP_ENV') === 'development') {
$hostname = 'localhost';
+11 -2
View File
@@ -18,7 +18,9 @@ use Appwrite\Event\StatsUsage;
use Appwrite\Event\Webhook;
use Appwrite\Platform\Appwrite;
use Executor\Executor;
use Swoole\Process;
use Swoole\Runtime;
use Swoole\Timer;
use Utopia\Abuse\Adapters\TimeLimit\Redis as TimeLimitRedis;
use Utopia\Cache\Adapter\Pool as CachePool;
use Utopia\Cache\Adapter\Sharding;
@@ -482,8 +484,15 @@ $worker
});
$worker->workerStart()
->action(function () use ($workerName) {
Console::info("Worker $workerName started");
->action(function () use ($worker, $workerName) {
Console::info("Worker $workerName started");
Process::signal(SIGTERM, function () use ($worker, $workerName) {
Console::info("Stopping worker $workerName.");
$worker->stop();
Timer::clearAll();
});
});
$worker->start();
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php doctor $@
exec php /usr/src/code/app/cli.php doctor $@
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php install $@
exec php /usr/src/code/app/cli.php install $@
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php maintenance $@
exec php /usr/src/code/app/cli.php maintenance $@
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php migrate $@
exec php /usr/src/code/app/cli.php migrate $@
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php queue-count --type=failed $@
exec php /usr/src/code/app/cli.php queue-count --type=failed $@
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php queue-count --type=processing $@
exec php /usr/src/code/app/cli.php queue-count --type=processing $@
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php queue-count --type=success $@
exec php /usr/src/code/app/cli.php queue-count --type=success $@
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php queue-retry $@
exec php /usr/src/code/app/cli.php queue-retry $@
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/realtime.php $@
exec php /usr/src/code/app/realtime.php $@
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php schedule-executions $@
exec php /usr/src/code/app/cli.php schedule-executions $@
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php schedule-functions $@
exec php /usr/src/code/app/cli.php schedule-functions $@
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php schedule-messages $@
exec php /usr/src/code/app/cli.php schedule-messages $@
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php screenshot $@
exec php /usr/src/code/app/cli.php screenshot $@
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php sdks $@
exec php /usr/src/code/app/cli.php sdks $@
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php specs $@
exec php /usr/src/code/app/cli.php specs $@
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php ssl $@
exec php /usr/src/code/app/cli.php ssl $@
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php stats-resources $@
exec php /usr/src/code/app/cli.php stats-resources $@
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
/usr/src/code/vendor/bin/phpunit --configuration /usr/src/code/phpunit.xml $@
exec /usr/src/code/vendor/bin/phpunit --configuration /usr/src/code/phpunit.xml $@
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php upgrade $@
exec php /usr/src/code/app/cli.php upgrade $@

Some files were not shown because too many files have changed in this diff Show More